wip
This commit is contained in:
@@ -12,7 +12,6 @@ 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};
|
||||||
@@ -24,9 +23,6 @@ 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},
|
||||||
@@ -86,15 +82,6 @@ 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}");
|
||||||
|
|
||||||
@@ -103,7 +90,7 @@ pub fn start_peer_with_eti_loader(
|
|||||||
// 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_with_eti_loader(rx_control, tx_notify_ui, eti_games_loader).await {
|
if let Err(e) = run_peer(rx_control, tx_notify_ui).await {
|
||||||
log::error!("Peer system failed: {e}");
|
log::error!("Peer system failed: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1090,28 +1077,12 @@ async fn retry_failed_chunks(
|
|||||||
exhausted
|
exhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load games from the ETI game.db resource file
|
/// Load local game database combining locally installed games
|
||||||
fn load_eti_games(eti_games_loader: Option<EtiGamesLoader>) -> eyre::Result<Vec<EtiGame>> {
|
async fn load_local_game_db(game_dir: &str) -> eyre::Result<GameDB> {
|
||||||
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);
|
||||||
|
let mut games = Vec::new();
|
||||||
|
|
||||||
// Load games from ETI database using the loader (from() sets installed=false, local_version=None)
|
// Scan game directory and create entries for installed games
|
||||||
let eti_games = load_eti_games(eti_games_loader)?;
|
|
||||||
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?;
|
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();
|
||||||
@@ -1120,13 +1091,6 @@ async fn load_local_game_db_with_eti(
|
|||||||
{
|
{
|
||||||
// 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(),
|
||||||
@@ -1147,7 +1111,6 @@ async fn load_local_game_db_with_eti(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(GameDB::from(games))
|
Ok(GameDB::from(games))
|
||||||
}
|
}
|
||||||
@@ -1174,14 +1137,12 @@ 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)]
|
#[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 {
|
impl std::fmt::Debug for PeerCtx {
|
||||||
@@ -1189,7 +1150,6 @@ impl std::fmt::Debug for PeerCtx {
|
|||||||
f.debug_struct("PeerCtx")
|
f.debug_struct("PeerCtx")
|
||||||
.field("game_dir", &"...")
|
.field("game_dir", &"...")
|
||||||
.field("local_game_db", &"...")
|
.field("local_game_db", &"...")
|
||||||
.field("eti_games_loader", &self.eti_games_loader.is_some())
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1201,30 +1161,19 @@ impl std::fmt::Debug for 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
|
||||||
@@ -1491,9 +1440,8 @@ 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_with_eti(&game_dir, eti_games_loader).await {
|
match load_local_game_db(&game_dir).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");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::{
|
|||||||
use eyre::bail;
|
use eyre::bail;
|
||||||
use lanspread_compat::eti::get_games;
|
use lanspread_compat::eti::get_games;
|
||||||
use lanspread_db::db::{Game, GameDB};
|
use lanspread_db::db::{Game, GameDB};
|
||||||
use lanspread_peer::{EtiGamesLoader, PeerCommand, PeerEvent, start_peer_with_eti_loader};
|
use lanspread_peer::{PeerCommand, PeerEvent, start_peer};
|
||||||
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};
|
||||||
@@ -521,33 +521,39 @@ pub fn run() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !games_folder.is_empty() {
|
if !games_folder.is_empty() {
|
||||||
// Create ETI games loader that can access the Tauri resource
|
// Load ETI games from the game.db resource - this MUST succeed
|
||||||
let app_handle_for_loader = app_handle_clone.clone();
|
let game_db_path = match app_handle_clone.path().resolve("game.db", tauri::path::BaseDirectory::Resource) {
|
||||||
let eti_games_loader: EtiGamesLoader = Arc::new(move || {
|
Ok(path) => path,
|
||||||
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) => {
|
Err(e) => {
|
||||||
log::error!("Failed to resolve game.db resource: {e}");
|
log::error!("Failed to resolve game.db resource: {e}");
|
||||||
Err(eyre::eyre!("Failed to resolve game.db resource: {e}"))
|
panic!("game.db resource is mandatory - cannot continue");
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let eti_games = match get_games(&game_db_path).await {
|
||||||
|
Ok(games) => {
|
||||||
|
log::info!("Loaded {} ETI games from game.db", games.len());
|
||||||
|
games
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load ETI games: {e}");
|
||||||
|
panic!("game.db resource is mandatory - cannot continue");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert ETI games to GameDB and store in state
|
||||||
|
let games: Vec<Game> = eti_games.into_iter().map(Into::into).collect();
|
||||||
|
{
|
||||||
|
let state = app_handle_clone.state::<LanSpreadState>();
|
||||||
|
*state.games.write().await = GameDB::from(games);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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_with_eti_loader(games_folder, tx_peer_event_clone, Some(eti_games_loader)) {
|
match start_peer(games_folder, tx_peer_event_clone) {
|
||||||
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 and ETI loader");
|
log::info!("Peer system initialized successfully with games directory");
|
||||||
|
|
||||||
// Start peer discovery and request games from other peers
|
// Start peer discovery and request games from other peers
|
||||||
request_games(state);
|
request_games(state);
|
||||||
|
|||||||
Reference in New Issue
Block a user