use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; use std::sync::atomic::{AtomicU64, Ordering}; use super::task::{DownloadTask, DownloaderSettings}; /// 引擎持久化状态(序列化到 engine_state.json) #[derive(Serialize, Deserialize)] pub struct EngineState { /// 所有任务(active + queued + paused + complete + error) #[serde(default)] pub tasks: Vec, /// 下载设置 #[serde(default)] pub settings: DownloaderSettings, /// 自增 ID 计数器(不序列化为 hex,存原始数字) #[serde(default)] pub next_id: u64, } impl Default for EngineState { fn default() -> Self { Self { tasks: Vec::new(), settings: DownloaderSettings::default(), next_id: 1, } } } /// 存储管理器:负责加载/保存引擎状态到 JSON 文件 pub struct Storage { state_path: PathBuf, /// ID 计数器(内存中维护,与 EngineState.next_id 同步) pub id_counter: AtomicU64, } impl Storage { pub fn new(data_dir: PathBuf) -> Self { // 确保数据目录存在(首次启动或目录被删除时自动创建) if let Err(e) = fs::create_dir_all(&data_dir) { crate::logger::log_error("download", &format!("创建数据目录失败: {} ({})", data_dir.display(), e)); } let state_path = data_dir.join("engine_state.json"); let existing = Self::load_raw(&state_path); let next_id = existing.as_ref().map(|s| s.next_id).unwrap_or(1); Self { state_path, id_counter: AtomicU64::new(next_id), } } /// 生成下一个任务 ID(16 位 hex 字符串) pub fn next_task_id(&self) -> String { let id = self.id_counter.fetch_add(1, Ordering::SeqCst); format!("{:016x}", id) } /// 从磁盘加载状态 fn load_raw(path: &PathBuf) -> Option { let raw = fs::read_to_string(path).ok()?; serde_json::from_str::(&raw).ok() } /// 加载完整状态(若文件不存在返回默认值) pub fn load(&self) -> EngineState { Self::load_raw(&self.state_path).unwrap_or_default() } /// 保存状态到磁盘(原子写:先写 .tmp 再 rename 覆盖, /// 避免进程崩溃时产生半写/截断的状态文件导致任务列表丢失) /// 失败返回 Err,由调用方决定是否将任务置为 Error(防止"看似已保存"的假象)。 pub fn save(&self, mut state: EngineState) -> Result<(), String> { // 同步 ID 计数器 state.next_id = self.id_counter.load(Ordering::SeqCst); let json = serde_json::to_string_pretty(&state) .map_err(|e| format!("序列化状态失败: {}", e))?; // 兜底:若父目录被外部删除则在写入前重建 if let Some(parent) = self.state_path.parent() { let _ = fs::create_dir_all(parent); } let tmp_path = PathBuf::from(format!("{}.tmp", self.state_path.display())); fs::write(&tmp_path, json).map_err(|e| format!("保存状态失败: {}", e))?; fs::rename(&tmp_path, &self.state_path).map_err(|e| { let _ = fs::remove_file(&tmp_path); format!("替换状态文件失败: {}", e) })?; Ok(()) } }