diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 313e161..30ddccb 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -25,6 +25,7 @@ use crate::{ normal_info::NormalInfo, qrcode_data::QrcodeData, qrcode_status::QrcodeStatus, + restart_download_task_params::RestartDownloadTaskParams, search_params::SearchParams, search_result::{ BangumiSearchResult, CheeseSearchResult, FavSearchResult, NormalSearchResult, @@ -267,6 +268,14 @@ pub fn restart_download_tasks(app: AppHandle, task_ids: Vec) { download_manager.restart_download_tasks(&task_ids); } +#[allow(clippy::needless_pass_by_value)] +#[tauri::command(async)] +#[specta::specta] +pub fn restart_download_task(app: AppHandle, params: RestartDownloadTaskParams) { + let download_manager = app.get_download_manager(); + download_manager.restart_download_task(¶ms); +} + #[allow(clippy::needless_pass_by_value)] #[tauri::command(async)] #[specta::specta] diff --git a/src-tauri/src/downloader/download_manager.rs b/src-tauri/src/downloader/download_manager.rs index 3452ac2..a4147a5 100644 --- a/src-tauri/src/downloader/download_manager.rs +++ b/src-tauri/src/downloader/download_manager.rs @@ -17,7 +17,10 @@ use tokio::sync::Semaphore; use crate::{ events::DownloadEvent, extensions::{AnyhowErrorToStringChain, AppHandleExt}, - types::create_download_task_params::CreateDownloadTaskParams, + types::{ + create_download_task_params::CreateDownloadTaskParams, + restart_download_task_params::RestartDownloadTaskParams, + }, }; use super::{ @@ -182,6 +185,49 @@ impl DownloadManager { } } + pub fn restart_download_task(&self, params: &RestartDownloadTaskParams) { + let task_id = ¶ms.task_id; + + let tasks = self.download_tasks.read(); + let Some(task) = tasks.get(task_id) else { + let err_title = "重来下载任务失败"; + let err_msg = format!("未找到ID为`{task_id}`的下载任务"); + tracing::error!(err_title, message = err_msg); + return; + }; + + { + let mut progress = task.progress.write(); + + progress.video_task.selected = params.video_task_selected; + progress.audio_task.selected = params.audio_task_selected; + progress.video_process_task.merge_selected = params.merge_selected; + progress.video_process_task.embed_chapter_selected = params.embed_chapter_selected; + progress.video_process_task.embed_skip_selected = params.embed_skip_selected; + progress.subtitle_task.selected = params.subtitle_task_selected; + progress.danmaku_task.xml_selected = params.xml_danmaku_selected; + progress.danmaku_task.ass_selected = params.ass_danmaku_selected; + progress.danmaku_task.json_selected = params.json_danmaku_selected; + progress.cover_task.selected = params.cover_task_selected; + progress.nfo_task.selected = params.nfo_task_selected; + progress.json_task.selected = params.json_task_selected; + + progress.video_task.video_quality = params.video_quality; + progress.video_task.codec_type = params.codec_type; + progress.audio_task.audio_quality = params.audio_quality; + } + + if let Err(err) = task.restart_sender.send(()).map_err(anyhow::Error::from) { + let err_title = "重来下载任务失败"; + let err = err.context(format!("通知ID为`{task_id}`的下载任务重来失败")); + let string_chain = err.to_string_chain(); + tracing::error!(err_title, message = string_chain); + return; + } + + tracing::debug!("已通知ID为`{task_id}`的下载任务重来"); + } + async fn emit_download_speed_loop(app: AppHandle, byte_per_sec: Arc) { let mut interval = tokio::time::interval(Duration::from_secs(1)); diff --git a/src-tauri/src/downloader/tasks/audio_task.rs b/src-tauri/src/downloader/tasks/audio_task.rs index 6c6294f..162547b 100644 --- a/src-tauri/src/downloader/tasks/audio_task.rs +++ b/src-tauri/src/downloader/tasks/audio_task.rs @@ -296,6 +296,7 @@ impl AudioTask { !self.selected || self.completed } + #[allow(clippy::too_many_lines)] pub async fn process( &self, download_task: &Arc, @@ -325,14 +326,19 @@ impl AudioTask { "{filename}.m4a.com.lanyeeee.bilibili-video-downloader" )); - let file = if temp_file_path.exists() { - // 如果文件已存在,则打开它 + let should_reuse_temp_file = temp_file_path + .metadata() + .map(|m| m.len() == audio_task.content_length) + .unwrap_or(false); + + let file = if should_reuse_temp_file { + // 如果临时文件可以重用,则直接打开它 OpenOptions::new() .read(true) .write(true) .open(&temp_file_path)? } else { - // 如果文件不存在,创建它并预分配空间 + // 如果临时文件不能重用,则创建个新的 let file = File::create(&temp_file_path)?; file.allocate(audio_task.content_length)?; file diff --git a/src-tauri/src/downloader/tasks/video_task.rs b/src-tauri/src/downloader/tasks/video_task.rs index fc36860..4b1936e 100644 --- a/src-tauri/src/downloader/tasks/video_task.rs +++ b/src-tauri/src/downloader/tasks/video_task.rs @@ -337,14 +337,19 @@ impl VideoTask { "{filename}.mp4.com.lanyeeee.bilibili-video-downloader" )); - let file = if temp_file_path.exists() { - // 如果临时文件已存在,则打开它 + let should_reuse_temp_file = temp_file_path + .metadata() + .map(|m| m.len() == video_task.content_length) + .unwrap_or(false); + + let file = if should_reuse_temp_file { + // 如果临时文件可以重用,则直接打开它 OpenOptions::new() .read(true) .write(true) .open(&temp_file_path)? } else { - // 如果临时文件不存在,创建它并预分配空间 + // 如果临时文件不能重用,则创建个新的 let file = File::create(&temp_file_path)?; file.allocate(video_task.content_length)?; file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 58bd3bb..fc1aee3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -53,6 +53,7 @@ pub fn run() { resume_download_tasks, delete_download_tasks, restart_download_tasks, + restart_download_task, restore_download_tasks, search, get_logs_dir_size, diff --git a/src-tauri/src/types/mod.rs b/src-tauri/src/types/mod.rs index b3cc9e7..a7e157f 100644 --- a/src-tauri/src/types/mod.rs +++ b/src-tauri/src/types/mod.rs @@ -22,6 +22,7 @@ pub mod normal_media_url; pub mod player_info; pub mod qrcode_data; pub mod qrcode_status; +pub mod restart_download_task_params; pub mod search_params; pub mod search_result; pub mod skip_segments; diff --git a/src-tauri/src/types/restart_download_task_params.rs b/src-tauri/src/types/restart_download_task_params.rs new file mode 100644 index 0000000..9f63dd9 --- /dev/null +++ b/src-tauri/src/types/restart_download_task_params.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; +use specta::Type; + +use crate::types::{ + audio_quality::AudioQuality, codec_type::CodecType, video_quality::VideoQuality, +}; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)] +#[serde(default)] +#[allow(clippy::struct_excessive_bools)] +pub struct RestartDownloadTaskParams { + pub task_id: String, + + pub video_task_selected: bool, + pub audio_task_selected: bool, + pub merge_selected: bool, + pub embed_chapter_selected: bool, + pub embed_skip_selected: bool, + pub subtitle_task_selected: bool, + pub xml_danmaku_selected: bool, + pub ass_danmaku_selected: bool, + pub json_danmaku_selected: bool, + pub cover_task_selected: bool, + pub nfo_task_selected: bool, + pub json_task_selected: bool, + + pub video_quality: VideoQuality, + pub codec_type: CodecType, + pub audio_quality: AudioQuality, +} diff --git a/src/bindings.ts b/src/bindings.ts index fb25eec..58115f5 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -119,6 +119,9 @@ async deleteDownloadTasks(taskIds: string[]) : Promise { async restartDownloadTasks(taskIds: string[]) : Promise { await TAURI_INVOKE("restart_download_tasks", { taskIds }); }, +async restartDownloadTask(params: RestartDownloadTaskParams) : Promise { + await TAURI_INVOKE("restart_download_task", { params }); +}, async restoreDownloadTasks() : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("restore_download_tasks") }; @@ -359,6 +362,7 @@ export type QrcodeStatus = { url: string; refresh_token: string; timestamp: numb export type RatingInBangumi = { count: number; score: number } export type RatingInBangumiFollow = { score: number; count: number } export type RecommendSeason = { cover: string; ep_count: string; id: number; season_url: string; subtitle: string; title: string; view: number } +export type RestartDownloadTaskParams = { task_id: string; video_task_selected: boolean; audio_task_selected: boolean; merge_selected: boolean; embed_chapter_selected: boolean; embed_skip_selected: boolean; subtitle_task_selected: boolean; xml_danmaku_selected: boolean; ass_danmaku_selected: boolean; json_danmaku_selected: boolean; cover_task_selected: boolean; nfo_task_selected: boolean; json_task_selected: boolean; video_quality: VideoQuality; codec_type: CodecType; audio_quality: AudioQuality } export type Rights = { bp: number; elec: number; download: number; movie: number; pay: number; hd5: number; no_reprint: number; autoplay: number; ugc_pay: number; is_cooperation: number; ugc_pay_preview: number; no_background: number; clean_mode: number; is_stein_gate: number; is_360: number; no_share: number; arc_pay: number; free_watch: number } export type RightsInBangumi = { allow_bp: number; allow_bp_rank: number; allow_download: number; allow_review: number; area_limit: number; ban_area_show: number; can_watch: number; copyright: string; forbid_pre: number; freya_white: number; is_cover_show: number; is_preview: number; only_vip_download: number; resource: string; watch_platform: number } export type RightsInBangumiEp = { allow_dm: number; allow_download: number; area_limit: number }