Files
NebulaShell/oss/core/datastore.py
Falck bce27db4ac 重大重构:引擎模块拆分 + P0插件实现 + 55个Bug修复
核心变更:
- 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个测试, 全部通过
2026-05-12 11:40:06 +08:00

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