diff --git a/PEER_CLI_SCENARIOS.md b/PEER_CLI_SCENARIOS.md index 2bf821e..7458369 100644 --- a/PEER_CLI_SCENARIOS.md +++ b/PEER_CLI_SCENARIOS.md @@ -46,7 +46,7 @@ for deterministic local runs; mDNS/macvlan remains an environment smoke path. | S36 | Catalog singleton beats stale majority | Five peers advertise one game; one peer has the catalog version and four peers have stale versions. | `list-games` reports `peer_count=1` and the catalog `eti_game_version`; all descriptors and chunks come from the singleton catalog-version peer, while stale peers remain hidden and contribute zero bytes. | | S37 | Single-source download throughput | A source peer advertises a temporary catalog game with one sparse `2 GiB` `.eti`; an empty client downloads it with `install=false`. | The client emits `download-finished` with throughput measurements (`bytes`, `duration_ms`, `mib_per_s`, `mbit_per_s`), and the downloaded archive size matches the source. | | S38 | First-play launch-setting stamping | `fixture-persona/css` ships a real RAR `.eti` whose tree buries a CRLF `SmartSteamEmu.ini` with a stub `PersonaName` line under `engine/bin/win64/steam_settings/`, plus a stub `account_name.txt` and `language.txt` under `profiles/local/`. A peer installs `css` (with `--unrar`), then sends `play css` with a username and language, then `play css` again. | After install the marker `games/css/launch_settings_applied` is absent and the stub files are intact under `local/`. The first `play` returns `already_applied=false` with `account_name_written`, `language_written`, and `persona_name_written` all true; the deep `SmartSteamEmu.ini` `PersonaName` value becomes the username with its `\r\n` ending and sibling lines preserved, `account_name.txt` becomes the username, `language.txt` becomes the passed language, and the marker now exists. A second `play` returns `already_applied=true`, rewrites nothing, and leaves the files untouched even if their values were reset externally. | -| S39 | Streamed install without keeping archive payload | Empty client connects to `fixture-bravo`, then sends `stream-install cnctw`. The source has real RAR `.eti` payload entries under `bin/` and `data/`; the receiver uses the container-bundled `unrar` stream provider. | Client emits `download-begin`, streamed `download-chunk-finished`, `download-finished`, `install-begin`, and `install-finished`. Local `cnctw` is `downloaded=false`, `installed=true`, `availability=LocalOnly`; root `version.ini` and `.eti` are absent; `local/bin/cnctw-payload.bin` and `local/data/cnctw-assets.dat` match `unrar p` output by SHA-256; the source reports no active outbound transfer for `cnctw` after completion. | +| S39 | Streamed install without keeping archive payload | Empty client connects to `fixture-bravo`, then sends `stream-install cnctw`. The source has real RAR `.eti` payload entries under `bin/` and `data/`; the receiver uses the container-bundled `unrar` stream provider. | Client emits `download-begin`, streamed `download-chunk-finished`, `download-finished`, and `install-finished` (the install-start transition is observable via `active-operations-changed`; there is no separate `install-begin` event). Local `cnctw` is `downloaded=false`, `installed=true`, `availability=LocalOnly`; root `version.ini` and `.eti` are absent; `local/bin/cnctw-payload.bin` and `local/data/cnctw-assets.dat` match `unrar p` output by SHA-256; the source reports no active outbound transfer for `cnctw` after completion. | | S40 | Streamed install receiver is not a peer source | After S39, a third peer connects only to the streamed-install receiver. | The third peer may see the receiver's local-only summary in peer snapshots, but `list-games` remote aggregation does not expose `cnctw` as downloadable, `peer_count` remains zero/absent, and attempting `download cnctw` fails with no local files created. | | S41 | Solid archive streamed install | Empty client connects to a peer serving `fixture-solid/cnctw`, whose `.eti` is a real solid RAR archive. The receiver uses the container-bundled `unrar` stream provider. | The fixture is verified as solid with `unrar lt`; streamed install finishes with `downloaded=false`, `installed=true`, `availability=LocalOnly`; root archive and `version.ini` are absent; streamed byte count equals the extracted solid entries; local payload SHA-256 hashes match `unrar p` output. | | S42 | Streamed install whole-stream retry | Empty client connects to two peers serving the same catalog-version `cnctw`: one broken source whose `--unrar` path is missing, followed by one good source. | The broken source sorts before the good source in retry order, contributes zero chunks, and the good source completes a fresh whole-stream attempt. The final state is local-only installed, no root archive/sentinel, no `.local.installing`, byte count matches the extracted entries, and payload hashes match the good source. | @@ -143,6 +143,25 @@ Use S39-S41 to pin down low-disk streamed installs: ## Run Log +### 2026-06-20 - Prune Dead Lifecycle Events + +- Code under test removed the unconsumed `InstallGameBegin`, `UninstallGameBegin`, + and `RemoveDownloadedGameBegin` `PeerEvent` variants (and their peer-cli JSONL + `install-begin`/`uninstall-begin`/`remove-download-begin` events), plus the + Tauri webview emits that no frontend listener consumed (`peer-local-ready`, + `game-download-begin`, `game-download-pre`, `game-download-finished`, + `game-uninstall-finished`, `peer-connected`/`-disconnected`/`-discovered`/`-lost`). + `peer-runtime-failed` was kept pending a UI decision. +- Rationale: the GUI is state-as-source-of-truth (it renders the `games-list` + snapshot), and no scenario asserted these begin events; the install, uninstall, + and removal start transitions stay observable via `active-operations-changed`. +- Contract update: the S39 row no longer lists `install-begin`. Older run-log + entries below predate the removal and are left intact as historical records. +- Gates: `just test`, `just clippy`, `just frontend-test`, and `just build` + passed. (`just fmt`'s `tombi` step needs network and was skipped; no TOML + changed.) The Docker S39-S47 matrix was not re-run for this cleanup; S39-S47 + never asserted the removed begin events, so coverage is unchanged. + ### 2026-06-07 - Catalog-Version Matrix Alignment (S1-S47) - Code under test aligned checked-in fixture `version.ini` sentinels with the diff --git a/crates/lanspread-peer-cli/src/main.rs b/crates/lanspread-peer-cli/src/main.rs index 21ff267..9e014f7 100644 --- a/crates/lanspread-peer-cli/src/main.rs +++ b/crates/lanspread-peer-cli/src/main.rs @@ -17,7 +17,6 @@ use lanspread_peer::{ ActiveOperation, ActiveOperationKind, ExternalUnrarStreamProvider, - InstallOperation, NoopStreamInstallProvider, OutboundTransfers, PeerCommand, @@ -541,16 +540,10 @@ async fn update_state_from_event(shared: &SharedState, event: PeerEvent) -> (&'s download_terminal_event(shared, "download-failed", id).await } PeerEvent::DownloadGameFilesAllPeersGone { id } => game_id_event("download-peers-gone", id), - PeerEvent::InstallGameBegin { id, operation } => ( - "install-begin", - json!({"game_id": id, "operation": install_operation_name(operation)}), - ), PeerEvent::InstallGameFinished { id } => game_id_event("install-finished", id), PeerEvent::InstallGameFailed { id } => game_id_event("install-failed", id), - PeerEvent::UninstallGameBegin { id } => game_id_event("uninstall-begin", id), PeerEvent::UninstallGameFinished { id } => game_id_event("uninstall-finished", id), PeerEvent::UninstallGameFailed { id } => game_id_event("uninstall-failed", id), - PeerEvent::RemoveDownloadedGameBegin { id } => game_id_event("remove-download-begin", id), PeerEvent::RemoveDownloadedGameFinished { id } => { game_id_event("remove-download-finished", id) } @@ -686,10 +679,6 @@ fn active_operation_name(operation: ActiveOperationKind) -> &'static str { (&operation).into() } -fn install_operation_name(operation: InstallOperation) -> &'static str { - (&operation).into() -} - fn runtime_component_name(component: PeerRuntimeComponent) -> &'static str { (&component).into() } diff --git a/crates/lanspread-peer/src/handlers.rs b/crates/lanspread-peer/src/handlers.rs index 0ead982..290090c 100644 --- a/crates/lanspread-peer/src/handlers.rs +++ b/crates/lanspread-peer/src/handlers.rs @@ -784,13 +784,6 @@ async fn commit_streamed_install( ctx.active_operations.clone(), tx_notify_ui.clone(), ); - events::send( - tx_notify_ui, - PeerEvent::InstallGameBegin { - id: id.clone(), - operation: InstallOperation::Installing, - }, - ); match transaction.commit().await { Ok(()) => { @@ -918,14 +911,6 @@ async fn run_started_install_operation( tx_notify_ui.clone(), ); let result = { - events::send( - tx_notify_ui, - PeerEvent::InstallGameBegin { - id: id.clone(), - operation, - }, - ); - let state_dir = ctx.state_dir.as_ref(); match operation { InstallOperation::Installing => { @@ -995,14 +980,7 @@ async fn run_uninstall_operation(ctx: &Ctx, tx_notify_ui: &UnboundedSender { @@ -1068,14 +1046,7 @@ async fn run_remove_downloaded_operation( ctx.active_operations.clone(), tx_notify_ui.clone(), ); - let result = { - events::send( - tx_notify_ui, - PeerEvent::RemoveDownloadedGameBegin { id: id.clone() }, - ); - - install::remove_downloaded(&game_dir, &id).await - }; + let result = install::remove_downloaded(&game_dir, &id).await; match result { Ok(()) => { @@ -2032,13 +2003,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Updating), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::InstallGameBegin { - id, - operation: InstallOperation::Updating - } if id == "game" - )); assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( recv_event(&mut rx).await, @@ -2063,13 +2027,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Installing), ); - match recv_event(&mut rx).await { - PeerEvent::InstallGameBegin { id, operation } => { - assert_eq!(id, "game"); - assert_eq!(operation, InstallOperation::Installing); - } - _ => panic!("expected InstallGameBegin"), - } assert_local_update(recv_event(&mut rx).await, true, true); assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( @@ -2124,13 +2081,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Installing), ); - match recv_event(&mut rx).await { - PeerEvent::InstallGameBegin { id, operation } => { - assert_eq!(id, "game"); - assert_eq!(operation, InstallOperation::Installing); - } - _ => panic!("expected InstallGameBegin"), - } assert_local_update(recv_event(&mut rx).await, true, true); assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( @@ -2184,13 +2134,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Updating), ); - match recv_event(&mut rx).await { - PeerEvent::InstallGameBegin { id, operation } => { - assert_eq!(id, "game"); - assert_eq!(operation, InstallOperation::Updating); - } - _ => panic!("expected InstallGameBegin"), - } assert_local_update(recv_event(&mut rx).await, true, true); assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( @@ -2215,13 +2158,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Installing), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::InstallGameBegin { - id, - operation: InstallOperation::Installing - } if id == "game" - )); let game = local_update_game(recv_event(&mut rx).await, true, true); assert_eq!(game.local_version.as_deref(), Some("20240101")); assert_active_update(recv_event(&mut rx).await, &[]); @@ -2238,13 +2174,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Updating), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::InstallGameBegin { - id, - operation: InstallOperation::Updating - } if id == "game" - )); let game = local_update_game(recv_event(&mut rx).await, true, true); assert_eq!(game.local_version.as_deref(), Some("20250101")); assert_active_update(recv_event(&mut rx).await, &[]); @@ -2258,10 +2187,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Uninstalling), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::UninstallGameBegin { id } if id == "game" - )); let game = local_update_game(recv_event(&mut rx).await, false, true); assert_eq!(game.local_version.as_deref(), Some("20250101")); assert_active_update(recv_event(&mut rx).await, &[]); @@ -2289,10 +2214,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::Uninstalling), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::UninstallGameBegin { id } if id == "game" - )); assert_local_update(recv_event(&mut rx).await, false, true); assert_active_update(recv_event(&mut rx).await, &[]); assert!(matches!( @@ -2324,10 +2245,6 @@ mod tests { recv_event(&mut rx).await, &active_update("game", ActiveOperationKind::RemovingDownload), ); - assert!(matches!( - recv_event(&mut rx).await, - PeerEvent::RemoveDownloadedGameBegin { id } if id == "game" - )); let PeerEvent::LocalLibraryChanged { games } = recv_event(&mut rx).await else { panic!("expected LocalLibraryChanged"); }; diff --git a/crates/lanspread-peer/src/lib.rs b/crates/lanspread-peer/src/lib.rs index 425cca3..fa5b765 100644 --- a/crates/lanspread-peer/src/lib.rs +++ b/crates/lanspread-peer/src/lib.rs @@ -127,23 +127,14 @@ pub enum PeerEvent { DownloadGameFilesFailed { id: String }, /// All peers with the game have disconnected during download. DownloadGameFilesAllPeersGone { id: String }, - /// Install or update transaction has started for a game. - InstallGameBegin { - id: String, - operation: InstallOperation, - }, /// Install or update transaction has completed successfully. InstallGameFinished { id: String }, /// Install or update transaction has failed after rollback. InstallGameFailed { id: String }, - /// Uninstall transaction has started for a game. - UninstallGameBegin { id: String }, /// Uninstall transaction has completed successfully. UninstallGameFinished { id: String }, /// Uninstall transaction has failed after rollback. UninstallGameFailed { id: String }, - /// Downloaded archive removal has started for an uninstalled game. - RemoveDownloadedGameBegin { id: String }, /// Downloaded archive removal has completed successfully. RemoveDownloadedGameFinished { id: String }, /// Downloaded archive removal has failed before deleting the game root. diff --git a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs index 53d6847..20f7cef 100644 --- a/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs +++ b/crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs @@ -2,7 +2,6 @@ use std::{ collections::{HashMap, HashSet}, fs::{self, OpenOptions}, io::{self, Read as _, Seek as _, SeekFrom, Write as _}, - net::SocketAddr, path::{Component, Path, PathBuf}, sync::{Arc, Mutex, OnceLock}, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -1970,12 +1969,6 @@ fn emit_game_id_event(app_handle: &AppHandle, event: &str, id: &str, label: &str } } -fn emit_peer_addr_event(app_handle: &AppHandle, event: &str, addr: SocketAddr) { - if let Err(e) = app_handle.emit(event, Some(addr.to_string())) { - log::error!("Failed to emit {event} event: {e}"); - } -} - fn spawn_peer_event_loop(app_handle: AppHandle, mut rx_peer_event: UnboundedReceiver) { tauri::async_runtime::spawn(async move { while let Some(event) = rx_peer_event.recv().await { @@ -2027,9 +2020,6 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { match event { PeerEvent::LocalPeerReady { peer_id, addr } => { log::info!("Local peer ready: {peer_id} at {addr}"); - if let Err(e) = app_handle.emit("peer-local-ready", Some((peer_id, addr.to_string()))) { - log::error!("Failed to emit peer-local-ready event: {e}"); - } } PeerEvent::ListGames(games) => { log::info!("PeerEvent::ListGames received"); @@ -2068,13 +2058,7 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { ); } PeerEvent::DownloadGameFilesBegin { id } => { - log::info!("PeerEvent::DownloadGameFilesBegin received"); - emit_game_id_event( - app_handle, - "game-download-begin", - &id, - "PeerEvent::DownloadGameFilesBegin", - ); + log::info!("PeerEvent::DownloadGameFilesBegin received for {id}"); } PeerEvent::DownloadGameFileChunkFinished { id, @@ -2094,7 +2078,7 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { } } PeerEvent::DownloadGameFilesFinished { id } => { - handle_download_finished(app_handle, id); + log::info!("PeerEvent::DownloadGameFilesFinished received for {id}"); } PeerEvent::DownloadGameFilesFailed { id } => { log::warn!("PeerEvent::DownloadGameFilesFailed received"); @@ -2114,16 +2098,6 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { "PeerEvent::DownloadGameFilesAllPeersGone", ); } - PeerEvent::InstallGameBegin { id, operation } => { - let operation_name: &'static str = (&operation).into(); - log::info!("PeerEvent::InstallGameBegin received for {id}: {operation_name}"); - emit_game_id_event( - app_handle, - "game-install-begin", - &id, - "PeerEvent::InstallGameBegin", - ); - } PeerEvent::InstallGameFinished { id } => { log::info!("PeerEvent::InstallGameFinished received for {id}"); emit_game_id_event( @@ -2142,23 +2116,8 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { "PeerEvent::InstallGameFailed", ); } - PeerEvent::UninstallGameBegin { id } => { - log::info!("PeerEvent::UninstallGameBegin received for {id}"); - emit_game_id_event( - app_handle, - "game-uninstall-begin", - &id, - "PeerEvent::UninstallGameBegin", - ); - } PeerEvent::UninstallGameFinished { id } => { log::info!("PeerEvent::UninstallGameFinished received for {id}"); - emit_game_id_event( - app_handle, - "game-uninstall-finished", - &id, - "PeerEvent::UninstallGameFinished", - ); } PeerEvent::UninstallGameFailed { id } => { log::warn!("PeerEvent::UninstallGameFailed received for {id}"); @@ -2169,15 +2128,6 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { "PeerEvent::UninstallGameFailed", ); } - PeerEvent::RemoveDownloadedGameBegin { id } => { - log::info!("PeerEvent::RemoveDownloadedGameBegin received for {id}"); - emit_game_id_event( - app_handle, - "game-remove-download-begin", - &id, - "PeerEvent::RemoveDownloadedGameBegin", - ); - } PeerEvent::RemoveDownloadedGameFinished { id } => { log::info!("PeerEvent::RemoveDownloadedGameFinished received for {id}"); emit_game_id_event( @@ -2198,19 +2148,15 @@ async fn handle_peer_event(app_handle: &AppHandle, event: PeerEvent) { } PeerEvent::PeerConnected(addr) => { log::info!("Peer connected: {addr}"); - emit_peer_addr_event(app_handle, "peer-connected", addr); } PeerEvent::PeerDisconnected(addr) => { log::info!("Peer disconnected: {addr}"); - emit_peer_addr_event(app_handle, "peer-disconnected", addr); } PeerEvent::PeerDiscovered(addr) => { log::info!("Peer discovered: {addr}"); - emit_peer_addr_event(app_handle, "peer-discovered", addr); } PeerEvent::PeerLost(addr) => { log::info!("Peer lost: {addr}"); - emit_peer_addr_event(app_handle, "peer-lost", addr); } PeerEvent::PeerCountUpdated(count) => { log::info!("Peer count updated: {count}"); @@ -2237,12 +2183,6 @@ async fn handle_got_game_files( file_descriptions: Vec, ) { log::info!("PeerEvent::GotGameFiles received"); - emit_game_id_event( - app_handle, - "game-download-pre", - &id, - "PeerEvent::GotGameFiles", - ); let state = app_handle.state::(); let peer_ctrl = state.peer_ctrl.read().await.clone(); @@ -2256,16 +2196,6 @@ async fn handle_got_game_files( } } -fn handle_download_finished(app_handle: &AppHandle, id: String) { - log::info!("PeerEvent::DownloadGameFilesFinished received"); - emit_game_id_event( - app_handle, - "game-download-finished", - &id, - "PeerEvent::DownloadGameFilesFinished", - ); -} - #[allow(clippy::missing_panics_doc)] #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() {