mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-31 04:14:38 +08:00
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。 按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩, CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。 修复空值、异步和供应方类型边界,保持插件与 API 兼容合同, 并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
167 lines
5.9 KiB
Python
167 lines
5.9 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.oncejob import SchedulerPluginOnceJobOwner
|
|
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,
|
|
SchedulerPluginOnceJobOwner,
|
|
SchedulerBridgeOwner,
|
|
SchedulerProgressOwner,
|
|
SchedulerExecutionOwner,
|
|
SchedulerCatalogOwner,
|
|
SchedulerMaintenanceOwner,
|
|
ConfigReloadMixin,
|
|
metaclass=SingletonClass,
|
|
):
|
|
"""
|
|
定时任务管理
|
|
"""
|
|
|
|
__module__ = _PUBLIC_MODULE
|
|
|
|
CONFIG_WATCH = {
|
|
"DEV",
|
|
"TZ",
|
|
# 壁纸来源或地址变化时重建任务并清理来源缓存。
|
|
"WALLPAPER",
|
|
"WALLPAPER_IMAGE_URL",
|
|
"CUSTOMIZE_WALLPAPER_API_URL",
|
|
"COOKIECLOUD_INTERVAL",
|
|
# 凭据补全或清空时重建任务,避免未配置时定时刷失败日志。
|
|
"COOKIECLOUD_HOST",
|
|
"COOKIECLOUD_KEY",
|
|
"COOKIECLOUD_PASSWORD",
|
|
"COOKIECLOUD_ENABLE_LOCAL",
|
|
"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",
|
|
"MOVIEPILOT_AUTO_UPDATE",
|
|
"AUTO_UPDATE_RESOURCE",
|
|
}
|
|
|
|
def __init__(self) -> None:
|
|
"""
|
|
创建调度器状态;后台任务由应用生命周期显式启动。
|
|
"""
|
|
# 定时服务
|
|
self._scheduler = None
|
|
# 退出事件
|
|
self._event = threading.Event()
|
|
# 锁
|
|
self._lock = threading.RLock()
|
|
# 各服务的运行状态
|
|
self._jobs = {}
|
|
# 热重载期间保留上一代目录,避免仪表盘暂时显示空列表。
|
|
self._reload_schedule_snapshot: list[Any] | None = None
|
|
# 生命周期门禁与事件循环句柄由调度器实例独立持有。
|
|
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:
|
|
"""
|
|
插件重载后按当前实例重新注册全部定时服务
|
|
"""
|
|
event_data = event.event_data if isinstance(event.event_data, dict) else {}
|
|
plugin_id = event_data.get("plugin_id")
|
|
if not plugin_id:
|
|
return
|
|
self.update_plugin_job(plugin_id)
|