""" 插件状态持久化模块 管理插件的启用/禁用状态,与 plugin.json 的 enabled 字段独立。 """ import json import logging from pathlib import Path from datetime import datetime from typing import Optional from app.plugins.models import PluginStateStore, PluginStateEntry logger = logging.getLogger(__name__) class PluginStateStoreManager: """插件状态文件管理器 事实源:data/plugin_state.json 以插件名为 key 保存用户启停状态。 """ def __init__(self, state_file_path: Path): """初始化状态文件管理器 Args: state_file_path: 状态文件路径 """ self.state_file_path = state_file_path self._store: Optional[PluginStateStore] = None def _load_store(self) -> PluginStateStore: """加载状态文件 如果文件不存在,返回空的 store。 """ if self._store is not None: return self._store if not self.state_file_path.exists(): logger.info(f"状态文件不存在,创建新的: {self.state_file_path}") self._store = PluginStateStore() return self._store try: with open(self.state_file_path, "r", encoding="utf-8") as f: data = json.load(f) self._store = PluginStateStore(**data) logger.info(f"加载状态文件: {len(self._store.plugins)} 个插件") return self._store except Exception as e: logger.error(f"加载状态文件失败: {e}") self._store = PluginStateStore() return self._store def _save_store(self) -> bool: """保存状态文件""" if self._store is None: return False try: # 确保目录存在 self.state_file_path.parent.mkdir(parents=True, exist_ok=True) # 序列化为 JSON data = self._store.model_dump(mode="json") with open(self.state_file_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2, default=str) logger.debug(f"保存状态文件: {self.state_file_path}") return True except Exception as e: logger.error(f"保存状态文件失败: {e}") return False def is_enabled(self, plugin_name: str) -> Optional[bool]: """检查插件是否启用 Args: plugin_name: 插件名 Returns: True/False 表示状态,None 表示没有记录 """ store = self._load_store() entry = store.plugins.get(plugin_name) if entry is None: return None return entry.enabled def set_enabled( self, plugin_name: str, enabled: bool, updated_by: str = "user" ) -> bool: """设置插件启用状态 Args: plugin_name: 插件名 enabled: 是否启用 updated_by: 更新者 Returns: 是否成功 """ store = self._load_store() store.plugins[plugin_name] = PluginStateEntry( enabled=enabled, updated_at=datetime.now(), updated_by=updated_by, ) return self._save_store() def remove_plugin(self, plugin_name: str) -> bool: """移除插件状态记录 Args: plugin_name: 插件名 Returns: 是否成功 """ store = self._load_store() if plugin_name in store.plugins: del store.plugins[plugin_name] return self._save_store() return True def get_all_states(self) -> dict: """获取所有插件状态 Returns: {plugin_name: enabled} 字典 """ store = self._load_store() return {name: entry.enabled for name, entry in store.plugins.items()} def initialize_from_metadata( self, plugin_name: str, default_enabled: bool = False ) -> bool: """从 plugin.json 初始化状态(首次扫描时) 如果状态文件中已有记录,不覆盖。 Args: plugin_name: 插件名 default_enabled: 默认启用状态(来自 plugin.json) Returns: 是否新增了记录 """ store = self._load_store() if plugin_name in store.plugins: # 已有记录,不覆盖 return False # 新增记录 store.plugins[plugin_name] = PluginStateEntry( enabled=default_enabled, updated_at=datetime.now(), updated_by="system", ) self._save_store() return True # 全局实例(延迟初始化) _state_store_manager: Optional[PluginStateStoreManager] = None def get_state_store_manager(state_file_path: Optional[Path] = None) -> PluginStateStoreManager: """获取状态文件管理器实例 Args: state_file_path: 状态文件路径(首次调用时必须提供) Returns: PluginStateStoreManager 实例 """ global _state_store_manager if _state_store_manager is None: if state_file_path is None: raise ValueError("首次调用必须提供 state_file_path") _state_store_manager = PluginStateStoreManager(state_file_path) return _state_store_manager