#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 文件存储 提供文件读写的统一接口 """ import logging import json from typing import Any, Optional, Union from pathlib import Path logger = logging.getLogger(__name__) class FileStorage: """ 文件存储 提供: - 文本文件读写 - JSON 文件读写 - 二进制文件读写 - 路径管理 """ def __init__(self, base_path: str = "data"): """ 初始化文件存储 Args: base_path: 基础存储路径(相对于项目根目录) """ self._base_path = Path(base_path) self._project_root: Optional[Path] = None def set_project_root(self, project_root: str): """设置项目根目录""" self._project_root = Path(project_root) def _get_full_path(self, relative_path: str) -> Path: """获取完整路径""" if self._project_root: return self._project_root / self._base_path / relative_path return self._base_path / relative_path def _ensure_dir(self, file_path: Path): """确保目录存在""" file_path.parent.mkdir(parents=True, exist_ok=True) def save_text(self, content: str, filename: str, subdir: str = "") -> bool: """ 保存文本文件 Args: content: 文本内容 filename: 文件名 subdir: 子目录 Returns: 是否成功 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) self._ensure_dir(path) path.write_text(content, encoding='utf-8') return True except Exception as e: logger.error(f"保存文本文件失败: {filename}, {e}") return False def load_text(self, filename: str, subdir: str = "") -> Optional[str]: """ 加载文本文件 Args: filename: 文件名 subdir: 子目录 Returns: 文本内容 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) if path.exists(): return path.read_text(encoding='utf-8') return None except Exception as e: logger.error(f"加载文本文件失败: {filename}, {e}") return None def save_json(self, data: Any, filename: str, subdir: str = "") -> bool: """ 保存 JSON 文件 Args: data: 数据 filename: 文件名 subdir: 子目录 Returns: 是否成功 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) self._ensure_dir(path) with open(path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) return True except Exception as e: logger.error(f"保存 JSON 文件失败: {filename}, {e}") return False def load_json(self, filename: str, subdir: str = "") -> Optional[Any]: """ 加载 JSON 文件 Args: filename: 文件名 subdir: 子目录 Returns: 数据 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) if path.exists(): with open(path, 'r', encoding='utf-8') as f: return json.load(f) return None except Exception as e: logger.error(f"加载 JSON 文件失败: {filename}, {e}") return None def save_binary(self, data: bytes, filename: str, subdir: str = "") -> bool: """ 保存二进制文件 Args: data: 二进制数据 filename: 文件名 subdir: 子目录 Returns: 是否成功 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) self._ensure_dir(path) path.write_bytes(data) return True except Exception as e: logger.error(f"保存二进制文件失败: {filename}, {e}") return False def load_binary(self, filename: str, subdir: str = "") -> Optional[bytes]: """ 加载二进制文件 Args: filename: 文件名 subdir: 子目录 Returns: 二进制数据 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) if path.exists(): return path.read_bytes() return None except Exception as e: logger.error(f"加载二进制文件失败: {filename}, {e}") return None def exists(self, filename: str, subdir: str = "") -> bool: """ 检查文件是否存在 Args: filename: 文件名 subdir: 子目录 Returns: 是否存在 """ path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) return path.exists() def delete(self, filename: str, subdir: str = "") -> bool: """ 删除文件 Args: filename: 文件名 subdir: 子目录 Returns: 是否成功 """ try: path = self._get_full_path(f"{subdir}/{filename}" if subdir else filename) if path.exists(): path.unlink() return True except Exception as e: logger.error(f"删除文件失败: {filename}, {e}") return False def list_files(self, subdir: str = "", pattern: str = "*") -> list[str]: """ 列出文件 Args: subdir: 子目录 pattern: 匹配模式 Returns: 文件名列表 """ try: path = self._get_full_path(subdir) if subdir else self._get_full_path("") if path.exists(): return [f.name for f in path.glob(pattern) if f.is_file()] return [] except Exception as e: logger.error(f"列出文件失败: {subdir}, {e}") return [] def get_path(self, filename: str, subdir: str = "") -> Path: """ 获取文件完整路径 Args: filename: 文件名 subdir: 子目录 Returns: 完整路径 """ return self._get_full_path(f"{subdir}/{filename}" if subdir else filename)