import os import asyncio import logging import time import aiofiles from datetime import datetime from pathlib import Path from typing import Dict, List, Optional logger = logging.getLogger(__name__) class FileCachePlugin: """文件缓存插件""" def __init__(self): self.name = "cache" self.enabled = False self.cache_dir = None self.temp_dir = None self.permanent_dir = None self.file_metadata = {} # 存储文件元数据:{file_id: {"name": str, "type": str, "created": timestamp, "is_temp": bool}} def on_enable(self): """插件启用时调用""" self.enabled = True self._init_directories() self._load_metadata() logger.info(f"{self.name} 已启用") def on_disable(self): """插件禁用时调用""" self.enabled = False try: from . import api api.stop_cleanup_task() except Exception as e: logger.error(f"停止清理任务失败: {e}") logger.info(f"{self.name} 已禁用") def _init_directories(self): """初始化缓存目录""" plugin_dir = Path(__file__).parent / "cache" plugin_dir.mkdir(exist_ok=True) self.cache_dir = plugin_dir self.temp_dir = plugin_dir / "temp" self.permanent_dir = plugin_dir / "permanent" self.temp_dir.mkdir(exist_ok=True) self.permanent_dir.mkdir(exist_ok=True) def _load_metadata(self): """从缓存目录加载文件元数据""" self.file_metadata = {} # 加载临时文件 if self.temp_dir.exists(): for file_path in self.temp_dir.iterdir(): if file_path.is_file(): file_id = file_path.stem stat = file_path.stat() self.file_metadata[file_id] = { "name": file_path.name, "type": "temp", "created": stat.st_mtime, "path": str(file_path), } # 加载永久文件 if self.permanent_dir.exists(): for file_path in self.permanent_dir.iterdir(): if file_path.is_file(): file_id = file_path.stem stat = file_path.stat() self.file_metadata[file_id] = { "name": file_path.name, "type": "permanent", "created": stat.st_mtime, "path": str(file_path), } async def cleanup_expired_files(self): """清理超过8小时的临时文件""" current_time = time.time() expired_files = [] for file_id, metadata in list(self.file_metadata.items()): if metadata["type"] == "temp": file_age = current_time - metadata["created"] # 8小时 = 28800秒 if file_age > 28800: expired_files.append(file_id) for file_id in expired_files: await self.delete_file(file_id) async def save_file( self, file_id: str, file_content: bytes, original_filename: str, is_temp: bool = True, ) -> bool: """保存文件到缓存目录""" try: target_dir = self.temp_dir if is_temp else self.permanent_dir file_path = target_dir / original_filename # 如果文件已存在,添加时间戳以避免重名 if file_path.exists(): name, suffix = ( original_filename.rsplit(".", 1) if "." in original_filename else (original_filename, "") ) timestamp = int(time.time() * 1000) original_filename = ( f"{name}_{timestamp}.{suffix}" if suffix else f"{name}_{timestamp}" ) file_path = target_dir / original_filename # 保存文件 async with aiofiles.open(file_path, "wb") as f: await f.write(file_content) # 记录元数据 self.file_metadata[file_id] = { "name": original_filename, "type": "temp" if is_temp else "permanent", "created": time.time(), "path": str(file_path), } return True except Exception as e: logger.error(f"保存文件失败: {e}") return False async def delete_file(self, file_id: str) -> bool: """删除文件""" try: if file_id not in self.file_metadata: return False file_path = Path(self.file_metadata[file_id]["path"]) if file_path.exists(): file_path.unlink() del self.file_metadata[file_id] return True except Exception as e: logger.error(f"删除文件失败: {e}") return False def get_file_list(self) -> List[Dict]: """获取文件列表""" file_list = [] for file_id, metadata in self.file_metadata.items(): file_list.append( { "file_id": file_id, "name": metadata["name"], "type": metadata["type"], "created": datetime.fromtimestamp(metadata["created"]).isoformat(), "url": f"/plugins/cache/api/file/{file_id}", } ) return file_list def get_file_path(self, file_id: str) -> Optional[str]: """获取文件路径""" if file_id in self.file_metadata: return self.file_metadata[file_id]["path"] return None def get_status(self) -> Dict: """获取插件状态""" return { "name": self.name, "enabled": self.enabled, "file_count": len(self.file_metadata), "cache_dir": str(self.cache_dir), } # 创建全局实例 plugin = FileCachePlugin()