refactor(peer): split local library and operation UI events

Replace the `a9f9845` local-update dedup cache with explicit peer event
semantics. Local scans now emit `LocalLibraryChanged` when the library changes,
while operation mutations emit `ActiveOperationsChanged` from the mutation
path. Tauri keeps joining those facts into the existing `games-list-updated`
payload, so the frontend contract stays stable.

This removes the cache/invalidation coupling between scan emission and
operation state. The remaining forced local snapshot is explicit: accepted game
directory changes can refresh the UI for an equivalent new path without sending
a peer library delta.

Operation guard cleanup and liveness cancellation now publish the same active
operation snapshot as normal command-handler transitions. The peer CLI JSONL
events follow the same split with `local-library-changed` and
`active-operations-changed`.

Test Plan:
- `just fmt`
- `CARGO_BUILD_RUSTC_WRAPPER= just test`
- `CARGO_BUILD_RUSTC_WRAPPER= just clippy`
- `git diff --check`

Refs: CLEAN_CODE_PLAN_1.md
This commit is contained in:
ddidderr committed 2026-05-18 21:25:20 +02:00
1 parent be00a7a298
commit 41e9a0efc1
14 files changed
+657 -255

No files matched your search

+51 -2
View File
@@ -1,10 +1,16 @@
//! UI event helpers used by peer command and service code.
use std::{net::SocketAddr, sync::Arc};
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
use tokio::sync::{RwLock, mpsc::UnboundedSender};
use crate::{PeerEvent, peer_db::PeerGameDB};
use crate::{
ActiveOperation,
ActiveOperationKind,
PeerEvent,
context::OperationKind,
peer_db::PeerGameDB,
};
pub fn send(tx_notify_ui: &UnboundedSender<PeerEvent>, event: PeerEvent) {
if let Err(err) = tx_notify_ui.send(event) {
@@ -13,6 +19,49 @@ pub fn send(tx_notify_ui: &UnboundedSender<PeerEvent>, event: PeerEvent) {
}
}
pub(crate) fn active_operation_snapshot_from_map(
active_operations: &HashMap<String, OperationKind>,
) -> Vec<ActiveOperation> {
let mut snapshot = active_operations
.iter()
.map(|(id, operation)| ActiveOperation {
id: id.clone(),
operation: active_operation_kind(*operation),
})
.collect::<Vec<_>>();
snapshot.sort_by(|left, right| left.id.cmp(&right.id));
snapshot
}
pub(crate) fn send_active_operations_snapshot(
tx_notify_ui: &UnboundedSender<PeerEvent>,
active_operations: &HashMap<String, OperationKind>,
) {
send(
tx_notify_ui,
PeerEvent::ActiveOperationsChanged {
active_operations: active_operation_snapshot_from_map(active_operations),
},
);
}
pub(crate) async fn emit_active_operations(
active_operations: &Arc<RwLock<HashMap<String, OperationKind>>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
let active_operations = active_operations.read().await;
send_active_operations_snapshot(tx_notify_ui, &active_operations);
}
fn active_operation_kind(operation: OperationKind) -> ActiveOperationKind {
match operation {
OperationKind::Downloading => ActiveOperationKind::Downloading,
OperationKind::Installing => ActiveOperationKind::Installing,
OperationKind::Updating => ActiveOperationKind::Updating,
OperationKind::Uninstalling => ActiveOperationKind::Uninstalling,
}
}
pub async fn emit_peer_game_list(
peer_game_db: &Arc<RwLock<PeerGameDB>>,
tx_notify_ui: &UnboundedSender<PeerEvent>,