下载非内核

This commit is contained in:
zhongluofeng
2026-07-22 18:26:26 +08:00
parent 1e31ee8da9
commit f7d3c13f35
24 changed files with 2609 additions and 2532 deletions
+623
View File
@@ -0,0 +1,623 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tauri::{AppHandle, Emitter};
use super::http_dl::{HttpDownloader, split_segments};
use super::rate_limit::RateLimiter;
use super::storage::{EngineState, Storage};
use super::task::{DownloadTask, DownloaderSettings, Segment, TaskStatus};
/// 进度事件载荷(发给前端 download-progress 事件)
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgressPayload {
pub id: String,
pub completed_size: u64,
pub total_size: u64,
pub speed: u64,
pub status: TaskStatus,
}
/// 完成事件载荷(发给前端 download-complete 事件)
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletePayload {
pub id: String,
pub filename: String,
pub status: TaskStatus,
pub error: Option<String>,
}
/// 活跃下载句柄
struct TaskHandle {
cancel: Arc<AtomicBool>,
/// 每个分段的已下载字节(与 segments 一一对应)
progress: Vec<Arc<std::sync::atomic::AtomicU64>>,
/// 下载任务的 JoinHandleNone = 已完成/已取消)
join: Mutex<Option<tauri::async_runtime::JoinHandle<()>>>,
}
/// 下载引擎(进程内运行,通过 Arc 共享状态)
#[derive(Clone)]
pub struct DownloadEngine {
inner: Arc<EngineInner>,
}
struct EngineInner {
/// 所有任务状态
tasks: Mutex<HashMap<String, DownloadTask>>,
/// 活跃下载句柄
handles: Mutex<HashMap<String, TaskHandle>>,
/// 持久化存储
storage: Storage,
/// 下载设置
settings: Mutex<DownloaderSettings>,
/// 全局限速器(bytes/s0=不限)
global_limiter: Arc<RateLimiter>,
/// HTTP 下载器
http: HttpDownloader,
/// Tauri 应用句柄(用于发事件)
app_handle: AppHandle,
/// 引擎是否已启动
started: AtomicBool,
/// 持久化节流:上次保存时间
last_save: Mutex<Instant>,
}
impl DownloadEngine {
/// 创建引擎并从磁盘恢复状态
pub fn new(data_dir: PathBuf, app_handle: AppHandle) -> Self {
let storage = Storage::new(data_dir);
let state = storage.load();
// 恢复时,将 Active 任务标记为 Paused(避免自动恢复下载)
let mut tasks: HashMap<String, DownloadTask> = state
.tasks
.into_iter()
.map(|t| (t.id.clone(), t))
.collect();
for task in tasks.values_mut() {
if task.status == TaskStatus::Active {
task.status = TaskStatus::Paused;
task.speed = 0;
}
}
let settings = state.settings;
// 创建全局限速器(KB/s → bytes/s
let global_limit = if settings.global_speed_limit > 0 {
settings.global_speed_limit * 1024
} else {
0
};
let global_limiter = Arc::new(RateLimiter::new(global_limit));
let engine = Self {
inner: Arc::new(EngineInner {
tasks: Mutex::new(tasks),
handles: Mutex::new(HashMap::new()),
storage,
settings: Mutex::new(settings),
global_limiter,
http: HttpDownloader::new(),
app_handle,
started: AtomicBool::new(false),
last_save: Mutex::new(Instant::now()),
}),
};
// 启动后台持久化定时器
engine.start_save_timer();
engine.inner.started.store(true, Ordering::SeqCst);
engine
}
/// 启动后台定时持久化(每 5 秒检查一次)
fn start_save_timer(&self) {
let engine = self.clone();
tauri::async_runtime::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
engine.persist_throttled();
}
});
}
// ===================== 公开 API =====================
/// 添加下载任务
pub async fn add_task(
&self,
url: String,
filename: Option<String>,
dir: Option<String>,
headers: HashMap<String, String>,
) -> Result<String, String> {
// 探测资源信息
let probe = self.inner.http.probe(&url, &headers).await;
let settings = self.inner.settings.lock().unwrap().clone();
let task_dir = dir.unwrap_or_else(|| settings.download_dir.clone());
// 确定文件名
let task_filename = filename
.or_else(|| probe.as_ref().ok().and_then(|p| p.filename.clone()))
.unwrap_or_else(|| {
url.split('?')
.next()
.and_then(|u| u.rsplit('/').next())
.filter(|n| !n.is_empty())
.map(|n| n.to_string())
.unwrap_or_else(|| format!("download_{}", chrono::Utc::now().timestamp()))
});
let id = self.inner.storage.next_task_id();
// 创建分段
let segments = match &probe {
Ok(p) if p.supports_resume && p.total_size.map(|s| s > 0).unwrap_or(false) => {
split_segments(p.total_size.unwrap(), settings.max_connections)
}
_ => vec![Segment {
index: 0,
start: 0,
end: 0,
completed: 0,
}],
};
let total_size = probe.as_ref().ok().and_then(|p| p.total_size).unwrap_or(0);
let supports_resume = probe.as_ref().ok().map(|p| p.supports_resume).unwrap_or(false);
let task = DownloadTask {
id: id.clone(),
url: url.clone(),
filename: task_filename,
dir: task_dir,
status: TaskStatus::Queued,
total_size,
completed_size: 0,
speed: 0,
supports_resume,
segments: segments.clone(),
error: None,
created_at: chrono::Utc::now().timestamp_millis(),
headers,
};
// 如果探测失败,标记为 Error
let task = match probe {
Ok(_) => task,
Err(e) => DownloadTask {
status: TaskStatus::Error,
error: Some(e),
..task
},
};
{
let mut tasks = self.inner.tasks.lock().unwrap();
tasks.insert(id.clone(), task);
}
self.persist_now();
// 如果探测成功,尝试调度
let should_schedule = {
let tasks = self.inner.tasks.lock().unwrap();
tasks.get(&id).map(|t| t.status == TaskStatus::Queued).unwrap_or(false)
};
if should_schedule {
self.schedule();
}
// 通知前端有新任务加入(扩展通过 HTTP API 添加时,前端需要刷新)
let _ = self.inner.app_handle.emit("download-added", serde_json::json!({ "id": id }));
Ok(id)
}
/// 暂停任务
pub fn pause_task(&self, id: &str) -> Result<(), String> {
// 1. 设置取消标志 + 读取进度(锁 handles)
let progress_values: Vec<u64> = {
let handles = self.inner.handles.lock().unwrap();
if let Some(handle) = handles.get(id) {
handle.cancel.store(true, Ordering::SeqCst);
handle.progress.iter().map(|p| p.load(Ordering::Relaxed)).collect()
} else {
Vec::new()
}
};
// 2. 更新任务状态 + 同步进度(锁 tasks,不嵌套锁 handles
{
let mut tasks = self.inner.tasks.lock().unwrap();
if let Some(task) = tasks.get_mut(id) {
if task.status == TaskStatus::Active || task.status == TaskStatus::Queued {
task.status = TaskStatus::Paused;
task.speed = 0;
// 同步分段进度
for (i, val) in progress_values.iter().enumerate() {
if let Some(seg) = task.segments.get_mut(i) {
seg.completed = *val;
}
}
task.recalc_completed();
}
} else {
return Err("任务不存在".to_string());
}
}
self.persist_now();
self.schedule();
Ok(())
}
/// 恢复任务
pub fn resume_task(&self, id: &str) -> Result<(), String> {
{
let mut tasks = self.inner.tasks.lock().unwrap();
if let Some(task) = tasks.get_mut(id) {
if task.status != TaskStatus::Paused && task.status != TaskStatus::Error {
return Err("任务不在可恢复状态".to_string());
}
// 如果不支持断点续传,从头开始
if !task.supports_resume {
task.completed_size = 0;
for seg in &mut task.segments {
seg.completed = 0;
}
}
task.status = TaskStatus::Queued;
task.error = None;
} else {
return Err("任务不存在".to_string());
}
}
self.persist_now();
self.schedule();
Ok(())
}
/// 移除任务
pub fn remove_task(&self, id: &str, delete_files: bool) -> Result<(), String> {
// 1. 设置取消标志 + abort join handle(锁 handles
{
let mut handles = self.inner.handles.lock().unwrap();
if let Some(handle) = handles.remove(id) {
handle.cancel.store(true, Ordering::SeqCst);
if let Ok(mut join) = handle.join.lock() {
if let Some(j) = join.take() {
j.abort();
}
}
}
}
// 2. 从任务列表移除(锁 tasks)
let task = {
let mut tasks = self.inner.tasks.lock().unwrap();
tasks.remove(id)
};
// 3. 可选删除文件(临时文件 + 最终文件)
if delete_files {
if let Some(task) = &task {
let _ = std::fs::remove_file(task.file_path());
let _ = std::fs::remove_file(task.temp_file_path());
}
}
self.persist_now();
self.schedule();
Ok(())
}
/// 获取所有任务
pub fn get_tasks(&self) -> Vec<DownloadTask> {
self.inner.tasks.lock().unwrap().values().cloned().collect()
}
/// 获取设置
pub fn get_settings(&self) -> DownloaderSettings {
self.inner.settings.lock().unwrap().clone()
}
/// 保存设置
pub fn save_settings(&self, settings: DownloaderSettings) {
// 更新全局限速器
let new_limit = if settings.global_speed_limit > 0 {
settings.global_speed_limit * 1024
} else {
0
};
self.inner.global_limiter.set_limit(new_limit);
{
let mut s = self.inner.settings.lock().unwrap();
*s = settings;
}
self.persist_now();
}
/// 引擎是否已启动
pub fn is_started(&self) -> bool {
self.inner.started.load(Ordering::SeqCst)
}
/// 退出时清理:停止所有下载、保存状态
pub fn cleanup_on_exit(&self) {
// 取消所有活跃下载
{
let handles = self.inner.handles.lock().unwrap();
for handle in handles.values() {
handle.cancel.store(true, Ordering::SeqCst);
}
}
// 将 Active 任务标记为 Paused
{
let mut tasks = self.inner.tasks.lock().unwrap();
for task in tasks.values_mut() {
if task.status == TaskStatus::Active {
task.status = TaskStatus::Paused;
task.speed = 0;
}
}
}
// 等待短暂时间让下载任务退出
std::thread::sleep(Duration::from_millis(200));
// 最终保存
self.persist_now();
}
// ===================== 内部调度逻辑 =====================
/// 调度:如果活跃任务数 < max_concurrent,启动排队任务
fn schedule(&self) {
let max_concurrent = self.inner.settings.lock().unwrap().max_concurrent as usize;
let (active_count, queued_ids) = {
let tasks = self.inner.tasks.lock().unwrap();
let active = tasks.values().filter(|t| t.status == TaskStatus::Active).count();
let mut queued: Vec<_> = tasks
.values()
.filter(|t| t.status == TaskStatus::Queued)
.collect();
queued.sort_by_key(|t| t.created_at);
(active, queued.into_iter().map(|t| t.id.clone()).collect::<Vec<_>>())
};
if active_count >= max_concurrent {
return;
}
let slots = max_concurrent.saturating_sub(active_count);
for id in queued_ids.into_iter().take(slots) {
self.start_download(id);
}
}
/// 启动单个下载任务
fn start_download(&self, id: String) {
let task = {
let mut tasks = self.inner.tasks.lock().unwrap();
match tasks.get_mut(&id) {
Some(task) if task.status == TaskStatus::Queued => {
task.status = TaskStatus::Active;
task.speed = 0;
task.clone()
}
_ => return,
}
};
// 创建取消标志和进度计数器
let cancel = Arc::new(AtomicBool::new(false));
let progress: Vec<Arc<std::sync::atomic::AtomicU64>> = task
.segments
.iter()
.map(|s| Arc::new(std::sync::atomic::AtomicU64::new(s.completed)))
.collect();
// 存储句柄
let handle = TaskHandle {
cancel: cancel.clone(),
progress: progress.iter().map(|p| p.clone()).collect(),
join: Mutex::new(None),
};
self.inner.handles.lock().unwrap().insert(id.clone(), handle);
// 生成下载 future
let engine = self.clone();
let id_clone = id.clone();
let cancel_clone = cancel.clone();
let progress_clone: Vec<Arc<std::sync::atomic::AtomicU64>> =
progress.iter().map(|p| p.clone()).collect();
let limiter = self.inner.global_limiter.clone();
let url = task.url.clone();
let headers = task.headers.clone();
let segments = task.segments.clone();
let temp_file_path = task.temp_file_path();
let final_file_path = task.file_path();
let http = self.inner.http.clone();
let join = tauri::async_runtime::spawn(async move {
let result = http
.download(&url, &headers, &segments, &temp_file_path, cancel_clone, &progress_clone, limiter)
.await;
// 下载结束,更新任务状态
let final_status = match &result {
Ok(()) => TaskStatus::Complete,
Err(e) if e == "已取消" => TaskStatus::Paused,
Err(_) => TaskStatus::Error,
};
// 下载成功后,将临时文件重命名为最终文件名
if final_status == TaskStatus::Complete {
let _ = tokio::fs::rename(&temp_file_path, &final_file_path).await;
}
// 同步最终进度到任务
{
let mut tasks = engine.inner.tasks.lock().unwrap();
if let Some(task) = tasks.get_mut(&id_clone) {
for (i, prog) in progress_clone.iter().enumerate() {
if let Some(seg) = task.segments.get_mut(i) {
seg.completed = prog.load(Ordering::Relaxed);
}
}
task.recalc_completed();
task.speed = 0;
task.status = final_status.clone();
if let Err(e) = &result {
if e != "已取消" {
task.error = Some(e.clone());
}
}
if final_status == TaskStatus::Complete {
task.completed_size = task.total_size.max(task.completed_size);
}
}
}
// 从活跃句柄中移除
engine.inner.handles.lock().unwrap().remove(&id_clone);
// 发送完成事件
let task = engine.inner.tasks.lock().unwrap().get(&id_clone).cloned();
if let Some(task) = task {
let _ = engine.inner.app_handle.emit(
"download-complete",
CompletePayload {
id: id_clone.clone(),
filename: task.filename.clone(),
status: task.status.clone(),
error: task.error.clone(),
},
);
}
// 持久化 + 调度下一个
engine.persist_now();
engine.schedule();
});
// 存储 JoinHandle
if let Some(h) = self.inner.handles.lock().unwrap().get_mut(&id) {
if let Ok(mut join_guard) = h.join.lock() {
*join_guard = Some(join);
}
}
// 启动进度监控(每 500ms 更新一次)
let engine = self.clone();
let id_monitor = id.clone();
let progress_monitor = progress;
let cancel_monitor = cancel;
tauri::async_runtime::spawn(async move {
// 初始化为当前已下载量,避免恢复下载时首次计算速度异常
let mut last_completed: u64 = progress_monitor
.iter()
.map(|p| p.load(Ordering::Relaxed))
.sum();
let mut last_time = Instant::now();
let mut interval = tokio::time::interval(Duration::from_millis(500));
interval.tick().await; // 跳过第一次立即触发
loop {
interval.tick().await;
// 如果任务已不在活跃状态,停止监控
let is_active = {
let tasks = engine.inner.tasks.lock().unwrap();
tasks.get(&id_monitor).map(|t| t.status == TaskStatus::Active).unwrap_or(false)
};
if !is_active || cancel_monitor.load(Ordering::SeqCst) {
break;
}
// 读取进度
let completed: u64 = progress_monitor
.iter()
.map(|p| p.load(Ordering::Relaxed))
.sum();
// 计算速度
let now = Instant::now();
let elapsed = now.duration_since(last_time).as_secs_f64();
let speed = if elapsed > 0.0 && completed >= last_completed {
((completed - last_completed) as f64 / elapsed) as u64
} else {
0
};
last_completed = completed;
last_time = now;
// 更新任务状态 + 发送进度事件
let total_size = {
let mut tasks = engine.inner.tasks.lock().unwrap();
if let Some(task) = tasks.get_mut(&id_monitor) {
task.completed_size = completed;
task.speed = speed;
for (i, prog) in progress_monitor.iter().enumerate() {
if let Some(seg) = task.segments.get_mut(i) {
seg.completed = prog.load(Ordering::Relaxed);
}
}
task.total_size
} else {
break;
}
};
let _ = engine.inner.app_handle.emit(
"download-progress",
ProgressPayload {
id: id_monitor.clone(),
completed_size: completed,
total_size,
speed,
status: TaskStatus::Active,
},
);
}
});
}
// ===================== 持久化 =====================
/// 节流持久化(至少间隔 3 秒)
fn persist_throttled(&self) {
let should_save = {
let last = self.inner.last_save.lock().unwrap();
last.elapsed() >= Duration::from_secs(3)
};
if should_save {
self.persist_now();
}
}
/// 立即持久化
fn persist_now(&self) {
let tasks: Vec<DownloadTask> = {
let tasks = self.inner.tasks.lock().unwrap();
tasks.values().cloned().collect()
};
let settings = self.inner.settings.lock().unwrap().clone();
let state = EngineState {
tasks,
settings,
next_id: 0, // storage.save 会从 id_counter 读取
};
self.inner.storage.save(state);
*self.inner.last_save.lock().unwrap() = Instant::now();
}
}