wip
This commit is contained in:
@@ -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<RwLock<GameDB>>,
|
||||
games_in_download: Arc<RwLock<HashSet<String>>>,
|
||||
games_folder: Arc<RwLock<String>>,
|
||||
// Track backup paths for games being updated
|
||||
game_backups: Arc<RwLock<HashMap<String, PathBuf>>>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -53,6 +55,137 @@ fn install_game(id: String, state: tauri::State<LanSpreadState>) -> 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<PathBuf> {
|
||||
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<LanSpreadState>) -> 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::<LanSpreadState>()
|
||||
.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::<LanSpreadState>()
|
||||
.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::<LanSpreadState>()
|
||||
.inner()
|
||||
.game_backups
|
||||
.write()
|
||||
.await
|
||||
.remove(&id);
|
||||
|
||||
if let Some(backup_path) = backup_path {
|
||||
let games_folder = app_handle
|
||||
.state::<LanSpreadState>()
|
||||
.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}");
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user