核心变更: - engine.py(1781行)拆分为8个独立模块: lifecycle/security/deps/ datastore/pl_injector/watcher/signature/manager - 新增plugin-bridge: 事件总线 + 服务注册 + RPC通信 - 新增i18n: 国际化/多语言翻译支持 - 新增plugin-storage: 插件键值/文件存储 - 新增ws-api: WebSocket实时通信(pub/sub + 自定义处理器) - nodejs-adapter统一为Plugin ABC模式 Bug修复: - 修复load_all()中store_dir未定义崩溃 - 修复DependencyResolver入度计算(拓扑排序) - 修复PermissionError隐藏内置异常 - 修复CORS中间件头部未附加到响应 - 修复IntegrityChecker跳过__pycache__目录 - 修复版本号不一致(v2.0.0→v1.2.0) - 修复测试文件的Logger导入/路径/私有方法调用 - 修复context.py缺少typing导入 - 修复config.py STORE_DIR默认路径(./mods→./store) 测试覆盖: 14→91个测试, 全部通过
93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
import json
|
|
import os
|
|
import threading
|
|
from pathlib import Path
|
|
from typing import Any, Optional
|
|
|
|
from oss.config import get_config
|
|
from oss.logger.logger import Log
|
|
|
|
|
|
class DataStore:
|
|
"""数据存储抽象接口
|
|
|
|
默认实现使用 JSON 文件存储到 ~/.nebula/data/
|
|
后续可由 data-store 插件替换为更完善的实现
|
|
"""
|
|
|
|
def __init__(self):
|
|
config = get_config()
|
|
data_dir_env = os.environ.get("NEBULA_DATA_DIR", "")
|
|
default_dir = Path(data_dir_env) if data_dir_env else Path.home() / ".nebula" / "data"
|
|
self._base_dir = Path(config.get("DATA_DIR", str(default_dir)))
|
|
self._base_dir.mkdir(parents=True, exist_ok=True)
|
|
self._lock = threading.Lock()
|
|
|
|
def _plugin_dir(self, plugin_name: str) -> Path:
|
|
"""获取插件专属数据目录"""
|
|
pd = self._base_dir / plugin_name
|
|
pd.mkdir(parents=True, exist_ok=True)
|
|
return pd
|
|
|
|
def save(self, plugin_name: str, key: str, data: Any) -> bool:
|
|
"""保存数据"""
|
|
with self._lock:
|
|
try:
|
|
file_path = self._plugin_dir(plugin_name) / f"{key}.json"
|
|
file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
return True
|
|
except Exception as e:
|
|
Log.error("Core", f"数据存储保存失败 [{plugin_name}/{key}]: {e}")
|
|
return False
|
|
|
|
def load(self, plugin_name: str, key: str, default: Any = None) -> Any:
|
|
"""加载数据"""
|
|
with self._lock:
|
|
try:
|
|
file_path = self._plugin_dir(plugin_name) / f"{key}.json"
|
|
if file_path.exists():
|
|
return json.loads(file_path.read_text(encoding="utf-8"))
|
|
return default
|
|
except Exception as e:
|
|
Log.error("Core", f"数据存储加载失败 [{plugin_name}/{key}]: {e}")
|
|
return default
|
|
|
|
def delete(self, plugin_name: str, key: str) -> bool:
|
|
"""删除数据"""
|
|
with self._lock:
|
|
try:
|
|
file_path = self._plugin_dir(plugin_name) / f"{key}.json"
|
|
if file_path.exists():
|
|
file_path.unlink()
|
|
return True
|
|
except Exception as e:
|
|
Log.error("Core", f"数据存储删除失败 [{plugin_name}/{key}]: {e}")
|
|
return False
|
|
|
|
def list_keys(self, plugin_name: str) -> list[str]:
|
|
"""列出插件所有数据键"""
|
|
pd = self._plugin_dir(plugin_name)
|
|
if not pd.exists():
|
|
return []
|
|
return [f.stem for f in pd.glob("*.json")]
|
|
|
|
def set_custom_path(self, plugin_name: str, custom_path: str) -> bool:
|
|
"""插件自定义存储路径(不能修改到项目目录内)"""
|
|
path = Path(custom_path).expanduser().resolve()
|
|
project_dir = Path.cwd().resolve()
|
|
if str(path).startswith(str(project_dir)):
|
|
Log.error("Core", f"插件 '{plugin_name}' 试图将数据存储到项目目录: {custom_path}")
|
|
return False
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
# 创建符号链接或记录映射
|
|
mapping_file = self._base_dir / "_custom_paths.json"
|
|
mappings = {}
|
|
if mapping_file.exists():
|
|
try:
|
|
mappings = json.loads(mapping_file.read_text())
|
|
except (json.JSONDecodeError, OSError):
|
|
pass
|
|
mappings[plugin_name] = str(path)
|
|
mapping_file.write_text(json.dumps(mappings, indent=2))
|
|
return True
|