添加下载管理和 metalink 解析

This commit is contained in:
柚子
2025-02-07 18:49:06 +08:00
parent 0d5618ffd1
commit 2e963b65e2
9 changed files with 340 additions and 140 deletions

View File

@@ -0,0 +1,178 @@
use std::collections::HashMap;
use std::sync::Mutex;
use crate::models::download::{DownloadStatus, DownloadTask};
use crate::utils::metalink::parse_metalink;
use crate::handlers::server::get_json_server_url;
use crate::utils::{format_icon_url, UA};
pub struct DownloadManager {
queue: Mutex<HashMap<String, DownloadTask>>,
}
impl DownloadManager {
pub fn new() -> Self {
DownloadManager {
queue: Mutex::new(HashMap::new()),
}
}
// 获取所有下载任务
pub fn get_downloads(&self) -> Result<Vec<DownloadTask>, String> {
let downloads = self.queue.lock().map_err(|e| e.to_string())?;
Ok(downloads.values().cloned().collect())
}
// 检查是否有正在下载的任务
fn has_downloading_task(&self) -> Result<bool, String> {
let downloads = self.queue.lock().map_err(|e| e.to_string())?;
Ok(downloads.values().any(|task| matches!(task.status, DownloadStatus::Downloading)))
}
// 获取下一个等待下载的任务
fn get_next_queued_task(&self) -> Result<Option<String>, String> {
let downloads = self.queue.lock().map_err(|e| e.to_string())?;
Ok(downloads
.iter()
.find(|(_, task)| matches!(task.status, DownloadStatus::Queued))
.map(|(task_id, _)| task_id.clone()))
}
// 开始下载下一个任务
fn start_next_download(&self) -> Result<(), String> {
let mut downloads = self.queue.lock().map_err(|e| e.to_string())?;
if let Some(task_id) = self.get_next_queued_task()? {
if let Some(task) = downloads.get_mut(&task_id) {
task.status = DownloadStatus::Downloading;
}
}
Ok(())
}
// 添加下载任务
pub async fn add_download(&self, category: String, pkgname: String, filename: String, name: String) -> Result<(), String> {
let task_id = format!("{}/{}", category, pkgname);
// 获取metalink文件URL和内容在获取锁之前完成
let json_server_url = get_json_server_url();
let metalink_url = format!("{}{}/{}/{}.metalink", json_server_url, category, pkgname, filename);
// 发送请求获取metalink文件
let client = reqwest::Client::new();
let response = client
.get(&metalink_url)
.header("User-Agent", UA)
.send()
.await
.map_err(|e| format!("获取metalink文件失败: {}", e))?;
// 检查响应状态
if !response.status().is_success() {
return Err(format!("获取metalink文件失败: HTTP {}", response.status()));
}
// 解析 metalink 内容
let metalink_content = response.text().await
.map_err(|e| format!("读取metalink内容失败: {}", e))?;
let metalink_data = parse_metalink(&metalink_content)?;
println!("{:?}", metalink_data);
// 获取一次锁,完成所有状态更新操作
let mut downloads = self.queue.lock().map_err(|e| e.to_string())?;
// 检查任务是否已存在
if downloads.contains_key(&task_id) {
return Ok(());
}
// 检查是否有正在下载的任务
let has_downloading = downloads.values().any(|task| matches!(task.status, DownloadStatus::Downloading));
let initial_status = if has_downloading {
DownloadStatus::Queued
} else {
DownloadStatus::Downloading
};
// 创建下载任务
let task = DownloadTask {
category: category.clone(),
pkgname: pkgname.clone(),
filename,
name,
icon: format_icon_url(&category, &pkgname),
status: initial_status,
progress: 0.0,
speed: None,
size: None,
metalink: metalink_data,
};
// 添加任务到队列
downloads.insert(task_id, task);
Ok(())
}
// 暂停下载任务
pub fn pause_download(&self, category: String, pkgname: String) -> Result<(), String> {
let mut downloads = self.queue.lock().map_err(|e| e.to_string())?;
let task_id = format!("{}/{}", category, pkgname);
if let Some(task) = downloads.get_mut(&task_id) {
let old_status = task.status.clone();
task.status = DownloadStatus::Paused;
if matches!(old_status, DownloadStatus::Downloading) {
self.start_next_download()?;
}
}
Ok(())
}
// 恢复下载任务
pub fn resume_download(&self, category: String, pkgname: String) -> Result<(), String> {
let mut downloads = self.queue.lock().map_err(|e| e.to_string())?;
let task_id = format!("{}/{}", category, pkgname);
// 先检查任务是否存在且处于暂停状态
let should_resume = downloads
.get(&task_id)
.map(|task| matches!(task.status, DownloadStatus::Paused))
.unwrap_or(false);
if should_resume {
// 检查是否有其他正在下载的任务
let has_downloading = self.has_downloading_task()?;
// 更新任务状态
if let Some(task) = downloads.get_mut(&task_id) {
task.status = if has_downloading {
DownloadStatus::Queued
} else {
DownloadStatus::Downloading
};
}
}
Ok(())
}
// 取消下载任务
pub fn cancel_download(&self, category: String, pkgname: String) -> Result<(), String> {
let mut downloads = self.queue.lock().map_err(|e| e.to_string())?;
let task_id = format!("{}/{}", category, pkgname);
let was_downloading = downloads
.get(&task_id)
.map(|task| matches!(task.status, DownloadStatus::Downloading))
.unwrap_or(false);
downloads.remove(&task_id);
if was_downloading {
self.start_next_download()?;
}
Ok(())
}
}