From 5797db27c5a56b41ea3cf450be09b7b911982d2d Mon Sep 17 00:00:00 2001 From: NimBold Date: Sun, 9 Aug 2026 04:21:15 +0330 Subject: [PATCH] fix(windows): persist adaptive mirror history --- scripts/smoke-aria2-transfers.js | 1 + src-tauri/src/lib.rs | 4 ++++ src-tauri/src/storage.rs | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/scripts/smoke-aria2-transfers.js b/scripts/smoke-aria2-transfers.js index ba2fd91..fa7e91d 100644 --- a/scripts/smoke-aria2-transfers.js +++ b/scripts/smoke-aria2-transfers.js @@ -460,6 +460,7 @@ try { smokeFailure = new Error(`${error.message}${detail ? `\n${detail}` : ''}`); } finally { try { + if (process.platform === 'win32') fs.rmSync(serverStatPath, { force: true }); await stop(child, rpcPort, secret); if (smokePassed) { const stat = fs.readFileSync(serverStatPath, 'utf8'); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ed48337..19cac14 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3115,6 +3115,10 @@ async fn shutdown_aria2_daemon(app_handle: tauri::AppHandle) { if let Some(state) = app_handle.try_state::() { let port = state.aria2_port.load(Ordering::Relaxed); if port != 0 { + #[cfg(target_os = "windows")] + if let Err(error) = state.storage_layout.prepare_aria2_server_stat_for_replace() { + log::warn!("adaptive mirror history cannot be replaced on shutdown: {error}"); + } let shutdown = tokio::time::timeout( std::time::Duration::from_secs(2), rpc_call(port, &state.aria2_secret, "aria2.shutdown", serde_json::json!([])), diff --git a/src-tauri/src/storage.rs b/src-tauri/src/storage.rs index 4c82bde..ca92b88 100644 --- a/src-tauri/src/storage.rs +++ b/src-tauri/src/storage.rs @@ -228,6 +228,26 @@ impl StorageLayout { } Ok(path) } + + /// The MSVC build of Aria2 uses C `rename`, which cannot replace an + /// existing destination on Windows. Remove only the already-validated, + /// app-owned regular cache immediately before graceful shutdown so + /// Aria2's `__temp` file can be renamed into place. + pub fn prepare_aria2_server_stat_for_replace(&self) -> Result<(), String> { + let path = self.aria2_server_stat_path(); + match std::fs::symlink_metadata(&path) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => { + Err("Aria2 server-stat cache replacement target is not a regular file".to_string()) + } + Ok(_) => std::fs::remove_file(&path).map_err(|error| { + format!("failed to prepare Aria2 server-stat replacement: {error}") + }), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(error) => Err(format!( + "failed to inspect Aria2 server-stat replacement target: {error}" + )), + } + } } fn aria2_server_stat_is_valid(contents: &str) -> bool { @@ -395,6 +415,23 @@ mod tests { } } + #[test] + fn aria2_server_stat_replacement_removes_only_the_managed_regular_cache() { + let root = TempDir::new().unwrap(); + let layout = test_layout(root.path()); + layout.prepare_aria2_dht_paths().unwrap(); + let path = layout.prepare_aria2_server_stat_path().unwrap(); + fs::write( + &path, + "host=mirror.example, protocol=https, dl_speed=1, last_updated=1, status=OK\n", + ) + .unwrap(); + + layout.prepare_aria2_server_stat_for_replace().unwrap(); + assert!(!path.exists()); + layout.prepare_aria2_server_stat_for_replace().unwrap(); + } + #[cfg(unix)] #[test] fn aria2_server_stat_cache_rejects_symlink_output() { @@ -411,6 +448,7 @@ mod tests { .unwrap(); assert!(layout.prepare_aria2_server_stat_path().is_err()); + assert!(layout.prepare_aria2_server_stat_for_replace().is_err()); } #[cfg(unix)]