[code][fix] improvements for LAN 202503

- more robust client <-> server connection
  - new client event: DownloadGameFilesFailed
  - 3 seconds to reconnect
  - retry forever if server is gone and never lose a UI request

- code cleanup here and there (mostly server)
This commit is contained in:
2025-03-20 19:39:32 +01:00
parent 19434cd1b1
commit 765447e6d1
9 changed files with 405 additions and 239 deletions

View File

@ -21,7 +21,7 @@ struct LanSpreadState {
client_ctrl: UnboundedSender<ClientCommand>,
games: Arc<Mutex<GameDB>>,
games_in_download: Arc<Mutex<HashSet<String>>>,
games_dir: Arc<Mutex<String>>,
games_folder: Arc<Mutex<String>>,
}
#[tauri::command]
@ -35,11 +35,9 @@ fn request_games(state: tauri::State<LanSpreadState>) {
#[tauri::command]
fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
log::error!("Running game with id {id}");
let already_in_download = tauri::async_runtime::block_on(async {
if state.inner().games_in_download.lock().await.contains(&id) {
log::error!("Game is already downloading: {id}");
log::warn!("Game is already downloading: {id}");
return true;
}
false
@ -62,16 +60,16 @@ fn run_game(id: String, state: tauri::State<LanSpreadState>) {
log::error!("run_game {id}");
let games_dir =
tauri::async_runtime::block_on(async { state.inner().games_dir.lock().await.clone() });
let games_folder =
tauri::async_runtime::block_on(async { state.inner().games_folder.lock().await.clone() });
let games_dir = PathBuf::from(games_dir);
if !games_dir.exists() {
log::error!("games_dir {games_dir:?} does not exist");
let games_folder = PathBuf::from(games_folder);
if !games_folder.exists() {
log::error!("games_folder {games_folder:?} does not exist");
return;
}
let game_path = games_dir.join(id);
let game_path = games_folder.join(id);
let game_setup_bin = game_path.join("game_setup.cmd");
let game_start_bin = game_path.join("game_start.cmd");
@ -84,7 +82,7 @@ fn run_game(id: String, state: tauri::State<LanSpreadState>) {
{
log::error!("failed to run game_setup.cmd: {e}");
return;
} else if let Err(e) = std::fs::File::create(FIRST_START_DONE_FILE) {
} else if let Err(e) = File::create(FIRST_START_DONE_FILE) {
log::error!("failed to create {first_start_done_file:?}: {e}");
}
}
@ -104,7 +102,7 @@ fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed
if let Some(file_name) = file_name.to_str() {
if let Some(game) = game_db.get_mut_game_by_id(file_name) {
if installed {
log::info!("Game is installed: {game}");
log::debug!("Game is installed: {game}");
} else {
log::error!("Game is missing: {game}");
}
@ -126,14 +124,14 @@ fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
{
tauri::async_runtime::block_on(async {
let mut games_dir = app_handle
let mut games_folder = app_handle
.state::<LanSpreadState>()
.inner()
.games_dir
.games_folder
.lock()
.await;
*games_dir = path.clone();
*games_folder = path.clone();
});
}
@ -167,7 +165,7 @@ fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
if let Ok(path_type) = entry.file_type() {
if path_type.is_dir() {
let path = entry.path();
if path.join(".softlan_game_installed").exists() {
if path.join("version.ini").exists() {
set_game_install_state_from_path(&mut game_db, &path, true);
}
}
@ -249,9 +247,13 @@ async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::R
.to_str()
.ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?;
log::error!("SIDECARE: {:?}", &sidecar);
log::info!(
"unrar game: {} to {}",
rar_file.canonicalize()?.display(),
dest_dir
);
sidecar
let out = sidecar
.arg("x") // extract files
.arg(rar_file.canonicalize()?)
.arg("-y") // Assume Yes on all queries
@ -260,6 +262,10 @@ async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::R
.output()
.await?;
if !out.status.success() {
log::error!("unrar stderr: {}", String::from_utf8_lossy(&out.stderr));
}
return Ok(());
} else {
log::error!("dest_dir canonicalize failed: {:?}", &dest_dir);
@ -274,20 +280,13 @@ async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::R
bail!("failed to create directory: {dest_dir:?}");
}
async fn unpack_game(id: &str, sidecar: Command, games_dir: String) {
let game_path = PathBuf::from(games_dir).join(id);
async fn unpack_game(id: &str, sidecar: Command, games_folder: String) {
let game_path = PathBuf::from(games_folder).join(id);
let eti_rar = game_path.join(format!("{id}.eti"));
let local_path = game_path.join("local");
if let Err(e) = do_unrar(sidecar, &eti_rar, &local_path).await {
log::error!("{eti_rar:?} -> {local_path:?}: {e}");
} else {
let game_installed_file = game_path.join(".softlan_game_installed");
if let Err(e) = File::create(game_installed_file) {
log::error!("failed to create game_installed_file: {e}");
} else {
log::info!("game unpacked: {id}");
}
}
}
@ -299,8 +298,6 @@ pub fn run() {
tauri_plugin_log::TargetKind::Stdout,
))
.level(log::LevelFilter::Info)
.level_for("lanspread_client", log::LevelFilter::Debug)
.level_for("lanspread_tauri_leptos_lib", log::LevelFilter::Debug)
.level_for("mdns_sd::service_daemon", log::LevelFilter::Off);
// channel to pass commands to the client
@ -316,7 +313,7 @@ pub fn run() {
client_ctrl: tx_client_control,
games: Arc::new(Mutex::new(GameDB::empty())),
games_in_download: Arc::new(Mutex::new(HashSet::new())),
games_dir: Arc::new(Mutex::new("".to_string())),
games_folder: Arc::new(Mutex::new("".to_string())),
};
tauri::Builder::default()
@ -347,13 +344,12 @@ pub fn run() {
log::info!("ClientEvent::ListGames received");
update_game_db(games, app_handle.clone()).await;
}
ClientEvent::GotGameFiles(game_file_descs) => {
ClientEvent::GotGameFiles { id, file_descriptions } => {
log::info!("ClientEvent::GotGameFiles received");
if let Some(first_desc_file) = game_file_descs.first() {
if let Err(e) = app_handle.emit(
"game-download-pre",
Some(first_desc_file.game_id.clone()),
Some(id.clone()),
) {
log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}");
}
@ -362,11 +358,12 @@ pub fn run() {
.state::<LanSpreadState>()
.inner()
.client_ctrl
.send(ClientCommand::DownloadGameFiles(game_file_descs))
.send(ClientCommand::DownloadGameFiles{
id,
file_descriptions,
})
.unwrap();
} else {
log::error!("ClientEvent::GotGameFiles: Got empty game files list");
}
}
ClientEvent::DownloadGameFilesBegin { id } => {
log::info!("ClientEvent::DownloadGameFilesBegin received");
@ -398,16 +395,16 @@ pub fn run() {
.remove(&id.clone());
let games_dir = app_handle
let games_folder = app_handle
.state::<LanSpreadState>()
.inner()
.games_dir
.games_folder
.lock()
.await
.clone();
if let Ok(sidecar) = app_handle.shell().sidecar("unrar") {
unpack_game(&id, sidecar, games_dir).await;
unpack_game(&id, sidecar, games_folder).await;
log::info!("ClientEvent::UnpackGameFinished received");
if let Err(e) = app_handle.emit("game-unpack-finished", Some(id.clone())) {
@ -415,6 +412,21 @@ pub fn run() {
}
}
}
ClientEvent::DownloadGameFilesFailed { id } => {
log::warn!("ClientEvent::DownloadGameFilesFailed received");
if let Err(e) = app_handle.emit("game-download-failed", Some(id.clone())) {
log::error!("Failed to emit game-download-failed event: {e}");
}
app_handle
.state::<LanSpreadState>()
.inner()
.games_in_download
.lock()
.await
.remove(&id.clone());
},
}
}
});