diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index 9cb96b1..de04ccd 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -120,6 +120,9 @@ pub enum PeerEvent { DownloadGameFilesFailed { id: String, }, + NoPeersHaveGame { + id: String, + }, PeerConnected(SocketAddr), PeerDisconnected(SocketAddr), PeerDiscovered(SocketAddr), @@ -1315,6 +1318,9 @@ async fn handle_get_game_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender { + log::warn!("PeerEvent::NoPeersHaveGame received for {id}"); + + if let Err(e) = app_handle.emit("game-no-peers", Some(id.clone())) { + log::error!("PeerEvent::NoPeersHaveGame: Failed to emit game-no-peers event: {e}"); + } + + app_handle + .state::() + .inner() + .games_in_download + .write() + .await + .remove(&id); + } PeerEvent::DownloadGameFilesBegin { id } => { log::info!("PeerEvent::DownloadGameFilesBegin received"); diff --git a/crates/lanspread-tauri-deno-ts/src/App.css b/crates/lanspread-tauri-deno-ts/src/App.css index 2ce0de2..ffce765 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.css +++ b/crates/lanspread-tauri-deno-ts/src/App.css @@ -216,3 +216,15 @@ h1.align-center { border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); } + +.item-info { + min-height: 18px; + margin: 8px 10px 16px; + font-size: 0.85em; + color: #8892b0; + text-align: center; +} + +.item-info.error { + color: #ff6666; +} diff --git a/crates/lanspread-tauri-deno-ts/src/App.tsx b/crates/lanspread-tauri-deno-ts/src/App.tsx index 1ecb928..ebb86ac 100644 --- a/crates/lanspread-tauri-deno-ts/src/App.tsx +++ b/crates/lanspread-tauri-deno-ts/src/App.tsx @@ -18,16 +18,20 @@ enum InstallStatus { Installed = 'Installed', } +type StatusLevel = 'info' | 'error'; + interface Game { id: string; name: string; description: string; size: number; - thumbnail: Uint8Array; + thumbnail: Uint8Array | number[]; installed: boolean; install_status: InstallStatus; eti_game_version?: string; local_version?: string; + status_message?: string; + status_level?: StatusLevel; } const App = () => { @@ -57,7 +61,12 @@ const App = () => { const game_id = event.payload as string; console.log(`❌ game-download-failed ${game_id} event received`); setGameItems(prev => prev.map(item => item.id === game_id - ? {...item, install_status: InstallStatus.NotInstalled} + ? { + ...item, + install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled, + status_message: 'Download failed. Please try again.', + status_level: 'error', + } : item)); // Convert to string explicitly and verify it's not empty @@ -72,7 +81,24 @@ const App = () => { return unlisten; }; + const setupNoPeersListener = async () => { + const unlisten = await listen('game-no-peers', (event) => { + const game_id = event.payload as string; + console.log(`⚠️ game-no-peers ${game_id} event received`); + setGameItems(prev => prev.map(item => item.id === game_id + ? { + ...item, + install_status: item.installed ? InstallStatus.Installed : InstallStatus.NotInstalled, + status_message: 'No peers currently have this game.', + status_level: 'error', + } + : item)); + }); + return unlisten; + }; + setupDownloadFailedListener(); + setupNoPeersListener(); }, [gameDir]); useEffect(() => { @@ -82,7 +108,12 @@ const App = () => { const game_id = event.payload as string; console.log(`🗲 game-unpack-finished ${game_id} event received`); setGameItems(prev => prev.map(item => item.id === game_id - ? {...item, install_status: InstallStatus.Installed} + ? { + ...item, + install_status: InstallStatus.Installed, + status_message: undefined, + status_level: undefined, + } : item)); // Convert to string explicitly and verify it's not empty @@ -131,7 +162,20 @@ const App = () => { console.log('🗲 Received games-list-updated event'); const games = event.payload as Game[]; console.log(`🎮 ${games.length} Games received`); - setGameItems(games); + setGameItems(prev => { + const previousById = new Map(prev.map(item => [item.id, item])); + return games.map(game => { + const previous = previousById.get(game.id); + const installStatus = previous?.install_status + ?? (game.installed ? InstallStatus.Installed : InstallStatus.NotInstalled); + return { + ...game, + install_status: installStatus, + status_message: previous?.status_message, + status_level: previous?.status_level, + }; + }); + }); getInitialGameDir(); }); @@ -140,7 +184,12 @@ const App = () => { const game_id = event.payload as string; console.log(`🗲 game-download-begin ${game_id} event received`); setGameItems(prev => prev.map(item => item.id === game_id - ? { ...item, install_status: InstallStatus.Downloading } + ? { + ...item, + install_status: InstallStatus.Downloading, + status_message: undefined, + status_level: undefined, + } : item)); }); @@ -149,7 +198,12 @@ const App = () => { const game_id = event.payload as string; console.log(`🗲 game-download-finished ${game_id} event received`); setGameItems(prev => prev.map(item => item.id === game_id - ? { ...item, install_status: InstallStatus.Unpacking } + ? { + ...item, + install_status: InstallStatus.Unpacking, + status_message: undefined, + status_level: undefined, + } : item)); }); @@ -195,7 +249,12 @@ const App = () => { console.log(`✅ Game install for id=${id} started...`); // update install status in gameItems for this game setGameItems(prev => prev.map(item => item.id === id - ? { ...item, install_status: InstallStatus.CheckingPeers } + ? { + ...item, + install_status: InstallStatus.CheckingPeers, + status_message: undefined, + status_level: undefined, + } : item)); } else { // game is already being installed @@ -214,7 +273,12 @@ const App = () => { console.log(`✅ Game update for id=${id} started...`); // update install status in gameItems for this game setGameItems(prev => prev.map(item => item.id === id - ? { ...item, install_status: InstallStatus.CheckingPeers } + ? { + ...item, + install_status: InstallStatus.CheckingPeers, + status_message: undefined, + status_level: undefined, + } : item)); } else { // game is already being installed/updated @@ -337,6 +401,9 @@ const App = () => { : 'Update' : 'Play'} +
+ {item.status_message ?? ''} +
); })}