Compare commits

..

11 Commits

Author SHA1 Message Date
ac11f91d79 games_in_download: Mutex -> RwLock 2025-08-27 21:29:55 +02:00
8e76e8d1e2 introduce cargo vet 2025-08-27 21:21:01 +02:00
3b6fc80578 [deps] cargo update 2025-08-27 19:55:56 +02:00
8c6fd139c8 [deps] cargo update 2025-08-20 08:54:56 +02:00
66572e16ce [deps] cargo update
Updating anyhow         v1.0.98  -> v1.0.99
Updating async-trait    v0.1.88  -> v0.1.89
Updating bitflags       v2.9.1   -> v2.9.2
Updating brotli         v8.0.1   -> v8.0.2
Updating cc             v1.2.32  -> v1.2.33
Updating clap_builder   v4.5.43  -> v4.5.44
Updating clap_derive    v4.5.41  -> v4.5.45
Updating clap           v4.5.43  -> v4.5.45
Updating dlopen2        v0.7.0   -> v0.8.0
Updating glob           v0.3.2   -> v0.3.3
Updating libc           v0.2.174 -> v0.2.175
Updating objc2          v0.6.1   -> v0.6.2
Updating proc-macro2    v1.0.96  -> v1.0.101
Updating reqwest        v0.12.22 -> v0.12.23
Updating serde-untagged v0.1.7   -> v0.1.8
Updating syn            v2.0.104 -> v2.0.106
Updating tao            v0.34.0  -> v0.34.1
Updating thiserror-impl v2.0.12  -> v2.0.15
Updating thiserror      v2.0.12  -> v2.0.15
Updating uuid           v1.17.0  -> v1.18.0
2025-08-17 16:37:01 +02:00
3b19cb8b18 clippy: apply and fix new lints 2025-08-17 16:35:54 +02:00
61a41c7122 clippy: add same lints to all crates 2025-08-17 16:12:42 +02:00
cbad9389ee code: remove unnecessary else branch 2025-08-17 16:08:38 +02:00
02d84c4d84 code: better debug for install state 2025-08-17 16:07:06 +02:00
ca40a62ff8 clippy/fmt: just fix applied 2025-08-17 16:04:45 +02:00
3dcc0271b8 justfile: typical cargo commands 2025-08-17 16:04:09 +02:00
13 changed files with 5010 additions and 768 deletions

594
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,14 @@ name = "lanspread-compat"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[dependencies] [dependencies]
# local # local
lanspread-db = { path = "../lanspread-db" } lanspread-db = { path = "../lanspread-db" }

View File

