| """ |
| 插件状态持久化模块 |
| 管理插件的启用/禁用状态,与 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) |
|
|
| |
| 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 |
|
|