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) with self._lock: 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