load game.db

This commit is contained in:
2025-11-12 22:56:59 +01:00
parent 379a1bbfe9
commit 0f4e40383b
7 changed files with 117 additions and 27 deletions
Generated
+2
View File
@@ -2205,6 +2205,7 @@ dependencies = [
"bytes", "bytes",
"eyre", "eyre",
"gethostname", "gethostname",
"lanspread-compat",
"lanspread-db", "lanspread-db",
"lanspread-mdns", "lanspread-mdns",
"lanspread-proto", "lanspread-proto",
@@ -2233,6 +2234,7 @@ name = "lanspread-tauri-deno-ts"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"lanspread-compat",
"lanspread-db", "lanspread-db",
"lanspread-mdns", "lanspread-mdns",
"lanspread-peer", "lanspread-peer",
+1
View File
@@ -13,6 +13,7 @@ unwrap_used = "warn"
[dependencies] [dependencies]
# local # local
lanspread-compat = { path = "../lanspread-compat" }
lanspread-db = { path = "../lanspread-db" } lanspread-db = { path = "../lanspread-db" }
lanspread-mdns = { path = "../lanspread-mdns" } lanspread-mdns = { path = "../lanspread-mdns" }
lanspread-proto = { path = "../lanspread-proto" } lanspread-proto = { path = "../lanspread-proto" }
+69 -8
View File
@@ -12,6 +12,7 @@ use std::{
}; };
use bytes::BytesMut; use bytes::BytesMut;
use lanspread_compat::eti::EtiGame;
use lanspread_db::db::{Game, GameDB, GameFileDescription}; use lanspread_db::db::{Game, GameDB, GameFileDescription};
use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, discover_service}; use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, discover_service};
use lanspread_proto::{Message, Request, Response}; use lanspread_proto::{Message, Request, Response};
@@ -23,6 +24,9 @@ use s2n_quic::{
provider::limits::Limits, provider::limits::Limits,
stream::BidirectionalStream, stream::BidirectionalStream,
}; };
/// Handle for loading ETI games from Tauri resources
pub type EtiGamesLoader = Arc<dyn Fn() -> eyre::Result<Vec<EtiGame>> + Send + Sync>;
use tokio::{ use tokio::{
fs::OpenOptions, fs::OpenOptions,
io::{AsyncSeekExt, AsyncWriteExt}, io::{AsyncSeekExt, AsyncWriteExt},
@@ -82,6 +86,15 @@ impl From<eyre::Report> for PeerError {
pub fn start_peer( pub fn start_peer(
game_dir: String, game_dir: String,
tx_notify_ui: UnboundedSender<PeerEvent>, tx_notify_ui: UnboundedSender<PeerEvent>,
) -> eyre::Result<UnboundedSender<PeerCommand>> {
start_peer_with_eti_loader(game_dir, tx_notify_ui, None)
}
/// Initialize and start the peer system with ETI games loader
pub fn start_peer_with_eti_loader(
game_dir: String,
tx_notify_ui: UnboundedSender<PeerEvent>,
eti_games_loader: Option<EtiGamesLoader>,
) -> eyre::Result<UnboundedSender<PeerCommand>> { ) -> eyre::Result<UnboundedSender<PeerCommand>> {
log::info!("Starting peer system with game directory: {game_dir}"); log::info!("Starting peer system with game directory: {game_dir}");
@@ -90,7 +103,7 @@ pub fn start_peer(
// Start the peer in a background task // Start the peer in a background task
let tx_control_clone = tx_control.clone(); let tx_control_clone = tx_control.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = run_peer(rx_control, tx_notify_ui).await { if let Err(e) = run_peer_with_eti_loader(rx_control, tx_notify_ui, eti_games_loader).await {
log::error!("Peer system failed: {e}"); log::error!("Peer system failed: {e}");
} }
}); });
@@ -1077,13 +1090,29 @@ async fn retry_failed_chunks(
exhausted exhausted
} }
async fn load_local_game_db(game_dir: &str) -> eyre::Result<GameDB> { /// Load games from the ETI game.db resource file
fn load_eti_games(eti_games_loader: Option<EtiGamesLoader>) -> eyre::Result<Vec<EtiGame>> {
if let Some(loader) = eti_games_loader {
loader()
} else {
log::warn!("ETI games loader not provided - returning empty games list");
Ok(Vec::new())
}
}
/// Load local game database combining ETI games and locally installed games
async fn load_local_game_db_with_eti(
game_dir: &str,
eti_games_loader: Option<EtiGamesLoader>,
) -> eyre::Result<GameDB> {
let game_path = PathBuf::from(game_dir); let game_path = PathBuf::from(game_dir);
// Scan game directory for game folders // Load games from ETI database using the loader (from() sets installed=false, local_version=None)
let mut games = Vec::new(); let eti_games = load_eti_games(eti_games_loader)?;
let mut entries = tokio::fs::read_dir(&game_path).await?; let mut games: Vec<Game> = eti_games.into_iter().map(Into::into).collect();
// Scan game directory and mark found games as installed
let mut entries = tokio::fs::read_dir(&game_path).await?;
while let Some(entry) = entries.next_entry().await? { while let Some(entry) = entries.next_entry().await? {
let path = entry.path(); let path = entry.path();
if path.is_dir() if path.is_dir()
@@ -1091,10 +1120,17 @@ async fn load_local_game_db(game_dir: &str) -> eyre::Result<GameDB> {
{ {
// Check if this game has a version.ini file // Check if this game has a version.ini file
if let Ok(version) = lanspread_db::db::read_version_from_ini(&path) { if let Ok(version) = lanspread_db::db::read_version_from_ini(&path) {
// Update existing game or create new one if not found in ETI DB
if let Some(game) = games.iter_mut().find(|g| g.id == game_id) {
game.installed = true;
game.local_version.clone_from(&version);
// Keep the ETI size for informational purposes
} else {
// Game not found in ETI DB, create basic entry
let size = calculate_directory_size(&path).await?; let size = calculate_directory_size(&path).await?;
let game = Game { let game = Game {
id: game_id.to_string(), id: game_id.to_string(),
name: game_id.to_string(), // Use folder name as game name for now name: game_id.to_string(),
description: String::new(), description: String::new(),
release_year: String::new(), release_year: String::new(),
publisher: String::new(), publisher: String::new(),
@@ -1111,6 +1147,7 @@ async fn load_local_game_db(game_dir: &str) -> eyre::Result<GameDB> {
} }
} }
} }
}
Ok(GameDB::from(games)) Ok(GameDB::from(games))
} }
@@ -1137,12 +1174,24 @@ struct Ctx {
game_dir: Arc<RwLock<Option<String>>>, game_dir: Arc<RwLock<Option<String>>>,
local_game_db: Arc<RwLock<Option<GameDB>>>, local_game_db: Arc<RwLock<Option<GameDB>>>,
peer_game_db: Arc<RwLock<PeerGameDB>>, peer_game_db: Arc<RwLock<PeerGameDB>>,
eti_games_loader: Option<EtiGamesLoader>,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
struct PeerCtx { struct PeerCtx {
game_dir: Arc<RwLock<Option<String>>>, game_dir: Arc<RwLock<Option<String>>>,
local_game_db: Arc<RwLock<Option<GameDB>>>, local_game_db: Arc<RwLock<Option<GameDB>>>,
eti_games_loader: Option<EtiGamesLoader>,
}
impl std::fmt::Debug for PeerCtx {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PeerCtx")
.field("game_dir", &"...")
.field("local_game_db", &"...")
.field("eti_games_loader", &self.eti_games_loader.is_some())
.finish()
}
} }
/// Main peer execution loop that handles peer commands and manages the peer system. /// Main peer execution loop that handles peer commands and manages the peer system.
@@ -1152,19 +1201,30 @@ struct PeerCtx {
/// This function will panic if the games folder is None after being checked for None. /// This function will panic if the games folder is None after being checked for None.
/// The panic occurs at line 908 where `games_folder.expect("checked above")` is called. /// The panic occurs at line 908 where `games_folder.expect("checked above")` is called.
pub async fn run_peer( pub async fn run_peer(
rx_control: UnboundedReceiver<PeerCommand>,
tx_notify_ui: UnboundedSender<PeerEvent>,
) -> eyre::Result<()> {
run_peer_with_eti_loader(rx_control, tx_notify_ui, None).await
}
/// Main peer execution loop that handles peer commands and manages the peer system with ETI games loader.
pub async fn run_peer_with_eti_loader(
mut rx_control: UnboundedReceiver<PeerCommand>, mut rx_control: UnboundedReceiver<PeerCommand>,
tx_notify_ui: UnboundedSender<PeerEvent>, tx_notify_ui: UnboundedSender<PeerEvent>,
eti_games_loader: Option<EtiGamesLoader>,
) -> eyre::Result<()> { ) -> eyre::Result<()> {
// peer context // peer context
let ctx = Ctx { let ctx = Ctx {
game_dir: Arc::new(RwLock::new(None)), game_dir: Arc::new(RwLock::new(None)),
local_game_db: Arc::new(RwLock::new(None)), local_game_db: Arc::new(RwLock::new(None)),
peer_game_db: Arc::new(RwLock::new(PeerGameDB::new())), peer_game_db: Arc::new(RwLock::new(PeerGameDB::new())),
eti_games_loader,
}; };
let peer_ctx = PeerCtx { let peer_ctx = PeerCtx {
game_dir: ctx.game_dir.clone(), game_dir: ctx.game_dir.clone(),
local_game_db: ctx.local_game_db.clone(), local_game_db: ctx.local_game_db.clone(),
eti_games_loader: ctx.eti_games_loader.clone(),
}; };
// Start server component // Start server component
@@ -1431,8 +1491,9 @@ async fn handle_set_game_dir_command(ctx: &Ctx, game_dir: String) {
// Load local game database when game directory is set // Load local game database when game directory is set
let game_dir = game_dir.clone(); let game_dir = game_dir.clone();
let local_game_db = ctx.local_game_db.clone(); let local_game_db = ctx.local_game_db.clone();
let eti_games_loader = ctx.eti_games_loader.clone();
tokio::spawn(async move { tokio::spawn(async move {
match load_local_game_db(&game_dir).await { match load_local_game_db_with_eti(&game_dir, eti_games_loader).await {
Ok(db) => { Ok(db) => {
*local_game_db.write().await = Some(db); *local_game_db.write().await = Some(db);
log::info!("Local game database loaded successfully"); log::info!("Local game database loaded successfully");
@@ -31,6 +31,7 @@ tauri-build = { version = "2", features = [] }
lanspread-peer = { path = "../../lanspread-peer" } lanspread-peer = { path = "../../lanspread-peer" }
lanspread-db = { path = "../../lanspread-db" } lanspread-db = { path = "../../lanspread-db" }
lanspread-mdns = { path = "../../lanspread-mdns" } lanspread-mdns = { path = "../../lanspread-mdns" }
lanspread-compat = { path = "../../lanspread-compat" }
# external # external
eyre = { workspace = true } eyre = { workspace = true }
Binary file not shown.
@@ -5,8 +5,9 @@ use std::{
}; };
use eyre::bail; use eyre::bail;
use lanspread_compat::eti::get_games;
use lanspread_db::db::{Game, GameDB}; use lanspread_db::db::{Game, GameDB};
use lanspread_peer::{PeerCommand, PeerEvent, start_peer}; use lanspread_peer::{EtiGamesLoader, PeerCommand, PeerEvent, start_peer_with_eti_loader};
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::{RwLock, mpsc::UnboundedSender}; use tokio::sync::{RwLock, mpsc::UnboundedSender};
@@ -520,12 +521,33 @@ pub fn run() {
}; };
if !games_folder.is_empty() { if !games_folder.is_empty() {
// Create ETI games loader that can access the Tauri resource
let app_handle_for_loader = app_handle_clone.clone();
let eti_games_loader: EtiGamesLoader = Arc::new(move || {
let app_handle = app_handle_for_loader.clone();
// Use tokio's block_in_place to call async function from sync context
tokio::task::block_in_place(move || {
tokio::runtime::Handle::current().block_on(async move {
// Resolve the game.db resource path
match app_handle.path().resolve("game.db", tauri::path::BaseDirectory::Resource) {
Ok(game_db_path) => {
get_games(&game_db_path).await
}
Err(e) => {
log::error!("Failed to resolve game.db resource: {e}");
Err(eyre::eyre!("Failed to resolve game.db resource: {e}"))
}
}
})
})
});
// Only start peer system when we have a valid games directory // Only start peer system when we have a valid games directory
match start_peer(games_folder, tx_peer_event_clone) { match start_peer_with_eti_loader(games_folder, tx_peer_event_clone, Some(eti_games_loader)) {
Ok(peer_ctrl) => { Ok(peer_ctrl) => {
let state = app_handle_clone.state::<LanSpreadState>(); let state = app_handle_clone.state::<LanSpreadState>();
*state.peer_ctrl.write().await = Some(peer_ctrl); *state.peer_ctrl.write().await = Some(peer_ctrl);
log::info!("Peer system initialized successfully with games directory"); log::info!("Peer system initialized successfully with games directory and ETI loader");
// Start peer discovery and request games from other peers // Start peer discovery and request games from other peers
request_games(state); request_games(state);
@@ -33,6 +33,9 @@
], ],
"externalBin": [ "externalBin": [
"binaries/unrar" "binaries/unrar"
],
"resources": [
"game.db"
] ]
} }
} }