From 069c1b6788856da0f864642900e86880cd804e80 Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 25 Jun 2026 14:27:38 +0330 Subject: [PATCH] fix: resolve deadlock, redownload failure, and UI issues - Fix re-entrant deadlock in `legacy_download_queue_paths` by scoping DB lock - Ensure `release_registered_id` runs in `remove_download` even if cleanup fails - Add fallback to `std::fs::remove_file` if `trash::delete` fails - Ignore 'cannot be paused now' error in `detach_download_for_reconfigure` - Apply `break-all` and `whitespace-pre-wrap` to Toast messages to prevent UI overflow --- src-tauri/src/download_ownership.rs | 16 +++++++------ src-tauri/src/lib.rs | 35 ++++++++++++++++++++--------- src/contexts/ToastContext.tsx | 2 +- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src-tauri/src/download_ownership.rs b/src-tauri/src/download_ownership.rs index aa9ba61..83f6dfc 100644 --- a/src-tauri/src/download_ownership.rs +++ b/src-tauri/src/download_ownership.rs @@ -144,13 +144,15 @@ fn load_records(app_handle: &tauri::AppHandle) -> Result Result, String> { let settings = crate::settings::load_settings(app_handle).ok(); - let database = app_handle.state::(); - let connection = database.lock()?; - let downloads = crate::db::load_downloads(&connection)? - .into_iter() - .map(|value| serde_json::from_str::(&value)) - .collect::, _>>() - .map_err(|error| format!("Invalid download queue ownership data: {error}"))?; + let downloads = { + let database = app_handle.state::(); + let connection = database.lock()?; + crate::db::load_downloads(&connection)? + .into_iter() + .map(|value| serde_json::from_str::(&value)) + .collect::, _>>() + .map_err(|error| format!("Invalid download queue ownership data: {error}"))? + }; let mut paths = Vec::new(); for download in downloads { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 58e5452..5f265ec 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2869,15 +2869,19 @@ async fn remove_download( crate::ipc::DownloadStateEvent::new(id.clone(), crate::ipc::DownloadStatus::Paused), ); - if delete_assets { - if let Some(path) = primary_path.as_deref() { - remove_download_assets(path, &app_handle).await?; + let cleanup_result = (|| async { + if delete_assets { + if let Some(path) = primary_path.as_deref() { + remove_download_assets(path, &app_handle).await?; + } } - } + crate::download_ownership::remove(&app_handle, &id)?; + Ok::<(), String>(()) + })() + .await; - crate::download_ownership::remove(&app_handle, &id)?; state.queue_manager.release_registered_id(&id).await; - Ok(()) + cleanup_result } async fn remove_download_assets( @@ -2889,8 +2893,12 @@ async fn remove_download_assets( } if primary.exists() { - trash::delete(primary) - .map_err(|error| format!("failed to move downloaded file to Trash: {error}"))?; + if let Err(e) = trash::delete(primary) { + log::warn!("failed to move downloaded file to Trash, attempting hard delete: {}", e); + if let Err(hard_err) = std::fs::remove_file(primary) { + return Err(format!("failed to move downloaded file to Trash ({e}) and hard delete failed ({hard_err})")); + } + } } for suffix in [".aria2", ".part", ".ytdl"] { @@ -2923,13 +2931,20 @@ async fn detach_download_for_reconfigure( if let Some(gid) = gid.as_deref().filter(|gid| !gid.starts_with("native:")) { let removal_result = async { - rpc_call( + let pause_res = rpc_call( state.aria2_port.load(std::sync::atomic::Ordering::Relaxed), &state.aria2_secret, "aria2.forcePause", serde_json::json!([gid]), ) - .await?; + .await; + + if let Err(e) = pause_res { + if !e.contains("cannot be paused now") { + return Err(e); + } + } + wait_for_aria2_stopped( state.aria2_port.load(std::sync::atomic::Ordering::Relaxed), &state.aria2_secret, diff --git a/src/contexts/ToastContext.tsx b/src/contexts/ToastContext.tsx index 40184e1..a9ab437 100644 --- a/src/contexts/ToastContext.tsx +++ b/src/contexts/ToastContext.tsx @@ -162,7 +162,7 @@ const ToastItem: React.FC<{ toast: ToastState; removeToast: (id: string) => void onBlur={() => setIsHovered(false)} >
{Icon}
-
{toast.message}
+
{toast.message}