92 lines
3.3 KiB
Rust
92 lines
3.3 KiB
Rust
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<DownloadTask>,
|
||
/// 下载设置
|
||
#[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<EngineState> {
|
||
let raw = fs::read_to_string(path).ok()?;
|
||
serde_json::from_str::<EngineState>(&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(())
|
||
}
|
||
}
|