from typing import Dict, List, Optional, Any, TYPE_CHECKING, Tuple import os import importlib import importlib.util from pathlib import Path import traceback if TYPE_CHECKING: from src.plugin_system.base.base_plugin import BasePlugin from src.common.logger import get_logger from src.plugin_system.core.component_registry import component_registry from src.plugin_system.core.dependency_manager import dependency_manager from src.plugin_system.base.component_types import ComponentType, PluginInfo logger = get_logger("plugin_manager") class PluginManager: """插件管理器 负责加载、初始化和管理所有插件及其组件 """ def __init__(self): self.plugin_directories: List[str] = [] self.loaded_plugins: Dict[str, "BasePlugin"] = {} self.failed_plugins: Dict[str, str] = {} self.plugin_paths: Dict[str, str] = {} # 记录插件名到目录路径的映射 # 确保插件目录存在 self._ensure_plugin_directories() logger.info("插件管理器初始化完成") def _ensure_plugin_directories(self): """确保所有插件目录存在,如果不存在则创建""" default_directories = ["src/plugins/built_in", "plugins"] for directory in default_directories: if not os.path.exists(directory): os.makedirs(directory, exist_ok=True) logger.info(f"创建插件目录: {directory}") if directory not in self.plugin_directories: self.plugin_directories.append(directory) logger.debug(f"已添加插件目录: {directory}") else: logger.warning(f"插件不可重复加载: {directory}") def add_plugin_directory(self, directory: str): """添加插件目录""" if os.path.exists(directory): if directory not in self.plugin_directories: self.plugin_directories.append(directory) logger.debug(f"已添加插件目录: {directory}") else: logger.warning(f"插件不可重复加载: {directory}") else: logger.warning(f"插件目录不存在: {directory}") def load_all_plugins(self) -> tuple[int, int]: """加载所有插件目录中的插件 Returns: tuple[int, int]: (插件数量, 组件数量) """ logger.debug("开始加载所有插件...") # 第一阶段:加载所有插件模块(注册插件类) total_loaded_modules = 0 total_failed_modules = 0 for directory in self.plugin_directories: loaded, failed = self._load_plugin_modules_from_directory(directory) total_loaded_modules += loaded total_failed_modules += failed logger.debug(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}") # 第二阶段:实例化所有已注册的插件类 from src.plugin_system.base.base_plugin import get_registered_plugin_classes plugin_classes = get_registered_plugin_classes() total_registered = 0 total_failed_registration = 0 for plugin_name, plugin_class in plugin_classes.items(): try: # 使用记录的插件目录路径 plugin_dir = self.plugin_paths.get(plugin_name) # 如果没有记录,则尝试查找(fallback) if not plugin_dir: plugin_dir = self._find_plugin_directory(plugin_class) if plugin_dir: self.plugin_paths[plugin_name] = plugin_dir # 实例化插件(可能因为缺少manifest而失败) plugin_instance = plugin_class(plugin_dir=plugin_dir) # 检查插件是否启用 if not plugin_instance.enable_plugin: logger.info(f"插件 {plugin_name} 已禁用,跳过加载") continue # 检查版本兼容性 is_compatible, compatibility_error = self.check_plugin_version_compatibility( plugin_name, plugin_instance.manifest_data ) if not is_compatible: total_failed_registration += 1 self.failed_plugins[plugin_name] = compatibility_error logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}") continue if plugin_instance.register_plugin(): total_registered += 1 self.loaded_plugins[plugin_name] = plugin_instance # 📊 显示插件详细信息 plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info: component_types = {} for comp in plugin_info.components: comp_type = comp.component_type.name component_types[comp_type] = component_types.get(comp_type, 0) + 1 components_str = ", ".join([f"{count}个{ctype}" for ctype, count in component_types.items()]) # 显示manifest信息 manifest_info = "" if plugin_info.license: manifest_info += f" [{plugin_info.license}]" if plugin_info.keywords: manifest_info += f" 关键词: {', '.join(plugin_info.keywords[:3])}" # 只显示前3个关键词 if len(plugin_info.keywords) > 3: manifest_info += "..." logger.info( f"✅ 插件加载成功: {plugin_name} v{plugin_info.version} ({components_str}){manifest_info} - {plugin_info.description}" ) else: logger.info(f"✅ 插件加载成功: {plugin_name}") else: total_failed_registration += 1 self.failed_plugins[plugin_name] = "插件注册失败" logger.error(f"❌ 插件注册失败: {plugin_name}") except FileNotFoundError as e: # manifest文件缺失 total_failed_registration += 1 error_msg = f"缺少manifest文件: {str(e)}" self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") except ValueError as e: # manifest文件格式错误或验证失败 traceback.print_exc() total_failed_registration += 1 error_msg = f"manifest验证失败: {str(e)}" self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") except Exception as e: # 其他错误 total_failed_registration += 1 error_msg = f"未知错误: {str(e)}" self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") logger.debug("详细错误信息: ", exc_info=True) # 获取组件统计信息 stats = component_registry.get_registry_stats() action_count = stats.get("action_components", 0) command_count = stats.get("command_components", 0) total_components = stats.get("total_components", 0) # 📋 显示插件加载总览 if total_registered > 0: logger.info("🎉 插件系统加载完成!") logger.info( f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count})" ) # 显示详细的插件列表 logger.info("📋 已加载插件详情:") for plugin_name, _plugin_class in self.loaded_plugins.items(): plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info: # 插件基本信息 version_info = f"v{plugin_info.version}" if plugin_info.version else "" author_info = f"by {plugin_info.author}" if plugin_info.author else "unknown" license_info = f"[{plugin_info.license}]" if plugin_info.license else "" info_parts = [part for part in [version_info, author_info, license_info] if part] extra_info = f" ({', '.join(info_parts)})" if info_parts else "" logger.info(f" 📦 {plugin_name}{extra_info}") # Manifest信息 if plugin_info.manifest_data: if plugin_info.keywords: logger.info(f" 🏷️ 关键词: {', '.join(plugin_info.keywords)}") if plugin_info.categories: logger.info(f" 📁 分类: {', '.join(plugin_info.categories)}") if plugin_info.homepage_url: logger.info(f" 🌐 主页: {plugin_info.homepage_url}") # 组件列表 if plugin_info.components: action_components = [c for c in plugin_info.components if c.component_type.name == "ACTION"] command_components = [c for c in plugin_info.components if c.component_type.name == "COMMAND"] if action_components: action_names = [c.name for c in action_components] logger.info(f" 🎯 Action组件: {', '.join(action_names)}") if command_components: command_names = [c.name for c in command_components] logger.info(f" ⚡ Command组件: {', '.join(command_names)}") # 版本兼容性信息 if plugin_info.min_host_version or plugin_info.max_host_version: version_range = "" if plugin_info.min_host_version: version_range += f">={plugin_info.min_host_version}" if plugin_info.max_host_version: if version_range: version_range += f", <={plugin_info.max_host_version}" else: version_range += f"<={plugin_info.max_host_version}" logger.info(f" 📋 兼容版本: {version_range}") # 依赖信息 if plugin_info.dependencies: logger.info(f" 🔗 依赖: {', '.join(plugin_info.dependencies)}") # 配置文件信息 if plugin_info.config_file: config_status = "✅" if self.plugin_paths.get(plugin_name) else "❌" logger.info(f" ⚙️ 配置: {plugin_info.config_file} {config_status}") # 显示目录统计 logger.info("📂 加载目录统计:") for directory in self.plugin_directories: if os.path.exists(directory): plugins_in_dir = [] for plugin_name in self.loaded_plugins.keys(): plugin_path = self.plugin_paths.get(plugin_name, "") if plugin_path.startswith(directory): plugins_in_dir.append(plugin_name) if plugins_in_dir: logger.info(f" 📁 {directory}: {len(plugins_in_dir)}个插件 ({', '.join(plugins_in_dir)})") else: logger.info(f" 📁 {directory}: 0个插件") # 失败信息 if total_failed_registration > 0: logger.info(f"⚠️ 失败统计: {total_failed_registration}个插件加载失败") for failed_plugin, error in self.failed_plugins.items(): logger.info(f" ❌ {failed_plugin}: {error}") else: logger.warning("😕 没有成功加载任何插件") # 返回插件数量和组件数量 return total_registered, total_components def _find_plugin_directory(self, plugin_class) -> Optional[str]: """查找插件类对应的目录路径""" try: import inspect module = inspect.getmodule(plugin_class) if module and hasattr(module, "__file__") and module.__file__: return os.path.dirname(module.__file__) except Exception as e: logger.debug(f"通过inspect获取插件目录失败: {e}") return None def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]: """从指定目录加载插件模块""" loaded_count = 0 failed_count = 0 if not os.path.exists(directory): logger.warning(f"插件目录不存在: {directory}") return loaded_count, failed_count logger.debug(f"正在扫描插件目录: {directory}") # 遍历目录中的所有Python文件和包 for item in os.listdir(directory): item_path = os.path.join(directory, item) if os.path.isfile(item_path) and item.endswith(".py") and item != "__init__.py": # 单文件插件 plugin_name = Path(item_path).stem if self._load_plugin_module_file(item_path, plugin_name, directory): loaded_count += 1 else: failed_count += 1 elif os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"): # 插件包 plugin_file = os.path.join(item_path, "plugin.py") if os.path.exists(plugin_file): plugin_name = item # 使用目录名作为插件名 if self._load_plugin_module_file(plugin_file, plugin_name, item_path): loaded_count += 1 else: failed_count += 1 return loaded_count, failed_count def _load_plugin_module_file(self, plugin_file: str, plugin_name: str, plugin_dir: str) -> bool: """加载单个插件模块文件 Args: plugin_file: 插件文件路径 plugin_name: 插件名称 plugin_dir: 插件目录路径 """ # 生成模块名 plugin_path = Path(plugin_file) if plugin_path.parent.name != "plugins": # 插件包格式:parent_dir.plugin module_name = f"plugins.{plugin_path.parent.name}.plugin" else: # 单文件格式:plugins.filename module_name = f"plugins.{plugin_path.stem}" try: # 动态导入插件模块 spec = importlib.util.spec_from_file_location(module_name, plugin_file) if spec is None or spec.loader is None: logger.error(f"无法创建模块规范: {plugin_file}") return False module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # 记录插件名和目录路径的映射 self.plugin_paths[plugin_name] = plugin_dir logger.debug(f"插件模块加载成功: {plugin_file}") return True except Exception as e: error_msg = f"加载插件模块 {plugin_file} 失败: {e}" logger.error(error_msg) self.failed_plugins[plugin_name] = error_msg return False def get_loaded_plugins(self) -> List[PluginInfo]: """获取所有已加载的插件信息""" return list(component_registry.get_all_plugins().values()) def get_enabled_plugins(self) -> List[PluginInfo]: """获取所有启用的插件信息""" return list(component_registry.get_enabled_plugins().values()) def enable_plugin(self, plugin_name: str) -> bool: """启用插件""" plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info: plugin_info.enabled = True # 启用插件的所有组件 for component in plugin_info.components: component_registry.enable_component(component.name) logger.debug(f"已启用插件: {plugin_name}") return True return False def disable_plugin(self, plugin_name: str) -> bool: """禁用插件""" plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info: plugin_info.enabled = False # 禁用插件的所有组件 for component in plugin_info.components: component_registry.disable_component(component.name) logger.debug(f"已禁用插件: {plugin_name}") return True return False def get_plugin_instance(self, plugin_name: str) -> Optional["BasePlugin"]: """获取插件实例 Args: plugin_name: 插件名称 Returns: Optional[BasePlugin]: 插件实例或None """ return self.loaded_plugins.get(plugin_name) def get_plugin_stats(self) -> Dict[str, Any]: """获取插件统计信息""" all_plugins = component_registry.get_all_plugins() enabled_plugins = component_registry.get_enabled_plugins() action_components = component_registry.get_components_by_type(ComponentType.ACTION) command_components = component_registry.get_components_by_type(ComponentType.COMMAND) return { "total_plugins": len(all_plugins), "enabled_plugins": len(enabled_plugins), "failed_plugins": len(self.failed_plugins), "total_components": len(action_components) + len(command_components), "action_components": len(action_components), "command_components": len(command_components), "loaded_plugin_files": len(self.loaded_plugins), "failed_plugin_details": self.failed_plugins.copy(), } def reload_plugin(self, plugin_name: str) -> bool: """重新加载插件(高级功能,需要谨慎使用)""" # TODO: 实现插件热重载功能 logger.warning("插件热重载功能尚未实现") return False def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, any]: """检查所有插件的Python依赖包 Args: auto_install: 是否自动安装缺失的依赖包 Returns: Dict[str, any]: 检查结果摘要 """ logger.info("开始检查所有插件的Python依赖包...") all_required_missing = [] all_optional_missing = [] plugin_status = {} for plugin_name, _plugin_instance in self.loaded_plugins.items(): plugin_info = component_registry.get_plugin_info(plugin_name) if not plugin_info or not plugin_info.python_dependencies: plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []} continue logger.info(f"检查插件 {plugin_name} 的依赖...") missing_required, missing_optional = dependency_manager.check_dependencies(plugin_info.python_dependencies) if missing_required: all_required_missing.extend(missing_required) plugin_status[plugin_name] = { "status": "missing_required", "missing": [dep.package_name for dep in missing_required], "optional_missing": [dep.package_name for dep in missing_optional], } logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}") elif missing_optional: all_optional_missing.extend(missing_optional) plugin_status[plugin_name] = { "status": "missing_optional", "missing": [], "optional_missing": [dep.package_name for dep in missing_optional], } logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}") else: plugin_status[plugin_name] = {"status": "ok", "missing": []} logger.info(f"插件 {plugin_name} 依赖检查通过") # 汇总结果 total_missing = len(set(dep.package_name for dep in all_required_missing)) total_optional_missing = len(set(dep.package_name for dep in all_optional_missing)) logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个") # 如果需要自动安装 install_success = True if auto_install and all_required_missing: # 去重 unique_required = {} for dep in all_required_missing: unique_required[dep.package_name] = dep logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...") install_success = dependency_manager.install_dependencies(list(unique_required.values()), auto_install=True) return { "total_plugins_checked": len(plugin_status), "plugins_with_missing_required": len( [p for p in plugin_status.values() if p["status"] == "missing_required"] ), "plugins_with_missing_optional": len( [p for p in plugin_status.values() if p["status"] == "missing_optional"] ), "total_missing_required": total_missing, "total_missing_optional": total_optional_missing, "plugin_status": plugin_status, "auto_install_attempted": auto_install and bool(all_required_missing), "auto_install_success": install_success, "install_summary": dependency_manager.get_install_summary(), } def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool: """生成所有插件依赖的requirements文件 Args: output_path: 输出文件路径 Returns: bool: 生成是否成功 """ logger.info("开始生成插件依赖requirements文件...") all_dependencies = [] for plugin_name, _plugin_instance in self.loaded_plugins.items(): plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info and plugin_info.python_dependencies: all_dependencies.append(plugin_info.python_dependencies) if not all_dependencies: logger.info("没有找到任何插件依赖") return False return dependency_manager.generate_requirements_file(all_dependencies, output_path) def check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]: """检查插件版本兼容性 Args: plugin_name: 插件名称 manifest_data: manifest数据 Returns: Tuple[bool, str]: (是否兼容, 错误信息) """ if "host_application" not in manifest_data: # 没有版本要求,默认兼容 return True, "" host_app = manifest_data["host_application"] if not isinstance(host_app, dict): return True, "" min_version = host_app.get("min_version", "") max_version = host_app.get("max_version", "") if not min_version and not max_version: return True, "" try: from src.plugin_system.utils.manifest_utils import VersionComparator current_version = VersionComparator.get_current_host_version() is_compatible, error_msg = VersionComparator.is_version_in_range(current_version, min_version, max_version) if not is_compatible: return False, f"版本不兼容: {error_msg}" else: logger.debug(f"插件 {plugin_name} 版本兼容性检查通过") return True, "" except Exception as e: logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}") return True, "" # 检查失败时默认允许加载 # 全局插件管理器实例 plugin_manager = PluginManager() # 注释掉以解决插件目录重复加载的情况 # 默认插件目录 # plugin_manager.add_plugin_directory("src/plugins/built_in") # plugin_manager.add_plugin_directory("src/plugins/examples") # 用户插件目录 # plugin_manager.add_plugin_directory("plugins")