This commit is contained in:
zhongluofeng
2026-07-30 09:09:29 +08:00
parent 74902c4cec
commit f452322aad
32 changed files with 4586 additions and 115 deletions
+146 -2
View File
@@ -9,7 +9,50 @@ 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};
use super::task::{DownloadTask, DownloaderSettings, ProbeResult, Segment, TaskStatus};
/// 重复类型
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum DuplicateKind {
/// 无重复
None,
/// URL 重复(已有相同链接的任务)
Url,
/// 文件名重复(已有同名任务下载到同一目录)
Filename,
/// 磁盘文件已存在
FileExists,
}
/// 已存在的任务信息(用于前端展示)
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExistingTaskInfo {
pub id: String,
pub filename: String,
pub status: TaskStatus,
}
/// check_url 命令返回的结果
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckUrlResult {
/// 探测是否成功
pub ok: bool,
/// 错误信息(探测失败时)
pub error: Option<String>,
/// 文件名(探测成功时)
pub filename: Option<String>,
/// 文件大小(字节)
pub total_size: Option<u64>,
/// 是否支持断点续传
pub supports_resume: bool,
/// 重复类型
pub duplicate: DuplicateKind,
/// 已存在的任务信息
pub existing: Option<ExistingTaskInfo>,
}
/// 进度事件载荷(发给前端 download-progress 事件)
#[derive(Debug, Clone, serde::Serialize)]
@@ -130,13 +173,109 @@ impl DownloadEngine {
// ===================== 公开 API =====================
/// 检查 URL 重复性并探测文件信息。
/// 返回 (探测结果, 重复类型, 已存在任务信息)。
pub async fn check_url(
&self,
url: &str,
dir: Option<&str>,
headers: &HashMap<String, String>,
) -> (Result<ProbeResult, String>, DuplicateKind, Option<ExistingTaskInfo>) {
let probe = self.inner.http.probe(url, headers).await;
let settings = self.inner.settings.lock().unwrap().clone();
let task_dir = dir.map(|d| d.to_string()).unwrap_or_else(|| settings.download_dir.clone());
let filename = 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 mut duplicate = DuplicateKind::None;
let mut existing: Option<ExistingTaskInfo> = None;
{
let tasks = self.inner.tasks.lock().unwrap();
for t in tasks.values() {
// URL 完全相同
if t.url == url {
duplicate = DuplicateKind::Url;
existing = Some(ExistingTaskInfo {
id: t.id.clone(),
filename: t.filename.clone(),
status: t.status.clone(),
});
break;
}
// 目标文件名 + 目录相同(可能 URL 不同但下载到同一文件)
if t.filename == filename && t.dir == task_dir {
duplicate = DuplicateKind::Filename;
existing = Some(ExistingTaskInfo {
id: t.id.clone(),
filename: t.filename.clone(),
status: t.status.clone(),
});
break;
}
}
}
// 检查磁盘文件是否已存在(仅探测成功时)
if duplicate == DuplicateKind::None {
if let Ok(ref _p) = probe {
let filepath = PathBuf::from(&task_dir).join(&filename);
if filepath.exists() {
duplicate = DuplicateKind::FileExists;
existing = Some(ExistingTaskInfo {
id: String::new(),
filename,
status: TaskStatus::Complete,
});
}
}
}
(probe, duplicate, existing)
}
/// 生成不冲突的文件名(同名时追加 (1)、(2)...)
fn generate_unique_filename(&self, dir: &str, filename: &str) -> String {
let path = PathBuf::from(dir).join(filename);
if !path.exists() {
return filename.to_string();
}
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("download");
let ext = path.extension().and_then(|s| s.to_str());
for i in 1..1000 {
let new_name = match ext {
Some(e) => format!("{} ({}).{}", stem, i, e),
None => format!("{} ({})", stem, i),
};
let new_path = PathBuf::from(dir).join(&new_name);
if !new_path.exists() {
return new_name;
}
}
// 极端情况:追加时间戳
match ext {
Some(e) => format!("{} ({}).{}", stem, chrono::Utc::now().timestamp_millis(), e),
None => format!("{} ({})", stem, chrono::Utc::now().timestamp_millis()),
}
}
/// 添加下载任务
/// auto_rename: 同名时自动重命名(追加 (1)、(2)...),否则覆盖
pub async fn add_task(
&self,
url: String,
filename: Option<String>,
dir: Option<String>,
headers: HashMap<String, String>,
auto_rename: bool,
) -> Result<String, String> {
// 探测资源信息
let probe = self.inner.http.probe(&url, &headers).await;
@@ -145,7 +284,7 @@ impl DownloadEngine {
let task_dir = dir.unwrap_or_else(|| settings.download_dir.clone());
// 确定文件名
let task_filename = filename
let mut task_filename = filename
.or_else(|| probe.as_ref().ok().and_then(|p| p.filename.clone()))
.unwrap_or_else(|| {
url.split('?')
@@ -156,6 +295,11 @@ impl DownloadEngine {
.unwrap_or_else(|| format!("download_{}", chrono::Utc::now().timestamp()))
});
// 自动重命名:若磁盘已存在同名文件,追加 (1)、(2)...
if auto_rename {
task_filename = self.generate_unique_filename(&task_dir, &task_filename);
}
let id = self.inner.storage.next_task_id();
// 创建分段(受 continue_download 设置控制:关闭时强制单线程、不支持续传)