diff --git a/src-tauri/src/download.rs b/src-tauri/src/download.rs index 1302052..23b2b43 100644 --- a/src-tauri/src/download.rs +++ b/src-tauri/src/download.rs @@ -23,7 +23,7 @@ const WRITE_BUFFER_CAPACITY: usize = 256 * 1024; #[derive(Debug)] pub enum DownloadCmd { - Start(DownloadPayload), + Start(Box), Pause(Uuid), Cancel(Uuid), CaptureUrls(Vec), @@ -255,7 +255,8 @@ async fn run_coordinator( }; match command { - DownloadCmd::Start(payload) => { + DownloadCmd::Start(payload_box) => { + let payload = *payload_box; if let Some(previous) = active.remove(&payload.id) { let _ = previous.control_tx.send(DownloadControl::Replace).await; } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 18aefae..971cdb7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -48,20 +48,17 @@ async fn fetch_metadata(url: String, user_agent: Option, username: Optio if let Some(host) = parsed.host_str() { let port = parsed.port_or_known_default().unwrap_or(80); if let Ok(addrs) = std::net::ToSocketAddrs::to_socket_addrs(&(host, port)) { - for addr in addrs { + if let Some(addr) = addrs.into_iter().next() { let ip = addr.ip(); if ip.is_loopback() || ip.is_multicast() || ip.is_unspecified() { return Err("SSRF blocked: Private/local IP not allowed".to_string()); } - match ip { - std::net::IpAddr::V4(ipv4) if ipv4.is_private() || ipv4.is_link_local() => { + if let std::net::IpAddr::V4(ipv4) = ip { + if ipv4.is_private() || ipv4.is_link_local() { return Err("SSRF blocked: Private/local IP not allowed".to_string()); } - _ => { - resolved_addr = Some((host.to_string(), addr)); - break; - } } + resolved_addr = Some((host.to_string(), addr)); } } } @@ -127,8 +124,8 @@ async fn fetch_metadata(url: String, user_agent: Option, username: Optio if filename.is_empty() { if let Ok(parsed) = reqwest::Url::parse(¤t_url) { - if let Some(segments) = parsed.path_segments() { - if let Some(last) = segments.last() { + if let Some(mut segments) = parsed.path_segments() { + if let Some(last) = segments.next_back() { let normalized = last.replace('\\', "/"); filename = std::path::Path::new(&normalized) .file_name() @@ -390,10 +387,9 @@ pub use error::AppError; // Retained only for compatibility with the optional aria2 diagnostic monitor. // Active downloads are owned by DownloadCoordinator. +#[non_exhaustive] pub enum TaskHandle { Aria2(String), - #[doc(hidden)] - Inactive, } pub struct AppState { @@ -421,9 +417,9 @@ pub struct DownloadProgressEvent { fn resolve_path(path: &str, app_handle: &tauri::AppHandle) -> std::path::PathBuf { use tauri::Manager; let mut resolved = std::path::PathBuf::from(path); - if path.starts_with("~/") { + if let Some(stripped) = path.strip_prefix("~/") { if let Ok(home) = app_handle.path().home_dir() { - resolved = home.join(&path[2..]); + resolved = home.join(stripped); } } else if path == "~" { if let Ok(home) = app_handle.path().home_dir() { @@ -559,6 +555,7 @@ async fn test_aria2c(state: tauri::State<'_, AppState>) -> Result { let gid = result.as_str().unwrap_or("").to_string(); state.aria2_gids.write().unwrap().insert(id.clone(), gid); - return Ok(()); + Ok(()) } Err(e) => { eprintln!("aria2 failed, falling back to native coordinator: {}", e); state .download_coordinator - .send(download::DownloadCmd::Start(download::DownloadPayload { + .send(download::DownloadCmd::Start(Box::new(download::DownloadPayload { id: download_id, urls: collect_download_uris(&url, mirrors.as_deref()), output_path: resolved_dest.join(safe_filename), @@ -664,14 +661,15 @@ async fn start_download( user_agent, max_tries: mt, proxy, - })) + }))) .await .map_err(AppError::Internal)?; - return Ok(()); + Ok(()) } } } +#[allow(clippy::too_many_arguments)] #[tauri::command] async fn start_media_download( app_handle: tauri::AppHandle, @@ -730,6 +728,7 @@ async fn start_media_download( Ok(()) } +#[allow(clippy::too_many_arguments)] pub(crate) async fn start_media_download_internal( app_handle: tauri::AppHandle, id: &str, @@ -1120,7 +1119,7 @@ fn open_automation_settings(app_handle: tauri::AppHandle) -> Result<(), String> use tauri_plugin_opener::OpenerExt; app_handle.opener().open_url("x-apple.systempreferences:com.apple.preference.security?Privacy_Automation", None::) .map_err(|e| format!("Failed to open Automation settings: {}", e))?; - return Ok(()); + Ok(()) } #[cfg(not(target_os = "macos"))] diff --git a/src-tauri/src/parity.rs b/src-tauri/src/parity.rs index ff4c0ee..dfeb158 100644 --- a/src-tauri/src/parity.rs +++ b/src-tauri/src/parity.rs @@ -107,7 +107,7 @@ pub async fn check_for_updates(app_handle: tauri::AppHandle) -> Result return Err("No stable release was found.".to_string()), }; - let latest_version = release.tag_name.trim_start_matches(|c| c == 'v' || c == 'V').to_string(); + let latest_version = release.tag_name.trim_start_matches(['v', 'V']).to_string(); if cmp_versions(&latest_version, ¤t_version) == std::cmp::Ordering::Greater { Ok(ReleaseCheckOutcome::UpdateAvailable { @@ -131,8 +131,8 @@ pub async fn check_for_updates(app_handle: tauri::AppHandle) -> Result std::cmp::Ordering { use semver::Version; - let a_clean = a.trim_start_matches(|c| c == 'v' || c == 'V'); - let b_clean = b.trim_start_matches(|c| c == 'v' || c == 'V'); + let a_clean = a.trim_start_matches(['v', 'V']); + let b_clean = b.trim_start_matches(['v', 'V']); let a_ver = Version::parse(a_clean).unwrap_or_else(|_| Version::new(0, 0, 0)); let b_ver = Version::parse(b_clean).unwrap_or_else(|_| Version::new(0, 0, 0)); @@ -145,9 +145,9 @@ pub async fn create_category_directories(app_handle: tauri::AppHandle, paths: Ve use tauri::Manager; for path in paths { let mut expanded = std::path::PathBuf::from(&path); - if path.starts_with("~/") { + if let Some(stripped) = path.strip_prefix("~/") { if let Ok(home) = app_handle.path().home_dir() { - expanded = home.join(&path[2..]); + expanded = home.join(stripped); } } if !expanded.exists() { diff --git a/src-tauri/tests/download_engine.rs b/src-tauri/tests/download_engine.rs index d12a468..3a30c41 100644 --- a/src-tauri/tests/download_engine.rs +++ b/src-tauri/tests/download_engine.rs @@ -284,7 +284,7 @@ async fn pause_then_resume_uses_range_and_preserves_integrity() { let (coordinator, mut events) = DownloadCoordinator::spawn_headless(); coordinator - .send(DownloadCmd::Start(download)) + .send(DownloadCmd::Start(Box::new(download))) .await .unwrap(); wait_for_progress(&mut events, id, 256 * 1024).await; @@ -296,11 +296,11 @@ async fn pause_then_resume_uses_range_and_preserves_integrity() { assert!(paused_len < expected.len() as u64); coordinator - .send(DownloadCmd::Start(payload( + .send(DownloadCmd::Start(Box::new(payload( id, server.file_url(), output_path.clone(), - ))) + )))) .await .unwrap(); wait_for_completion(&mut events, id).await; @@ -331,11 +331,11 @@ async fn aggregates_many_http_chunks_with_complete_progress() { let started = Instant::now(); coordinator - .send(DownloadCmd::Start(payload( + .send(DownloadCmd::Start(Box::new(payload( id, server.file_url(), output_path.clone(), - ))) + )))) .await .unwrap(); let observed = wait_for_completion(&mut events, id).await; @@ -376,7 +376,7 @@ async fn retries_transient_http_failures_then_completes() { download.max_tries = 3; coordinator - .send(DownloadCmd::Start(download)) + .send(DownloadCmd::Start(Box::new(download))) .await .unwrap(); wait_for_completion(&mut events, id).await; @@ -399,7 +399,7 @@ async fn reports_terminal_http_errors_after_retry_budget() { download.max_tries = 2; coordinator - .send(DownloadCmd::Start(download)) + .send(DownloadCmd::Start(Box::new(download))) .await .unwrap(); let error = loop { @@ -430,11 +430,11 @@ async fn cancel_removes_partial_file_without_terminal_success_event() { let (coordinator, mut events) = DownloadCoordinator::spawn_headless(); coordinator - .send(DownloadCmd::Start(payload( + .send(DownloadCmd::Start(Box::new(payload( id, server.file_url(), output_path.clone(), - ))) + )))) .await .unwrap(); wait_for_progress(&mut events, id, 256 * 1024).await;