mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-11 00:01:55 +08:00
163 lines
7.1 KiB
Python
163 lines
7.1 KiB
Python
"""持久化 Outbox handler 与 dispatcher 的宿主组合装配。"""
|
|
|
|
from collections.abc import Callable
|
|
|
|
from app.adapters.external.server import MoviePilotServerHelper
|
|
from app.application.chain.events import (
|
|
restore_download_added,
|
|
restore_transfer_result,
|
|
)
|
|
from app.application.outbox import (
|
|
ClaimedOutboxMessage,
|
|
OutboxDispatcher,
|
|
durable_event_topic,
|
|
reset_outbox_dispatcher,
|
|
validate_durable_event_handlers,
|
|
)
|
|
from app.command import CommandChain
|
|
from app.db.adapters.outbox import SqlAlchemyOutboxDispatchStore
|
|
from app.db.session import SessionFactory
|
|
from app.runtime.events import EventManager
|
|
from app.runtime.observability import record_metric
|
|
from app.schemas.message import Message
|
|
from app.schemas.types import EventType
|
|
|
|
|
|
def build_outbox_handlers() -> dict[
|
|
str,
|
|
Callable[[ClaimedOutboxMessage], None],
|
|
]:
|
|
"""构造等待真实执行边界的 at-least-once 通知、事件和统计 handler。"""
|
|
event_manager_factory: Callable[[], EventManager] = EventManager
|
|
|
|
def discard_event_receipt(_event: object) -> None:
|
|
"""丢弃普通事件 API 的回执,使 outbox handler 仅表达结算成功。"""
|
|
|
|
def dispatch_subscribe_deleted_report(message: ClaimedOutboxMessage) -> None:
|
|
"""重放订阅删除统计;未确认时抛错以进入有限重试。"""
|
|
if not MoviePilotServerHelper.sub_done_durable(message.payload.get("subscribe_info") or {}):
|
|
raise RuntimeError("订阅删除统计上报未确认")
|
|
|
|
def dispatch_subscribe_added_report(message: ClaimedOutboxMessage) -> None:
|
|
"""重放订阅新增统计;未确认时抛错以进入有限重试。"""
|
|
if not MoviePilotServerHelper.sub_reg_durable(message.payload.get("subscribe_info") or {}):
|
|
raise RuntimeError("订阅新增统计上报未确认")
|
|
|
|
def dispatch_subscribe_complete_report(message: ClaimedOutboxMessage) -> None:
|
|
"""重放订阅完成统计;未确认时抛错以进入有限重试。"""
|
|
if not MoviePilotServerHelper.sub_done_durable(message.payload.get("subscribe_info") or {}):
|
|
raise RuntimeError("订阅完成统计上报未确认")
|
|
|
|
def dispatch_subscribe_notification(message: ClaimedOutboxMessage) -> None:
|
|
"""恢复订阅完成通知;消息快照无需重建领域对象。"""
|
|
snapshot = message.payload.get("message") or {}
|
|
if not isinstance(snapshot, dict):
|
|
raise RuntimeError("订阅完成通知快照格式无效")
|
|
CommandChain().post_message_strict(
|
|
Message.model_validate(snapshot),
|
|
event_key=message.event_key,
|
|
)
|
|
|
|
def dispatch_subscribe_added_notification(message: ClaimedOutboxMessage) -> None:
|
|
"""恢复订阅新增通知;恢复使用提交前冻结的渲染消息快照。"""
|
|
snapshot = message.payload.get("message") or {}
|
|
if not isinstance(snapshot, dict):
|
|
raise RuntimeError("订阅新增通知快照格式无效")
|
|
CommandChain().post_message_strict(
|
|
Message.model_validate(snapshot),
|
|
event_key=message.event_key,
|
|
)
|
|
|
|
handlers: dict[str, Callable[[ClaimedOutboxMessage], None]] = {
|
|
durable_event_topic(EventType.SubscribeAdded): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubscribeAdded,
|
|
message.payload,
|
|
)
|
|
),
|
|
"subscribe.added.report": dispatch_subscribe_added_report,
|
|
"subscribe.added.notification": dispatch_subscribe_added_notification,
|
|
durable_event_topic(EventType.SubscribeModified): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubscribeModified,
|
|
message.payload,
|
|
)
|
|
),
|
|
durable_event_topic(EventType.SubscribeDeleted): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubscribeDeleted,
|
|
message.payload,
|
|
)
|
|
),
|
|
"subscribe.deleted.report": dispatch_subscribe_deleted_report,
|
|
durable_event_topic(EventType.SubscribeComplete): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubscribeComplete,
|
|
message.payload,
|
|
)
|
|
),
|
|
"subscribe.complete.report": dispatch_subscribe_complete_report,
|
|
"subscribe.complete.notification": dispatch_subscribe_notification,
|
|
durable_event_topic(EventType.DownloadAdded): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.DownloadAdded,
|
|
restore_download_added(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.TransferComplete): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.TransferComplete,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.TransferFailed): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.TransferFailed,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.SubtitleTransferComplete): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubtitleTransferComplete,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.SubtitleTransferFailed): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.SubtitleTransferFailed,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.AudioTransferComplete): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.AudioTransferComplete,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
durable_event_topic(EventType.AudioTransferFailed): lambda message: discard_event_receipt(
|
|
event_manager_factory().send_event_strict(
|
|
EventType.AudioTransferFailed,
|
|
restore_transfer_result(message.payload),
|
|
)
|
|
),
|
|
}
|
|
validate_durable_event_handlers(handlers)
|
|
return handlers
|
|
|
|
|
|
def build_outbox_dispatcher() -> OutboxDispatcher:
|
|
"""创建使用独立短事务和 attempt fencing 的恢复 dispatcher。"""
|
|
return OutboxDispatcher(
|
|
repository=SqlAlchemyOutboxDispatchStore(SessionFactory),
|
|
handlers=build_outbox_handlers(),
|
|
failure_observer=lambda dead: record_metric(
|
|
"scheduler.job.dead_letter" if dead else "scheduler.job.retry",
|
|
owner="outbox",
|
|
),
|
|
)
|
|
|
|
|
|
def reset_outbox_services() -> None:
|
|
"""撤销当前 lifespan 发布的 Outbox dispatcher 工厂。"""
|
|
reset_outbox_dispatcher()
|