from __future__ import annotations import os import platform import sys from datetime import datetime from typing import Any, Optional from app.core.config import settings from app.doctor.checks import default_checks from app.doctor.models import ( DoctorFinding, DoctorFindingStatus, DoctorReport, DoctorSeverity, ) from app.utils.system import SystemUtils from version import APP_VERSION class DoctorRunner: """ MoviePilot 离线诊断运行器,负责组合检查项并生成报告。 """ def __init__(self, *, fix: bool = False, deep: bool = False): """ 初始化诊断运行器。 :param fix: 是否执行白名单安全修复 :param deep: 是否执行可能较慢的深度检查 """ self.fix = fix self.deep = deep self.report = DoctorReport( generated_at=datetime.now(), version=APP_VERSION, environment=self._environment(), ) def run(self) -> DoctorReport: """ 执行所有默认诊断检查并返回报告。 """ for check in default_checks(): try: check(self) except Exception as err: self.add( finding_id=f"doctor.check_failed.{check.__name__.lstrip('_')}", severity=DoctorSeverity.Error, status=DoctorFindingStatus.Failed, title="诊断检查自身执行失败", detail=f"{check.__name__}: {str(err)}", recommendation="请把该 doctor 报告附加到反馈 Issue,便于修复诊断器本身。", ) return self.report def add( self, *, finding_id: str, severity: DoctorSeverity, status: DoctorFindingStatus, title: str, detail: str, recommendation: str, fixable: bool = False, fixed: bool = False, context: Optional[dict[str, Any]] = None, ) -> DoctorFinding: """ 添加诊断发现并返回该对象。 """ finding = DoctorFinding( id=finding_id, severity=severity, status=status, title=title, detail=detail, recommendation=recommendation, fixable=fixable, fixed=fixed, context=context or {}, ) self.report.add_finding(finding) return finding @staticmethod def _environment() -> dict[str, Any]: return { "runtime": "Docker" if SystemUtils.is_docker() else platform.system(), "platform": platform.platform(), "python": sys.executable, "python_version": platform.python_version(), "root_path": str(settings.ROOT_PATH), "config_path": str(settings.CONFIG_PATH), "log_path": str(settings.LOG_PATH), "temp_path": str(settings.TEMP_PATH), "is_docker": SystemUtils.is_docker(), "safe_mode": settings.MOVIEPILOT_SAFE_MODE, "pid": os.getpid(), }