mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-04 06:55:23 +00:00
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:
@@ -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> {
|
fn legacy_download_queue_paths(app_handle: &tauri::AppHandle) -> Result<Vec<PathBuf>, String> {
|
||||||
let settings = crate::settings::load_settings(app_handle).ok();
|
let settings = crate::settings::load_settings(app_handle).ok();
|
||||||
|
|
||||||
let database = app_handle.state::<crate::db::DbState>();
|
let downloads = {
|
||||||
let connection = database.lock()?;
|
let database = app_handle.state::<crate::db::DbState>();
|
||||||
let downloads = crate::db::load_downloads(&connection)?
|
let connection = database.lock()?;
|
||||||
.into_iter()
|
crate::db::load_downloads(&connection)?
|
||||||
.map(|value| serde_json::from_str::<crate::ipc::DownloadItem>(&value))
|
.into_iter()
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.map(|value| serde_json::from_str::<crate::ipc::DownloadItem>(&value))
|
||||||
.map_err(|error| format!("Invalid download queue ownership data: {error}"))?;
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|error| format!("Invalid download queue ownership data: {error}"))?
|
||||||
|
};
|
||||||
|
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
for download in downloads {
|
for download in downloads {
|
||||||
|
|||||||
+25
-10
@@ -2869,15 +2869,19 @@ async fn remove_download(
|
|||||||
crate::ipc::DownloadStateEvent::new(id.clone(), crate::ipc::DownloadStatus::Paused),
|
crate::ipc::DownloadStateEvent::new(id.clone(), crate::ipc::DownloadStatus::Paused),
|
||||||
);
|
);
|
||||||
|
|
||||||
if delete_assets {
|
let cleanup_result = (|| async {
|
||||||
if let Some(path) = primary_path.as_deref() {
|
if delete_assets {
|
||||||
remove_download_assets(path, &app_handle).await?;
|
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;
|
state.queue_manager.release_registered_id(&id).await;
|
||||||
Ok(())
|
cleanup_result
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_download_assets(
|
async fn remove_download_assets(
|
||||||
@@ -2889,8 +2893,12 @@ async fn remove_download_assets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if primary.exists() {
|
if primary.exists() {
|
||||||
trash::delete(primary)
|
if let Err(e) = trash::delete(primary) {
|
||||||
.map_err(|error| format!("failed to move downloaded file to Trash: {error}"))?;
|
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"] {
|
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:")) {
|
if let Some(gid) = gid.as_deref().filter(|gid| !gid.starts_with("native:")) {
|
||||||
let removal_result = async {
|
let removal_result = async {
|
||||||
rpc_call(
|
let pause_res = rpc_call(
|
||||||
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
|
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
&state.aria2_secret,
|
&state.aria2_secret,
|
||||||
"aria2.forcePause",
|
"aria2.forcePause",
|
||||||
serde_json::json!([gid]),
|
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(
|
wait_for_aria2_stopped(
|
||||||
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
|
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
|
||||||
&state.aria2_secret,
|
&state.aria2_secret,
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const ToastItem: React.FC<{ toast: ToastState; removeToast: (id: string) => void
|
|||||||
onBlur={() => setIsHovered(false)}
|
onBlur={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
<div className="mt-0.5 shrink-0">{Icon}</div>
|
<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
|
<button
|
||||||
onClick={() => removeToast(toast.id)}
|
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"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user