@@ -58,6 +58,7 @@ impl From<EtiGame> for Game {
max_players: eti_game.game_maxplayers, max_players: eti_game.game_maxplayers,
version: eti_game.game_version, version: eti_game.game_version,
genre: eti_game.genre_de, genre: eti_game.genre_de,
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
size: (eti_game.game_size * 1024.0 * 1024.0 * 1024.0) as u64, size: (eti_game.game_size * 1024.0 * 1024.0 * 1024.0) as u64,
thumbnail: None, thumbnail: None,
installed: false, installed: false,

View File

@@ -3,6 +3,14 @@ name = "lanspread-mdns"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[dependencies] [dependencies]
mdns-sd = { workspace = true } mdns-sd = { workspace = true }
eyre = { workspace = true } eyre = { workspace = true }

View File

@@ -1,3 +1,5 @@
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
use std::net::SocketAddr; use std::net::SocketAddr;
use eyre::bail; use eyre::bail;

View File

@@ -14,6 +14,15 @@ edition = "2024"
name = "lanspread_tauri_deno_ts_lib" name = "lanspread_tauri_deno_ts_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[lints.rust]
unsafe_code = "forbid"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
needless_pass_by_value = "allow"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2", features = [] } tauri-build = { version = "2", features = [] }

View File

@@ -1,3 +1,3 @@
fn main() { fn main() {
tauri_build::build() tauri_build::build();
} }

View File

@@ -1,485 +1,482 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
net::SocketAddr, net::SocketAddr,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use eyre::bail; use eyre::bail;
use lanspread_client::{ClientCommand, ClientEvent}; use lanspread_client::{ClientCommand, ClientEvent};
use lanspread_db::db::{Game, GameDB}; use lanspread_db::db::{Game, GameDB};
use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service}; use lanspread_mdns::{LANSPREAD_INSTANCE_NAME, LANSPREAD_SERVICE_TYPE, discover_service};
use tauri::{AppHandle, Emitter as _, Manager}; use tauri::{AppHandle, Emitter as _, Manager};
use tauri_plugin_shell::{ShellExt, process::Command}; use tauri_plugin_shell::{ShellExt, process::Command};
use tokio::sync::{Mutex, RwLock, mpsc::UnboundedSender}; use tokio::sync::{RwLock, mpsc::UnboundedSender};
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
struct LanSpreadState { struct LanSpreadState {
server_addr: RwLock<Option<SocketAddr>>, server_addr: RwLock<Option<SocketAddr>>,
client_ctrl: UnboundedSender<ClientCommand>, client_ctrl: UnboundedSender<ClientCommand>,
games: Arc<RwLock<GameDB>>, games: Arc<RwLock<GameDB>>,
games_in_download: Arc<Mutex<HashSet<String>>>, games_in_download: Arc<RwLock<HashSet<String>>>,
games_folder: Arc<RwLock<String>>, games_folder: Arc<RwLock<String>>,
} }
#[tauri::command] #[tauri::command]
fn request_games(state: tauri::State<LanSpreadState>) { fn request_games(state: tauri::State<LanSpreadState>) {
log::debug!("request_games"); log::debug!("request_games");
if let Err(e) = state.inner().client_ctrl.send(ClientCommand::ListGames) { if let Err(e) = state.inner().client_ctrl.send(ClientCommand::ListGames) {
log::error!("Failed to send message to client: {e:?}"); log::error!("Failed to send message to client: {e:?}");
} }
} }
#[tauri::command] #[tauri::command]
fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool { fn install_game(id: String, state: tauri::State<LanSpreadState>) -> bool {
let already_in_download = tauri::async_runtime::block_on(async { let already_in_download = tauri::async_runtime::block_on(async {
if state.inner().games_in_download.lock().await.contains(&id) { if state.inner().games_in_download.read().await.contains(&id) {
log::warn!("Game is already downloading: {id}"); log::warn!("Game is already downloading: {id}");
return true; return true;
} }
false false
}); });
if already_in_download { if already_in_download {
return false; return false;
} }
if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) { if let Err(e) = state.inner().client_ctrl.send(ClientCommand::GetGame(id)) {
log::error!("Failed to send message to client: {e:?}"); log::error!("Failed to send message to client: {e:?}");
} }
true 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};
use windows::{Win32::UI::Shell::ShellExecuteW, core::PCWSTR}; use windows::{Win32::UI::Shell::ShellExecuteW, core::PCWSTR};
let file_wide: Vec<u16> = OsStr::new(file).encode_wide().chain(Some(0)).collect(); let file_wide: Vec<u16> = OsStr::new(file).encode_wide().chain(Some(0)).collect();
let params_wide: Vec<u16> = OsStr::new(params).encode_wide().chain(Some(0)).collect(); let params_wide: Vec<u16> = OsStr::new(params).encode_wide().chain(Some(0)).collect();
let dir_wide: Vec<u16> = OsStr::new(dir).encode_wide().chain(Some(0)).collect(); let dir_wide: Vec<u16> = OsStr::new(dir).encode_wide().chain(Some(0)).collect();
let runas_wide: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect(); let runas_wide: Vec<u16> = OsStr::new("runas").encode_wide().chain(Some(0)).collect();
let result = unsafe { let result = unsafe {
ShellExecuteW( ShellExecuteW(
None, None,
PCWSTR::from_raw(runas_wide.as_ptr()), PCWSTR::from_raw(runas_wide.as_ptr()),
PCWSTR::from_raw(file_wide.as_ptr()), PCWSTR::from_raw(file_wide.as_ptr()),
PCWSTR::from_raw(params_wide.as_ptr()), PCWSTR::from_raw(params_wide.as_ptr()),
PCWSTR::from_raw(dir_wide.as_ptr()), PCWSTR::from_raw(dir_wide.as_ptr()),
windows::Win32::UI::WindowsAndMessaging::SW_HIDE, windows::Win32::UI::WindowsAndMessaging::SW_HIDE,
) )
}; };
(result.0 as usize) > 32 // Success if greater than 32 (result.0 as usize) > 32 // Success if greater than 32
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn run_game_windows(id: String, state: tauri::State<LanSpreadState>) { fn run_game_windows(id: String, state: tauri::State<LanSpreadState>) {
use std::fs::File; use std::fs::File;
const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done"; const FIRST_START_DONE_FILE: &str = ".softlan_first_start_done";
let games_folder = let games_folder =
tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() }); tauri::async_runtime::block_on(async { state.inner().games_folder.read().await.clone() });
let games_folder = PathBuf::from(games_folder); let games_folder = PathBuf::from(games_folder);
if !games_folder.exists() { if !games_folder.exists() {
log::error!("games_folder {games_folder:?} does not exist"); log::error!("games_folder {games_folder:?} does not exist");
return; return;
} }
let game_path = games_folder.join(id.clone()); let game_path = games_folder.join(id.clone());
let game_setup_bin = game_path.join("game_setup.cmd"); let game_setup_bin = game_path.join("game_setup.cmd");
let game_start_bin = game_path.join("game_start.cmd"); let game_start_bin = game_path.join("game_start.cmd");
let first_start_done_file = game_path.join(FIRST_START_DONE_FILE); let first_start_done_file = game_path.join(FIRST_START_DONE_FILE);
if !first_start_done_file.exists() && game_setup_bin.exists() { if !first_start_done_file.exists() && game_setup_bin.exists() {
let result = run_as_admin( let result = run_as_admin(
"cmd.exe", "cmd.exe",
&format!( &format!(
r#"/c "{} local {} de playername""#, r#"/c "{} local {} de playername""#,
game_setup_bin.display(), game_setup_bin.display(),
&id &id
), ),
&game_path.display().to_string(), &game_path.display().to_string(),
); );
if !result { if !result {
log::error!("failed to run game_setup.cmd"); log::error!("failed to run game_setup.cmd");
return; return;
} }
if let Err(e) = File::create(&first_start_done_file) { if let Err(e) = File::create(&first_start_done_file) {
log::error!("failed to create {first_start_done_file:?}: {e}"); log::error!("failed to create {first_start_done_file:?}: {e}");
} }
} }
if game_start_bin.exists() { if game_start_bin.exists() {
let result = run_as_admin( let result = run_as_admin(
"cmd.exe", "cmd.exe",
&format!( &format!(
r#"/c "{} local {} de playername""#, r#"/c "{} local {} de playername""#,
game_start_bin.display(), game_start_bin.display(),
&id &id
), ),
&game_path.display().to_string(), &game_path.display().to_string(),
); );
if !result { if !result {
log::error!("failed to run game_start.cmd"); log::error!("failed to run game_start.cmd");
} }
} }
} }
#[tauri::command] #[tauri::command]
fn run_game(id: String, state: tauri::State<LanSpreadState>) { fn run_game(id: String, state: tauri::State<LanSpreadState>) {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
run_game_windows(id, state); run_game_windows(id, state);
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
{ {
let _ = state; let _ = state;
log::error!("run_game not implemented for this platform: id={id}"); log::error!("run_game not implemented for this platform: id={id}");
} }
} }
fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) { fn set_game_install_state_from_path(game_db: &mut GameDB, path: &Path, installed: bool) {
if let Some(file_name) = path.file_name() { if let Some(file_name) = path.file_name()
if let Some(file_name) = file_name.to_str() { && let Some(file_name) = file_name.to_str()
if let Some(game) = game_db.get_mut_game_by_id(file_name) { && let Some(game) = game_db.get_mut_game_by_id(file_name)
if installed { {
log::debug!("Game is installed: {game}"); if installed {
} else { log::debug!("Set {game} to installed");
log::error!("Game is missing: {game}"); } else {
} log::debug!("Set {game} to uninstalled");
game.installed = installed; }
} game.installed = installed;
} }
} }
}
#[tauri::command]
#[tauri::command] fn update_game_directory(app_handle: tauri::AppHandle, path: String) {
fn update_game_directory(app_handle: tauri::AppHandle, path: String) { log::info!("update_game_directory: {path}");
log::info!("update_game_directory: {path}");
app_handle
app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .client_ctrl
.client_ctrl .send(ClientCommand::SetGameDir(path.clone()))
.send(ClientCommand::SetGameDir(path.clone())) .expect("Failed to send ClientCommand: SetGameDir");
.unwrap();
{
{ tauri::async_runtime::block_on(async {
tauri::async_runtime::block_on(async { let mut games_folder = app_handle
let mut games_folder = app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .inner()
.inner() .games_folder
.games_folder .write()
.write() .await;
.await;
games_folder.clone_from(&path);
*games_folder = path.clone(); });
}); }
}
let path = PathBuf::from(path);
let path = PathBuf::from(path); if !path.exists() {
if !path.exists() { log::error!("game dir {} does not exist", path.display());
log::error!("game dir {path:?} does not exist"); }
}
let entries = match path.read_dir() {
let entries = match path.read_dir() { Ok(entries) => entries,
Ok(entries) => entries, Err(e) => {
Err(e) => { log::error!("Failed to read game dir: {e}");
log::error!("Failed to read game dir: {e}"); return;
return; }
} };
};
tauri::async_runtime::spawn(async move {
tauri::async_runtime::spawn(async move { let mut game_db = app_handle
let mut game_db = app_handle .state::<LanSpreadState>()
.state::<LanSpreadState>() .inner()
.inner() .games
.games .write()
.write() .await;
.await;
// Reset all games to uninstalled
// Reset all games to uninstalled game_db.set_all_uninstalled();
game_db.set_all_uninstalled();
// update game_db with installed games from real game directory
// update game_db with installed games from real game directory entries.into_iter().for_each(|entry| {
entries.into_iter().for_each(|entry| { if let Ok(entry) = entry
if let Ok(entry) = entry { && let Ok(path_type) = entry.file_type()
if let Ok(path_type) = entry.file_type() { && path_type.is_dir()
if path_type.is_dir() { {
let path = entry.path(); let path = entry.path();
if path.join("version.ini").exists() { if path.join("version.ini").exists() {
set_game_install_state_from_path(&mut game_db, &path, true); set_game_install_state_from_path(&mut game_db, &path, true);
} }
} }
} });
}
}); if let Err(e) = app_handle.emit("games-list-updated", Some(game_db.all_games())) {
log::error!("Failed to emit games-list-updated event: {e}");
if let Err(e) = app_handle.emit("games-list-updated", Some(game_db.all_games())) { }
log::error!("Failed to emit games-list-updated event: {e}"); });
} }
});
} async fn find_server(app: AppHandle) {
log::info!("Looking for server...");
async fn find_server(app: AppHandle) {
log::info!("Looking for server..."); loop {
match discover_service(LANSPREAD_SERVICE_TYPE, Some(LANSPREAD_INSTANCE_NAME)) {
loop { Ok(server_addr) => {
match discover_service(LANSPREAD_SERVICE_TYPE, Some(LANSPREAD_INSTANCE_NAME)) { log::info!("Found server at {server_addr}");
Ok(server_addr) => { let state: tauri::State<LanSpreadState> = app.state();
log::info!("Found server at {server_addr}"); *state.server_addr.write().await = Some(server_addr);
let state: tauri::State<LanSpreadState> = app.state(); state
*state.server_addr.write().await = Some(server_addr); .client_ctrl
state .send(ClientCommand::ServerAddr(server_addr))
.client_ctrl .expect("Failed to send ClientCommand: ServerAddr");
.send(ClientCommand::ServerAddr(server_addr)) request_games(state);
.unwrap(); break;
request_games(state); }
break; Err(e) => {
} log::warn!("Failed to find server: {e} - retrying...");
Err(e) => { }
log::warn!("Failed to find server: {e} - retrying..."); }
} }
} }
}
} async fn update_game_db(games: Vec<Game>, app: AppHandle) {
for game in &games {
async fn update_game_db(games: Vec<Game>, app: AppHandle) { log::trace!("client event ListGames iter: {game:?}");
for game in &games { }
log::trace!("client event ListGames iter: {game:?}");
} let state = app.state::<LanSpreadState>();
let state = app.state::<LanSpreadState>(); // Store games list
*state.games.write().await = GameDB::from(games.clone());
// Store games list
*state.games.write().await = GameDB::from(games.clone()); // Tell Frontend about new games list
if let Err(e) = app.emit("games-list-updated", Some(games)) {
// Tell Frontend about new games list log::error!("Failed to emit games-list-updated event: {e}");
if let Err(e) = app.emit("games-list-updated", Some(games)) { } else {
log::error!("Failed to emit games-list-updated event: {e}"); log::info!("Emitted games-list-updated event");
} else { }
log::info!("Emitted games-list-updated event"); }
}
} fn add_final_slash(path: &str) -> String {
#[cfg(target_os = "windows")]
fn add_final_slash(path: &str) -> String { const SLASH_CHAR: char = '\\';
#[cfg(target_os = "windows")]
const SLASH_CHAR: char = '\\'; #[cfg(not(target_os = "windows"))]
const SLASH_CHAR: char = '/';
#[cfg(not(target_os = "windows"))]
const SLASH_CHAR: char = '/'; if path.ends_with(SLASH_CHAR) {
path.to_string()
if path.ends_with(SLASH_CHAR) { } else {
path.to_string() format!("{path}{SLASH_CHAR}")
} else { }
format!("{path}{SLASH_CHAR}") }
}
} async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::Result<()> {
if let Ok(()) = std::fs::create_dir_all(dest_dir) {
async fn do_unrar(sidecar: Command, rar_file: &Path, dest_dir: &Path) -> eyre::Result<()> { if let Ok(rar_file) = rar_file.canonicalize() {
if let Ok(()) = std::fs::create_dir_all(dest_dir) { if let Ok(dest_dir) = dest_dir.canonicalize() {
if let Ok(rar_file) = rar_file.canonicalize() { let dest_dir = dest_dir
if let Ok(dest_dir) = dest_dir.canonicalize() { .to_str()
let dest_dir = dest_dir .ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?;
.to_str()
.ok_or_else(|| eyre::eyre!("failed to get str of dest_dir"))?; log::info!(
"unrar game: {} to {}",
log::info!( rar_file.canonicalize()?.display(),
"unrar game: {} to {}", dest_dir
rar_file.canonicalize()?.display(), );
dest_dir
); let out = sidecar
.arg("x") // extract files
let out = sidecar .arg(rar_file.canonicalize()?)
.arg("x") // extract files .arg("-y") // Assume Yes on all queries
.arg(rar_file.canonicalize()?) .arg("-o") // Set overwrite mode
.arg("-y") // Assume Yes on all queries .arg(add_final_slash(dest_dir))
.arg("-o") // Set overwrite mode .output()
.arg(add_final_slash(dest_dir)) .await?;
.output()
.await?; if !out.status.success() {
log::error!("unrar stderr: {}", String::from_utf8_lossy(&out.stderr));
if !out.status.success() { }
log::error!("unrar stderr: {}", String::from_utf8_lossy(&out.stderr));
} return Ok(());
}
return Ok(()); log::error!("dest_dir canonicalize failed: {}", dest_dir.display());
} else { } else {
log::error!("dest_dir canonicalize failed: {:?}", &dest_dir); log::error!("rar_file canonicalize failed: {}", rar_file.display());
} }
} else { }
log::error!("rar_file canonicalize failed: {:?}", &rar_file);
} bail!("failed to create directory: {dest_dir:?}");
} else { }
log::error!("failed to create dest_dir: {:?}", &dest_dir);
} async fn unpack_game(id: &str, sidecar: Command, games_folder: String) {
let game_path = PathBuf::from(games_folder).join(id);
bail!("failed to create directory: {dest_dir:?}"); let eti_rar = game_path.join(format!("{id}.eti"));
} let local_path = game_path.join("local");
async fn unpack_game(id: &str, sidecar: Command, games_folder: String) { if let Err(e) = do_unrar(sidecar, &eti_rar, &local_path).await {
let game_path = PathBuf::from(games_folder).join(id); log::error!("{} -> {}: {e}", eti_rar.display(), local_path.display());
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 { #[allow(clippy::too_many_lines)]
log::error!("{eti_rar:?} -> {local_path:?}: {e}"); #[allow(clippy::missing_panics_doc)]
} #[cfg_attr(mobile, tauri::mobile_entry_point)]
} pub fn run() {
let tauri_logger_builder = tauri_plugin_log::Builder::new()
#[cfg_attr(mobile, tauri::mobile_entry_point)] .clear_targets()
pub fn run() { .target(tauri_plugin_log::Target::new(
let tauri_logger_builder = tauri_plugin_log::Builder::new() tauri_plugin_log::TargetKind::Stdout,
.clear_targets() ))
.target(tauri_plugin_log::Target::new( .level(log::LevelFilter::Info)
tauri_plugin_log::TargetKind::Stdout, .level_for("mdns_sd::service_daemon", log::LevelFilter::Off);
))
.level(log::LevelFilter::Info) // channel to pass commands to the client
.level_for("mdns_sd::service_daemon", log::LevelFilter::Off); let (tx_client_control, rx_client_control) =
tokio::sync::mpsc::unbounded_channel::<ClientCommand>();
// channel to pass commands to the client
let (tx_client_control, rx_client_control) = // channel to receive events from the client
tokio::sync::mpsc::unbounded_channel::<ClientCommand>(); let (tx_client_event, mut rx_client_event) =
tokio::sync::mpsc::unbounded_channel::<ClientEvent>();
// channel to receive events from the client
let (tx_client_event, mut rx_client_event) = let lanspread_state = LanSpreadState {
tokio::sync::mpsc::unbounded_channel::<ClientEvent>(); server_addr: RwLock::new(None),
client_ctrl: tx_client_control,
let lanspread_state = LanSpreadState { games: Arc::new(RwLock::new(GameDB::empty())),
server_addr: RwLock::new(None), games_in_download: Arc::new(RwLock::new(HashSet::new())),
client_ctrl: tx_client_control, games_folder: Arc::new(RwLock::new(String::new())),
games: Arc::new(RwLock::new(GameDB::empty())), };
games_in_download: Arc::new(Mutex::new(HashSet::new())),
games_folder: Arc::new(RwLock::new("".to_string())), tauri::Builder::default()
}; .plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
tauri::Builder::default() .plugin(tauri_logger_builder.build())
.plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_dialog::init()) .invoke_handler(tauri::generate_handler![
.plugin(tauri_logger_builder.build()) request_games,
.plugin(tauri_plugin_shell::init()) install_game,
.invoke_handler(tauri::generate_handler![ run_game,
request_games, update_game_directory
install_game, ])
run_game, .manage(lanspread_state)
update_game_directory .setup(|app| {
]) let app_handle = app.handle().clone();
.manage(lanspread_state) // discover server
.setup(|app| { tauri::async_runtime::spawn(async move { find_server(app_handle).await });
let app_handle = app.handle().clone(); tauri::async_runtime::spawn(async move {
// discover server lanspread_client::run(rx_client_control, tx_client_event).await
tauri::async_runtime::spawn(async move { find_server(app_handle).await }); });
tauri::async_runtime::spawn(async move {
lanspread_client::run(rx_client_control, tx_client_event).await let app_handle = app.handle().clone();
}); tauri::async_runtime::spawn(async move {
while let Some(event) = rx_client_event.recv().await {
let app_handle = app.handle().clone(); match event {
tauri::async_runtime::spawn(async move { ClientEvent::ListGames(games) => {
while let Some(event) = rx_client_event.recv().await { log::info!("ClientEvent::ListGames received");
match event { update_game_db(games, app_handle.clone()).await;
ClientEvent::ListGames(games) => { }
log::info!("ClientEvent::ListGames received"); ClientEvent::GotGameFiles { id, file_descriptions } => {
update_game_db(games, app_handle.clone()).await; log::info!("ClientEvent::GotGameFiles received");
}
ClientEvent::GotGameFiles { id, file_descriptions } => { if let Err(e) = app_handle.emit(
log::info!("ClientEvent::GotGameFiles received"); "game-download-pre",
Some(id.clone()),
if let Err(e) = app_handle.emit( ) {
"game-download-pre", log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}");
Some(id.clone()), }
) {
log::error!("ClientEvent::GotGameFiles: Failed to emit game-download-pre event: {e}"); app_handle
} .state::<LanSpreadState>()
.inner()
app_handle .client_ctrl
.state::<LanSpreadState>() .send(ClientCommand::DownloadGameFiles{
.inner() id,
.client_ctrl file_descriptions,
.send(ClientCommand::DownloadGameFiles{ })
id, .expect("Failed to send ClientCommand: DownloadGameFiles");
file_descriptions,
}) }
.unwrap(); ClientEvent::DownloadGameFilesBegin { id } => {
log::info!("ClientEvent::DownloadGameFilesBegin received");
}
ClientEvent::DownloadGameFilesBegin { id } => { app_handle
log::info!("ClientEvent::DownloadGameFilesBegin received"); .state::<LanSpreadState>()
.inner()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .insert(id.clone());
.lock()
.await if let Err(e) = app_handle.emit("game-download-begin", Some(id)) {
.insert(id.clone()); log::error!("ClientEvent::DownloadGameFilesBegin: Failed to emit game-download-begin event: {e}");
}
if let Err(e) = app_handle.emit("game-download-begin", Some(id)) { }
log::error!("ClientEvent::DownloadGameFilesBegin: Failed to emit game-download-begin event: {e}"); ClientEvent::DownloadGameFilesFinished { id } => {
} log::info!("ClientEvent::DownloadGameFilesFinished received");
} if let Err(e) = app_handle.emit("game-download-finished", Some(id.clone())) {
ClientEvent::DownloadGameFilesFinished { id } => { log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}");
log::info!("ClientEvent::DownloadGameFilesFinished received"); }
if let Err(e) = app_handle.emit("game-download-finished", Some(id.clone())) {
log::error!("ClientEvent::DownloadGameFilesFinished: Failed to emit game-download-finished event: {e}"); app_handle
} .state::<LanSpreadState>()
.inner()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .remove(&id.clone());
.lock()
.await
.remove(&id.clone()); let games_folder = app_handle
.state::<LanSpreadState>()
.inner()
let games_folder = app_handle .games_folder
.state::<LanSpreadState>() .read()
.inner() .await
.games_folder .clone();
.read()
.await if let Ok(sidecar) = app_handle.shell().sidecar("unrar") {
.clone(); unpack_game(&id, sidecar, games_folder).await;
if let Ok(sidecar) = app_handle.shell().sidecar("unrar") { log::info!("ClientEvent::UnpackGameFinished received");
unpack_game(&id, sidecar, games_folder).await; 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}");
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}"); }
} ClientEvent::DownloadGameFilesFailed { id } => {
} log::warn!("ClientEvent::DownloadGameFilesFailed received");
}
ClientEvent::DownloadGameFilesFailed { id } => { if let Err(e) = app_handle.emit("game-download-failed", Some(id.clone())) {
log::warn!("ClientEvent::DownloadGameFilesFailed received"); log::error!("Failed to emit game-download-failed event: {e}");
}
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()
app_handle .games_in_download
.state::<LanSpreadState>() .write()
.inner() .await
.games_in_download .remove(&id.clone());
.lock() },
.await }
.remove(&id.clone()); }
}, });
}
} Ok(())
}); })
.run(tauri::generate_context!())
Ok(()) .expect("error while running tauri application");
}) }
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -7,5 +7,5 @@ use mimalloc::MiMalloc;
static GLOBAL: MiMalloc = MiMalloc; static GLOBAL: MiMalloc = MiMalloc;
fn main() { fn main() {
lanspread_tauri_deno_ts_lib::run() lanspread_tauri_deno_ts_lib::run();
} }

View File

@@ -6,5 +6,17 @@ server:
client: client:
cargo tauri dev cargo tauri dev
fmt:
cargo +nightly fmt
_fix:
cargo fix
cargo clippy --fix
fix: _fix fmt
clippy:
cargo clippy
clean: clean:
cargo clean cargo clean

7
supply-chain/audits.toml Normal file
View File

@@ -0,0 +1,7 @@
# cargo-vet audits file
[[audits.windows-link]]
who = "ddidderr <ddidderr@paul.network>"
criteria = "safe-to-deploy"
delta = "0.1.1 -> 0.1.3"

2184
supply-chain/config.toml Normal file

File diff suppressed because it is too large Load Diff

1982
supply-chain/imports.lock Normal file

File diff suppressed because it is too large Load Diff