wip
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::{HashMap, HashSet},
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -21,6 +21,8 @@ struct LanSpreadState {
|
|||||||
games: Arc<RwLock<GameDB>>,
|
games: Arc<RwLock<GameDB>>,
|
||||||
games_in_download: Arc<RwLock<HashSet<String>>>,
|
games_in_download: Arc<RwLock<HashSet<String>>>,
|
||||||
games_folder: Arc<RwLock<String>>,
|
games_folder: Arc<RwLock<String>>,
|
||||||
|
// Track backup paths for games being updated
|
||||||
|
game_backups: Arc<RwLock<HashMap<String, PathBuf>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -53,6 +55,137 @@ fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
|
|||||||
true
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn run_as_admin(file: &str, params: &str, dir: &str) -> bool {
|
fn run_as_admin(file: &str, params: &str, dir: &str) -> bool {
|
||||||
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
|
use std::{ffi::OsStr, os::windows::ffi::OsStrExt};
|
||||||
@@ -373,6 +506,7 @@ pub fn run() {
|
|||||||
games: Arc::new(RwLock::new(GameDB::empty())),
|
games: Arc::new(RwLock::new(GameDB::empty())),
|
||||||
games_in_download: Arc::new(RwLock::new(HashSet::new())),
|
games_in_download: Arc::new(RwLock::new(HashSet::new())),
|
||||||
games_folder: Arc::new(RwLock::new(String::new())),
|
games_folder: Arc::new(RwLock::new(String::new())),
|
||||||
|
game_backups: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
||||||
};
|
};
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -384,7 +518,8 @@ pub fn run() {
|
|||||||
request_games,
|
request_games,
|
||||||
install_game,
|
install_game,
|
||||||
run_game,
|
run_game,
|
||||||
update_game_directory
|
update_game_directory,
|
||||||
|
update_game
|
||||||
])
|
])
|
||||||
.manage(lanspread_state)
|
.manage(lanspread_state)
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
@@ -453,7 +588,6 @@ pub fn run() {
|
|||||||
.await
|
.await
|
||||||
.remove(&id.clone());
|
.remove(&id.clone());
|
||||||
|
|
||||||
|
|
||||||
let games_folder = app_handle
|
let games_folder = app_handle
|
||||||
.state::<LanSpreadState>()
|
.state::<LanSpreadState>()
|
||||||
.inner()
|
.inner()
|
||||||
@@ -463,12 +597,33 @@ pub fn run() {
|
|||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let Ok(sidecar) = app_handle.shell().sidecar("unrar") {
|
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");
|
// Spawn a separate task to handle unpacking and backup cleanup
|
||||||
if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) {
|
tauri::async_runtime::spawn(async move {
|
||||||
log::error!("ClientEvent::UnpackGameFinished: Failed to emit game-unpack-finished event: {e}");
|
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 } => {
|
ClientEvent::DownloadGameFilesFailed { id } => {
|
||||||
@@ -485,6 +640,30 @@ pub fn run() {
|
|||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.remove(&id.clone());
|
.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) => {
|
const updateGame = async (id: string) => {
|
||||||
console.log(`🎯 Updating game with id=${id}`);
|
console.log(`🎯 Updating game with id=${id}`);
|
||||||
try {
|
try {
|
||||||
const success = await invoke('install_game', { id });
|
const success = await invoke('update_game', { id });
|
||||||
if (success) {
|
if (success) {
|
||||||
console.log(`✅ Game update for id=${id} started...`);
|
console.log(`✅ Game update for id=${id} started...`);
|
||||||
// update install status in gameItems for this game
|
// update install status in gameItems for this game
|
||||||
@@ -217,7 +217,7 @@ const App = () => {
|
|||||||
? { ...item, install_status: InstallStatus.CheckingServer }
|
? { ...item, install_status: InstallStatus.CheckingServer }
|
||||||
: item));
|
: item));
|
||||||
} else {
|
} else {
|
||||||
// game is already being installed
|
// game is already being installed/updated
|
||||||
console.warn(`🚧 Game with id=${id} is already being updated`);
|
console.warn(`🚧 Game with id=${id} is already being updated`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user