250 lines
7.7 KiB
Rust
250 lines
7.7 KiB
Rust
use serde::{Deserialize, Serialize};
|
||
use specta::Type;
|
||
use std::collections::HashMap;
|
||
|
||
/// 任务下载协议类型
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type, Default)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum TaskProtocol {
|
||
/// HTTP/HTTPS 直链
|
||
#[default]
|
||
Http,
|
||
/// BitTorrent(磁力链 / .torrent 文件)
|
||
BitTorrent,
|
||
}
|
||
|
||
/// 任务状态
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type)]
|
||
#[serde(rename_all = "lowercase")]
|
||
pub enum TaskStatus {
|
||
/// 排队等待(并发数已满)
|
||
Queued,
|
||
/// 下载中
|
||
Active,
|
||
/// 已暂停
|
||
Paused,
|
||
/// 已完成
|
||
Complete,
|
||
/// 错误
|
||
Error,
|
||
/// 已取消(用户取消:进度与文件已清除,仅保留记录,只能再次下载)
|
||
Cancelled,
|
||
}
|
||
|
||
/// 下载分段(多线程 Range 下载 / 断点续传用)
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct Segment {
|
||
/// 分段索引
|
||
pub index: u32,
|
||
/// 起始字节(含)
|
||
pub start: u64,
|
||
/// 结束字节(含)
|
||
pub end: u64,
|
||
/// 已下载字节
|
||
pub completed: u64,
|
||
}
|
||
|
||
impl Segment {
|
||
/// 该分段总长度
|
||
pub fn len(&self) -> u64 {
|
||
self.end.saturating_sub(self.start) + 1
|
||
}
|
||
/// 是否为未知大小哨兵段(start=0, end=0,表示不支持 Range 或未探测到大小)
|
||
pub fn is_unknown_size(&self) -> bool {
|
||
self.start == 0 && self.end == 0
|
||
}
|
||
/// 是否已完成
|
||
pub fn is_done(&self) -> bool {
|
||
// 未知大小段无法用长度判断是否完成,由流结束(Ok(None))判定;
|
||
// 若按 len()=1 判断,暂停/恢复后 completed>=1 会误判为已完成,导致文件被截断
|
||
if self.is_unknown_size() {
|
||
return false;
|
||
}
|
||
self.completed >= self.len()
|
||
}
|
||
}
|
||
|
||
/// BT 种子内文件条目(多文件任务用;阶段1下载全部文件,但保留列表供 UI 展示)
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct BtFileInfo {
|
||
/// 文件在种子内的索引
|
||
pub index: u32,
|
||
/// 相对种子根目录的路径(如 "sub/file.mkv")
|
||
pub path: String,
|
||
/// 文件大小(字节)
|
||
pub size: u64,
|
||
}
|
||
|
||
/// 下载任务
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct DownloadTask {
|
||
/// 任务 ID(自增 hex 字符串)
|
||
pub id: String,
|
||
/// 下载地址(HTTP URL 或磁力链接)
|
||
pub url: String,
|
||
/// 文件名(HTTP:目标文件名;BT:种子名称)
|
||
pub filename: String,
|
||
/// 保存目录(绝对路径)
|
||
pub dir: String,
|
||
/// 协议类型
|
||
#[serde(default)]
|
||
pub protocol: TaskProtocol,
|
||
/// BT 种子 infohash(协议=BitTorrent 时存在)
|
||
#[serde(default)]
|
||
pub info_hash: Option<String>,
|
||
/// BT 种子内文件列表(协议=BitTorrent 时存在)
|
||
#[serde(default)]
|
||
pub bt_files: Vec<BtFileInfo>,
|
||
/// BT 元数据是否已解析就绪(异步添加时:后台解析完成前为 false,调度器跳过)
|
||
#[serde(default)]
|
||
pub bt_metadata_ready: bool,
|
||
/// 状态
|
||
pub status: TaskStatus,
|
||
/// 文件总大小(字节),0=未知
|
||
pub total_size: u64,
|
||
/// 已下载字节
|
||
pub completed_size: u64,
|
||
/// 当前下载速度 bytes/s
|
||
pub speed: u64,
|
||
/// 服务器是否支持断点续传
|
||
pub supports_resume: bool,
|
||
/// 分段信息
|
||
#[serde(default)]
|
||
pub segments: Vec<Segment>,
|
||
/// 错误信息
|
||
#[serde(default)]
|
||
pub error: Option<String>,
|
||
/// 创建时间(Unix 时间戳,毫秒)
|
||
pub created_at: i64,
|
||
/// 自定义请求头(Cookie / Referer 等)
|
||
#[serde(default)]
|
||
pub headers: HashMap<String, String>,
|
||
}
|
||
|
||
impl DownloadTask {
|
||
/// 文件完整路径(最终文件名)
|
||
pub fn file_path(&self) -> std::path::PathBuf {
|
||
std::path::PathBuf::from(&self.dir).join(&self.filename)
|
||
}
|
||
|
||
/// 下载临时文件路径(下载未完成时使用,完成后重命名为 file_path)
|
||
pub fn temp_file_path(&self) -> std::path::PathBuf {
|
||
std::path::PathBuf::from(&self.dir).join(format!("{}.thingdl", self.filename))
|
||
}
|
||
|
||
/// 更新已下载总量(聚合所有分段)
|
||
pub fn recalc_completed(&mut self) {
|
||
if self.segments.is_empty() {
|
||
return;
|
||
}
|
||
self.completed_size = self.segments.iter().map(|s| s.completed).sum();
|
||
}
|
||
}
|
||
|
||
/// 下载设置
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct DownloaderSettings {
|
||
/// 下载目录
|
||
#[serde(default = "default_download_dir")]
|
||
pub download_dir: String,
|
||
/// 最大同时下载数
|
||
#[serde(default = "default_max_concurrent")]
|
||
pub max_concurrent: u32,
|
||
/// 单任务最大连接数(多线程分段数)
|
||
#[serde(default = "default_max_connections")]
|
||
pub max_connections: u32,
|
||
/// 断点续传
|
||
#[serde(default = "default_true")]
|
||
pub continue_download: bool,
|
||
/// 全局速度限制 KB/s(0=不限)
|
||
#[serde(default)]
|
||
pub global_speed_limit: u64,
|
||
/// 扩展 HTTP API 端口
|
||
#[serde(default = "default_extension_port")]
|
||
pub extension_port: u16,
|
||
/// 扩展认证密钥(空=不认证)
|
||
#[serde(default)]
|
||
pub extension_secret: String,
|
||
/// 删除任务时是否同时删除已下载的文件
|
||
#[serde(default)]
|
||
pub delete_files_on_remove: bool,
|
||
/// 添加下载前检查重复(URL 或文件名重复时询问)
|
||
#[serde(default = "default_true")]
|
||
pub check_duplicate: bool,
|
||
/// 下载是否使用代理:true=尊重系统代理(mihomo 开启系统代理时经其转发),false=强制直连
|
||
#[serde(default = "default_true")]
|
||
pub use_proxy: bool,
|
||
/// BitTorrent 上传限速 KB/s(0=不限)
|
||
#[serde(default)]
|
||
pub bt_upload_limit_kb: u64,
|
||
/// BitTorrent 下载完成后是否继续做种上传(false=下载完即停止上传)
|
||
#[serde(default)]
|
||
pub bt_seed_after_download: bool,
|
||
/// BitTorrent 监听端口(0=自动选择)
|
||
#[serde(default)]
|
||
pub bt_listen_port: u16,
|
||
/// BitTorrent 使用代理下载:开启后自动使用代理模块(mihomo)的 SOCKS5 端口;代理不可用时降级直连
|
||
#[serde(default)]
|
||
pub bt_use_proxy: bool,
|
||
}
|
||
|
||
fn default_max_concurrent() -> u32 {
|
||
5
|
||
}
|
||
fn default_max_connections() -> u32 {
|
||
8
|
||
}
|
||
fn default_true() -> bool {
|
||
true
|
||
}
|
||
fn default_extension_port() -> u16 {
|
||
16800
|
||
}
|
||
|
||
fn default_download_dir() -> String {
|
||
dirs::download_dir()
|
||
.or_else(|| {
|
||
std::env::var("USERPROFILE")
|
||
.ok()
|
||
.map(|p| std::path::PathBuf::from(p).join("Downloads"))
|
||
})
|
||
.map(|p| p.to_string_lossy().to_string())
|
||
.unwrap_or_else(|| "downloads".to_string())
|
||
}
|
||
|
||
impl Default for DownloaderSettings {
|
||
fn default() -> Self {
|
||
Self {
|
||
download_dir: default_download_dir(),
|
||
max_concurrent: default_max_concurrent(),
|
||
max_connections: default_max_connections(),
|
||
continue_download: true,
|
||
global_speed_limit: 0,
|
||
extension_port: default_extension_port(),
|
||
extension_secret: String::new(),
|
||
delete_files_on_remove: false,
|
||
check_duplicate: true,
|
||
use_proxy: true,
|
||
bt_upload_limit_kb: 0,
|
||
bt_seed_after_download: false,
|
||
bt_listen_port: 0,
|
||
bt_use_proxy: false,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// HEAD/Range 探测结果
|
||
#[derive(Debug, Clone)]
|
||
pub struct ProbeResult {
|
||
/// 文件大小(字节),None=未知
|
||
pub total_size: Option<u64>,
|
||
/// 是否支持 Range 请求
|
||
pub supports_resume: bool,
|
||
/// 从 Content-Disposition 或 URL 推断的文件名
|
||
pub filename: Option<String>,
|
||
}
|