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
This commit is contained in:
NimBold
2026-06-25 14:27:38 +03:30
parent b4ff14805b
commit 069c1b6788
3 changed files with 35 additions and 18 deletions
+9 -7
View File
@@ -144,13 +144,15 @@ fn load_records(app_handle: &tauri::AppHandle) -> Result<Vec<DownloadOwnershipRe
fn legacy_download_queue_paths(app_handle: &tauri::AppHandle) -> Result<Vec<PathBuf>, String> {
let settings = crate::settings::load_settings(app_handle).ok();
let database = app_handle.state::<crate::db::DbState>();
let connection = database.lock()?;
let downloads = crate::db::load_downloads(&connection)?
.into_iter()
.map(|value| serde_json::from_str::<crate::ipc::DownloadItem>(&value))
.collect::<Result<Vec<_>, _>>()
.map_err(|error| format!("Invalid download queue ownership data: {error}"))?;
let downloads = {
let database = app_handle.state::<crate::db::DbState>();
let connection = database.lock()?;
crate::db::load_downloads(&connection)?
.into_iter()
.map(|value| serde_json::from_str::<crate::ipc::DownloadItem>(&value))
.collect::<Result<Vec<_>, _>>()
.map_err(|error| format!("Invalid download queue ownership data: {error}"))?
};
let mut paths = Vec::new();
for download in downloads {
+25 -10
View File
@@ -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,
+1 -1
View File
@@ -162,7 +162,7 @@ const ToastItem: React.FC<{ toast: ToastState; removeToast: (id: string) => void
onBlur={() => setIsHovered(false)}
>
<div className="mt-0.5 shrink-0">{Icon}</div>
<div className="font-semibold flex-1 tracking-tight">{toast.message}</div>
<div className="font-semibold flex-1 tracking-tight break-all whitespace-pre-wrap">{toast.message}</div>
<button
onClick={() => removeToast(toast.id)}
className="shrink-0 ml-2 mt-0.5 opacity-60 hover:opacity-100 hover:bg-black/5 dark:hover:bg-white/10 p-1 rounded-full transition-all active:scale-90"