From 5710d872957fccc57a6edde98797887ac5825f82 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 8 Nov 2025 18:40:19 +0100 Subject: [PATCH] wip --- .../src-tauri/src/lib.rs | 195 +++++++++++++++++- crates/lanspread-tauri-deno-ts/src/App.tsx | 4 +- 2 files changed, 189 insertions(+), 10 deletions(-) diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs index 9b2891e..266dc78 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs +++ b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, net::SocketAddr, path::{Path, PathBuf}, sync::Arc, @@ -21,6 +21,8 @@ struct LanSpreadState { games: Arc>, games_in_download: Arc>>, games_folder: Arc>, + // Track backup paths for games being updated + game_backups: Arc>>, } #[tauri::command] @@ -53,6 +55,137 @@ fn install_game(id: String, state: tauri::State) -> bool { true } +/// Backup the current game folder by renaming it to `___TO_BE_DELETE___GameNameHere` +/// # Errors +/// Returns error if backup fails +fn backup_game_folder(game_path: &Path) -> eyre::Result { + if !game_path.exists() { + bail!("Game folder does not exist: {}", game_path.display()); + } + + let game_name = game_path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| eyre::eyre!("Invalid game folder name"))?; + + let parent = game_path + .parent() + .ok_or_else(|| eyre::eyre!("Cannot get parent directory"))?; + + let backup_name = format!("___TO_BE_DELETE___{game_name}"); + let backup_path = parent.join(backup_name); + + // Remove existing backup if it exists + if backup_path.exists() { + std::fs::remove_dir_all(&backup_path)?; + } + + // Rename current game folder to backup + std::fs::rename(game_path, &backup_path)?; + + log::info!( + "Backed up game folder: {} -> {}", + game_path.display(), + backup_path.display() + ); + Ok(backup_path) +} + +/// Restore game folder from backup +/// # Errors +/// Returns error if restore fails +fn restore_game_folder(game_path: &Path, backup_path: &Path) -> eyre::Result<()> { + if !backup_path.exists() { + bail!("Backup folder does not exist: {}", backup_path.display()); + } + + // Remove the partially installed game folder if it exists + if game_path.exists() { + std::fs::remove_dir_all(game_path)?; + } + + // Restore backup + std::fs::rename(backup_path, game_path)?; + + log::info!( + "Restored game folder from backup: {} -> {}", + backup_path.display(), + game_path.display() + ); + Ok(()) +} + +/// Remove backup folder after successful update +/// # Errors +/// Returns error if cleanup fails +fn cleanup_backup_folder(backup_path: &Path) -> eyre::Result<()> { + if backup_path.exists() { + std::fs::remove_dir_all(backup_path)?; + log::info!("Cleaned up backup folder: {}", backup_path.display()); + } + Ok(()) +} + +#[tauri::command] +fn update_game(id: String, state: tauri::State) -> bool { + let already_in_download = tauri::async_runtime::block_on(async { + if state.inner().games_in_download.read().await.contains(&id) { + log::warn!("Game is already downloading/updating: {id}"); + return true; + } + false + }); + + if already_in_download { + return false; + } + + // Get the games folder + let games_folder = + tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() }); + + let games_folder = PathBuf::from(games_folder); + let game_path = games_folder.join(&id); + + // Backup current game folder + let backup_path = match backup_game_folder(&game_path) { + Ok(path) => path, + Err(e) => { + log::error!("Failed to backup game folder for {id}: {e}"); + return false; + } + }; + + // Store backup path for later cleanup/restore + tauri::async_runtime::block_on(async { + state + .inner() + .game_backups + .write() + .await + .insert(id.clone(), backup_path.clone()); + }); + + log::info!("Starting update for game: {id}"); + + // Start the download process + if let Err(e) = state + .inner() + .client_ctrl + .send(ClientCommand::GetGame(id.clone())) + { + log::error!("Failed to send message to client: {e:?}"); + + // Try to restore backup if download fails to start + if let Err(restore_err) = restore_game_folder(&game_path, &backup_path) { + log::error!("Failed to restore backup after download failure: {restore_err}"); + } + return false; + } + + true +} + #[cfg(target_os = "windows")] fn run_as_admin(file: &str, params: &str, dir: &str) -> bool { use std::{ffi::OsStr, os::windows::ffi::OsStrExt}; @@ -373,6 +506,7 @@ pub fn run() { games: Arc::new(RwLock::new(GameDB::empty())), games_in_download: Arc::new(RwLock::new(HashSet::new())), games_folder: Arc::new(RwLock::new(String::new())), + game_backups: Arc::new(RwLock::new(std::collections::HashMap::new())), }; tauri::Builder::default() @@ -384,7 +518,8 @@ pub fn run() { request_games, install_game, run_game, - update_game_directory + update_game_directory, + update_game ]) .manage(lanspread_state) .setup(|app| { @@ -453,7 +588,6 @@ pub fn run() { .await .remove(&id.clone()); - let games_folder = app_handle .state::() .inner() @@ -463,12 +597,33 @@ pub fn run() { .clone(); if let Ok(sidecar) = app_handle.shell().sidecar("unrar") { - unpack_game(&id, sidecar, games_folder).await; + let _games_folder_clone = games_folder.clone(); + let id_clone = id.clone(); + let app_handle_clone = app_handle.clone(); - log::info!("ClientEvent::UnpackGameFinished received"); - if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) { - log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}"); - } + // Spawn a separate task to handle unpacking and backup cleanup + tauri::async_runtime::spawn(async move { + unpack_game(&id, sidecar, games_folder).await; + + // Check if this was an update and clean up backup if successful + let backup_path = app_handle_clone + .state::() + .inner() + .game_backups + .write() + .await + .remove(&id_clone); + + if let Some(backup_path) = backup_path + && let Err(e) = cleanup_backup_folder(&backup_path) { + log::error!("Failed to cleanup backup folder after successful update: {e}"); + } + + log::info!("ClientEvent::UnpackGameFinished received"); + if let Err(e) = app_handle_clone.emit("game-unpack-finished", Some(id_clone.clone())) { + log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}"); + } + }); } } ClientEvent::DownloadGameFilesFailed { id } => { @@ -485,6 +640,30 @@ pub fn run() { .write() .await .remove(&id.clone()); + + // Check if this was an update and restore backup if failed + let backup_path = app_handle + .state::() + .inner() + .game_backups + .write() + .await + .remove(&id); + + if let Some(backup_path) = backup_path { + let games_folder = app_handle + .state::() + .inner() + .games_folder + .read() + .await + .clone(); + + let game_path = PathBuf::from(games_folder).join(&id); + if let Err(e) = restore_game_folder(&game_path, &backup_path) { + log::error!("Failed to restore backup after download failure: {e}"); + } + } }, } } diff --git a/crates/lanspread-tauri-deno-ts/src/App.tsx b/crates/lanspread-tauri-deno-ts/src/App.tsx index c2ad511..c7af9a4 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.tsx +++ b/crates/lanspread-tauri-deno-ts/src/App.tsx @@ -209,7 +209,7 @@ const App = () => { const updateGame = async (id: string) => { console.log(`🎯 Updating game with id=${id}`); try { - const success = await invoke('install_game', { id }); + const success = await invoke('update_game', { id }); if (success) { console.log(`✅ Game update for id=${id} started...`); // update install status in gameItems for this game @@ -217,7 +217,7 @@ const App = () => { ? { ...item, install_status: InstallStatus.CheckingServer } : item)); } else { - // game is already being installed + // game is already being installed/updated console.warn(`🚧 Game with id=${id} is already being updated`); } } catch (error) {