mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-11 16:21:09 +08:00
136 lines
5.0 KiB
Python
136 lines
5.0 KiB
Python
"""调度器具体实现的组合门面与旧插件 ABI。"""
|
|
|
|
import threading
|
|
from collections.abc import Callable
|
|
from typing import Any, TypeVar
|
|
|
|
from app.application.agenttask import AgentTaskRepository
|
|
from app.foundation.singleton import SingletonClass
|
|
from app.runtime.events import Event, eventmanager
|
|
from app.runtime.reload import ConfigReloadMixin
|
|
from app.scheduler.bridge import SchedulerBridgeOwner
|
|
from app.scheduler.catalog import SchedulerCatalogOwner
|
|
from app.scheduler.execution import SchedulerExecutionOwner
|
|
from app.scheduler.lifecycle import SchedulerLifecycleOwner
|
|
from app.scheduler.maintenance import SchedulerMaintenanceOwner
|
|
from app.scheduler.progress import SchedulerProgressOwner
|
|
from app.scheduler.reconcile import SchedulerReconcileOwner
|
|
from app.scheduler.registry import ExecutionRegistry
|
|
from app.scheduler.services import SchedulerServices
|
|
from app.schemas.types import EventType, SystemConfigKey
|
|
|
|
_PUBLIC_MODULE = "app.scheduler"
|
|
_Handler = TypeVar("_Handler", bound=Callable[..., Any])
|
|
|
|
|
|
def _public_handler(handler: _Handler) -> _Handler:
|
|
"""在事件注册前恢复插件可见的稳定模块身份。"""
|
|
handler.__module__ = _PUBLIC_MODULE
|
|
return handler
|
|
|
|
|
|
class Scheduler(
|
|
SchedulerLifecycleOwner,
|
|
SchedulerReconcileOwner,
|
|
SchedulerBridgeOwner,
|
|
SchedulerProgressOwner,
|
|
SchedulerExecutionOwner,
|
|
SchedulerCatalogOwner,
|
|
SchedulerMaintenanceOwner,
|
|
ConfigReloadMixin,
|
|
metaclass=SingletonClass,
|
|
):
|
|
"""
|
|
定时任务管理
|
|
"""
|
|
|
|
__module__ = _PUBLIC_MODULE
|
|
|
|
CONFIG_WATCH = {
|
|
"DEV",
|
|
"COOKIECLOUD_INTERVAL",
|
|
"MEDIASERVER_SYNC_INTERVAL",
|
|
SystemConfigKey.MediaServers.value,
|
|
"SUBSCRIBE_SEARCH",
|
|
"SUBSCRIBE_SEARCH_INTERVAL",
|
|
"SUBSCRIBE_MODE",
|
|
"SUBSCRIBE_RSS_INTERVAL",
|
|
"SITEDATA_REFRESH_INTERVAL",
|
|
"AI_AGENT_ENABLE",
|
|
"AI_AGENT_JOB_INTERVAL",
|
|
"DATA_CLEANUP_ENABLE",
|
|
"DATA_CLEANUP_MESSAGE_DAYS",
|
|
"DATA_CLEANUP_DOWNLOAD_HISTORY_DAYS",
|
|
"DATA_CLEANUP_SITE_USERDATA_DAYS",
|
|
"DATA_CLEANUP_TRANSFER_HISTORY_DAYS",
|
|
"DATA_CLEANUP_DOWNLOAD_FAILURE_DAYS",
|
|
"DATA_CLEANUP_SUBSCRIBE_HISTORY_DAYS",
|
|
"DATA_CLEANUP_AGENT_CHAT_DAYS",
|
|
"DATA_CLEANUP_AGENT_TASK_RUN_DAYS",
|
|
"DATA_CLEANUP_OUTBOX_COMPLETED_DAYS",
|
|
"DATA_CLEANUP_OUTBOX_DEAD_DAYS",
|
|
"DB_BACKUP_ENABLE",
|
|
"DB_BACKUP_CRON",
|
|
"USAGE_STATISTIC_SHARE",
|
|
}
|
|
|
|
def __init__(self) -> None:
|
|
"""创建调度器状态;后台任务由应用生命周期显式启动。"""
|
|
# 定时服务
|
|
self._scheduler = None
|
|
# 退出事件
|
|
self._event = threading.Event()
|
|
# 锁
|
|
self._lock = threading.RLock()
|
|
# 各服务的运行状态
|
|
self._jobs = {}
|
|
# 生命周期门禁与事件循环句柄由调度器实例独立持有。
|
|
self._lifecycle_state = "new"
|
|
self._registry = ExecutionRegistry(self._lock)
|
|
# 进程启动时只对账一次,配置热重载不得改写仍在执行的任务状态
|
|
self._agent_task_interruptions_reconciled = False
|
|
# 用户认证失败次数
|
|
self._auth_count = 0
|
|
# 用户认证失败消息发送
|
|
self._auth_message = False
|
|
# 插件已按认证结果重建,但动态路由尚未完成投影时保留重试状态。
|
|
self._auth_plugin_routes_pending = False
|
|
self._agent_tasks: AgentTaskRepository | None = None
|
|
self._services: SchedulerServices | None = None
|
|
|
|
def configure_services(self, services: SchedulerServices) -> None:
|
|
"""在调度器启动前绑定由组合根构造的业务能力。"""
|
|
if self._lifecycle_state not in {"new", "stopped"}:
|
|
raise RuntimeError("Scheduler 已运行,不能替换业务能力")
|
|
self._services = services
|
|
|
|
def reset_runtime_bindings(self) -> None:
|
|
"""在调度器停止后撤销当前 lifespan 的仓储与业务能力引用。"""
|
|
with self._lock:
|
|
if self._lifecycle_state not in {"new", "stopped"}:
|
|
raise RuntimeError("Scheduler 仍在运行,不能撤销运行时绑定")
|
|
self._agent_tasks = None
|
|
self._services = None
|
|
self._agent_task_interruptions_reconciled = False
|
|
|
|
def _scheduler_services(self) -> SchedulerServices:
|
|
"""返回显式注入能力;缺少组合根装配时稳定失败。"""
|
|
if self._services is None:
|
|
raise RuntimeError("Scheduler 的业务能力尚未注入")
|
|
return self._services
|
|
|
|
def get_reload_name(self) -> str:
|
|
"""
|
|
获取配置重载日志中的服务名称。
|
|
"""
|
|
return "定时服务"
|
|
|
|
@eventmanager.register(EventType.PluginReload)
|
|
@_public_handler
|
|
def on_plugin_reload(self, event: Event) -> None:
|
|
"""插件重载后按当前实例重新注册全部定时服务"""
|
|
plugin_id = event.event_data.get("plugin_id")
|
|
if not plugin_id:
|
|
return
|
|
self.update_plugin_job(plugin_id)
|