message / app /plugins /state_store.py
hunian
feat: 重构插件平台后端运行时和状态契约
10b8d56
Raw
History Blame Contribute Delete
5.44 kB
"""
插件状态持久化模块
管理插件的启用/禁用状态,与 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