Compare commits

..
3 Commits
Author SHA1 Message Date
ddidderr 0693db94b2 fix(peer): clear remaining clippy warnings
`just clippy` was failing on peer test and helper code after the current lint
set denied warnings. This keeps the cleanup local to the reported warnings and
avoids changing runtime peer behavior or the wire protocol surface.

The test helpers now avoid needless ownership, active-operation assertions take
slices, and the local monitor watch-event helper no longer wraps a value that
cannot fail. The install recovery matrix keeps the same cases, but moves the
case table and per-case assertions into helpers so the test body stays below the
clippy line limit. The remaining lint fixes replace similar temporary names and
use an explicit `expect` message for test-only length conversion.

No separate project documentation update is needed because this only changes
internal test/helper structure and lint-only assertions; the existing peer
architecture docs remain accurate.

Test Plan:
- just fmt
- just clippy
- just test
- git diff --check
- git diff --cached --check

Refs: none
2026-06-13 10:16:48 +02:00
ddidderr 80c50c0db3 clippy: just fix 2026-06-13 09:52:03 +02:00
ddidderr 05cac57f3a justfile: harden clippy and tests 2026-06-13 09:50:23 +02:00
8 changed files with 214 additions and 205 deletions
@@ -225,12 +225,16 @@ mod tests {
assert_eq!(chunk_counts.get(&peers[0]), Some(&2)); assert_eq!(chunk_counts.get(&peers[0]), Some(&2));
assert_eq!(chunk_counts.get(&peers[1]), Some(&2)); assert_eq!(chunk_counts.get(&peers[1]), Some(&2));
let peer_a_bytes = byte_counts.get(&peers[0]).copied().unwrap_or_default(); let assigned_bytes = [
let peer_b_bytes = byte_counts.get(&peers[1]).copied().unwrap_or_default(); byte_counts.get(&peers[0]).copied().unwrap_or_default(),
assert_eq!(peer_a_bytes + peer_b_bytes, file_size); byte_counts.get(&peers[1]).copied().unwrap_or_default(),
];
assert_eq!(assigned_bytes.iter().sum::<u64>(), file_size);
assert!( assert!(
peer_a_bytes.abs_diff(peer_b_bytes) <= CHUNK_SIZE, assigned_bytes[0].abs_diff(assigned_bytes[1]) <= CHUNK_SIZE,
"large file bytes should be balanced within one chunk: {peer_a_bytes} vs {peer_b_bytes}" "large file bytes should be balanced within one chunk: {} vs {}",
assigned_bytes[0],
assigned_bytes[1]
); );
} }
+27 -26
View File
@@ -1588,11 +1588,12 @@ mod tests {
} }
fn test_ctx(game_dir: PathBuf) -> Ctx { fn test_ctx(game_dir: PathBuf) -> Ctx {
let state_dir = game_dir.join(".test-state");
Ctx::new( Ctx::new(
Arc::new(RwLock::new(PeerGameDB::new())), Arc::new(RwLock::new(PeerGameDB::new())),
"peer".to_string(), "peer".to_string(),
game_dir.clone(), game_dir,
game_dir.join(".test-state"), state_dir,
Arc::new(FakeUnpacker), Arc::new(FakeUnpacker),
CancellationToken::new(), CancellationToken::new(),
TaskTracker::new(), TaskTracker::new(),
@@ -1688,15 +1689,15 @@ mod tests {
game game
} }
fn assert_active_update(event: PeerEvent, expected: Vec<ActiveOperation>) { fn assert_active_update(event: PeerEvent, expected: &[ActiveOperation]) {
let PeerEvent::ActiveOperationsChanged { active_operations } = event else { let PeerEvent::ActiveOperationsChanged { active_operations } = event else {
panic!("expected ActiveOperationsChanged"); panic!("expected ActiveOperationsChanged");
}; };
assert_eq!(active_operations, expected); assert_eq!(active_operations, expected);
} }
fn active_update(id: &str, operation: ActiveOperationKind) -> Vec<ActiveOperation> { fn active_update(id: &str, operation: ActiveOperationKind) -> [ActiveOperation; 1] {
vec![ActiveOperation { [ActiveOperation {
id: id.to_string(), id: id.to_string(),
operation, operation,
}] }]
@@ -1934,7 +1935,7 @@ mod tests {
); );
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
vec![ActiveOperation { &[ActiveOperation {
id: "game".to_string(), id: "game".to_string(),
operation: ActiveOperationKind::Updating, operation: ActiveOperationKind::Updating,
}], }],
@@ -1971,12 +1972,12 @@ mod tests {
assert!(token.is_cancelled()); assert!(token.is_cancelled());
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
vec![ActiveOperation { &[ActiveOperation {
id: "game".to_string(), id: "game".to_string(),
operation: ActiveOperationKind::Updating, operation: ActiveOperationKind::Updating,
}], }],
); );
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!( assert!(
!ctx.active_operations.read().await.contains_key("game"), !ctx.active_operations.read().await.contains_key("game"),
"timed-out drain should not leave the operation stuck active" "timed-out drain should not leave the operation stuck active"
@@ -2029,7 +2030,7 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Updating), &active_update("game", ActiveOperationKind::Updating),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
@@ -2038,7 +2039,7 @@ mod tests {
operation: InstallOperation::Updating operation: InstallOperation::Updating
} if id == "game" } if id == "game"
)); ));
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2060,7 +2061,7 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Installing), &active_update("game", ActiveOperationKind::Installing),
); );
match recv_event(&mut rx).await { match recv_event(&mut rx).await {
PeerEvent::InstallGameBegin { id, operation } => { PeerEvent::InstallGameBegin { id, operation } => {
@@ -2070,7 +2071,7 @@ mod tests {
_ => panic!("expected InstallGameBegin"), _ => panic!("expected InstallGameBegin"),
} }
assert_local_update(recv_event(&mut rx).await, true, true); assert_local_update(recv_event(&mut rx).await, true, true);
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2121,7 +2122,7 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Installing), &active_update("game", ActiveOperationKind::Installing),
); );
match recv_event(&mut rx).await { match recv_event(&mut rx).await {
PeerEvent::InstallGameBegin { id, operation } => { PeerEvent::InstallGameBegin { id, operation } => {
@@ -2131,7 +2132,7 @@ mod tests {
_ => panic!("expected InstallGameBegin"), _ => panic!("expected InstallGameBegin"),
} }
assert_local_update(recv_event(&mut rx).await, true, true); assert_local_update(recv_event(&mut rx).await, true, true);
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2181,7 +2182,7 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Updating), &active_update("game", ActiveOperationKind::Updating),
); );
match recv_event(&mut rx).await { match recv_event(&mut rx).await {
PeerEvent::InstallGameBegin { id, operation } => { PeerEvent::InstallGameBegin { id, operation } => {
@@ -2191,7 +2192,7 @@ mod tests {
_ => panic!("expected InstallGameBegin"), _ => panic!("expected InstallGameBegin"),
} }
assert_local_update(recv_event(&mut rx).await, true, true); assert_local_update(recv_event(&mut rx).await, true, true);
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2212,7 +2213,7 @@ mod tests {
run_install_operation(&ctx, &tx, "game".to_string()).await; run_install_operation(&ctx, &tx, "game".to_string()).await;
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Installing), &active_update("game", ActiveOperationKind::Installing),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
@@ -2223,7 +2224,7 @@ mod tests {
)); ));
let game = local_update_game(recv_event(&mut rx).await, true, true); let game = local_update_game(recv_event(&mut rx).await, true, true);
assert_eq!(game.local_version.as_deref(), Some("20240101")); assert_eq!(game.local_version.as_deref(), Some("20240101"));
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2235,7 +2236,7 @@ mod tests {
run_install_operation(&ctx, &tx, "game".to_string()).await; run_install_operation(&ctx, &tx, "game".to_string()).await;
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Updating), &active_update("game", ActiveOperationKind::Updating),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
@@ -2246,7 +2247,7 @@ mod tests {
)); ));
let game = local_update_game(recv_event(&mut rx).await, true, true); let game = local_update_game(recv_event(&mut rx).await, true, true);
assert_eq!(game.local_version.as_deref(), Some("20250101")); assert_eq!(game.local_version.as_deref(), Some("20250101"));
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::InstallGameFinished { id } if id == "game" PeerEvent::InstallGameFinished { id } if id == "game"
@@ -2255,7 +2256,7 @@ mod tests {
run_uninstall_operation(&ctx, &tx, "game".to_string()).await; run_uninstall_operation(&ctx, &tx, "game".to_string()).await;
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Uninstalling), &active_update("game", ActiveOperationKind::Uninstalling),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
@@ -2263,7 +2264,7 @@ mod tests {
)); ));
let game = local_update_game(recv_event(&mut rx).await, false, true); let game = local_update_game(recv_event(&mut rx).await, false, true);
assert_eq!(game.local_version.as_deref(), Some("20250101")); assert_eq!(game.local_version.as_deref(), Some("20250101"));
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::UninstallGameFinished { id } if id == "game" PeerEvent::UninstallGameFinished { id } if id == "game"
@@ -2286,14 +2287,14 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::Uninstalling), &active_update("game", ActiveOperationKind::Uninstalling),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::UninstallGameBegin { id } if id == "game" PeerEvent::UninstallGameBegin { id } if id == "game"
)); ));
assert_local_update(recv_event(&mut rx).await, false, true); assert_local_update(recv_event(&mut rx).await, false, true);
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::UninstallGameFinished { id } if id == "game" PeerEvent::UninstallGameFinished { id } if id == "game"
@@ -2321,7 +2322,7 @@ mod tests {
assert_active_update( assert_active_update(
recv_event(&mut rx).await, recv_event(&mut rx).await,
active_update("game", ActiveOperationKind::RemovingDownload), &active_update("game", ActiveOperationKind::RemovingDownload),
); );
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
@@ -2331,7 +2332,7 @@ mod tests {
panic!("expected LocalLibraryChanged"); panic!("expected LocalLibraryChanged");
}; };
assert!(games.is_empty()); assert!(games.is_empty());
assert_active_update(recv_event(&mut rx).await, Vec::new()); assert_active_update(recv_event(&mut rx).await, &[]);
assert!(matches!( assert!(matches!(
recv_event(&mut rx).await, recv_event(&mut rx).await,
PeerEvent::RemoveDownloadedGameFinished { id } if id == "game" PeerEvent::RemoveDownloadedGameFinished { id } if id == "game"
@@ -1044,53 +1044,8 @@ mod tests {
const INSTALLING_PAYLOAD: &[u8] = b"installing"; const INSTALLING_PAYLOAD: &[u8] = b"installing";
const BACKUP_PAYLOAD: &[u8] = b"backup"; const BACKUP_PAYLOAD: &[u8] = b"backup";
fn seed_recovery_case(root: &Path, case: &RecoveryCase) { fn recovery_cases() -> [RecoveryCase; 10] {
write_file(&root.join("version.ini"), b"20250101"); [
if case.has_local {
write_file(&root.join(LOCAL_DIR).join("payload.txt"), LOCAL_PAYLOAD);
}
if case.has_installing {
write_file(
&root.join(INSTALLING_DIR).join("payload.txt"),
INSTALLING_PAYLOAD,
);
}
if case.has_backup {
write_file(&root.join(BACKUP_DIR).join("payload.txt"), BACKUP_PAYLOAD);
}
}
fn assert_recovered_case(root: &Path, case: &RecoveryCase) {
let local_payload = root.join(LOCAL_DIR).join("payload.txt");
match case.expected_local_payload {
Some(expected) => assert_eq!(
std::fs::read(&local_payload)
.unwrap_or_else(|err| panic!("{} local payload: {err}", case.name)),
expected,
"{} local payload",
case.name
),
None => assert!(
!root.join(LOCAL_DIR).exists(),
"{} local dir should be absent",
case.name
),
}
assert!(
!root.join(INSTALLING_DIR).exists(),
"{} installing dir should be absent",
case.name
);
assert!(
!root.join(BACKUP_DIR).exists(),
"{} backup dir should be absent",
case.name
);
}
#[tokio::test]
async fn recovery_covers_install_matrix_rows() {
let cases = [
RecoveryCase { RecoveryCase {
name: "installing_commit_landed", name: "installing_commit_landed",
intent_state: InstallIntentState::Installing, intent_state: InstallIntentState::Installing,
@@ -1171,35 +1126,86 @@ mod tests {
has_backup: false, has_backup: false,
expected_local_payload: None, expected_local_payload: None,
}, },
]; ]
}
let state = test_state(); fn seed_recovery_case(root: &Path, case: &RecoveryCase) {
for case in cases { write_file(&root.join("version.ini"), b"20250101");
let temp = TempDir::new("lanspread-install"); if case.has_local {
let root = temp.game_root(); write_file(&root.join(LOCAL_DIR).join("payload.txt"), LOCAL_PAYLOAD);
seed_recovery_case(&root, &case); }
write_intent( if case.has_installing {
state.path(), write_file(
"game", &root.join(INSTALLING_DIR).join("payload.txt"),
&InstallIntent::new("game", case.intent_state.clone(), Some("20250101".into())), INSTALLING_PAYLOAD,
)
.await
.unwrap_or_else(|err| panic!("{} intent should be written: {err}", case.name));
recover_game_root(&root, state.path(), "game")
.await
.unwrap_or_else(|err| panic!("{} recovery should succeed: {err}", case.name));
assert_recovered_case(&root, &case);
let intent = read_intent(state.path(), "game").await;
assert_eq!(intent.state, InstallIntentState::None, "{}", case.name);
assert_eq!(
intent.eti_version.as_deref(),
Some("20250101"),
"{}",
case.name
); );
} }
if case.has_backup {
write_file(&root.join(BACKUP_DIR).join("payload.txt"), BACKUP_PAYLOAD);
}
}
fn assert_recovered_case(root: &Path, case: &RecoveryCase) {
let local_payload = root.join(LOCAL_DIR).join("payload.txt");
match case.expected_local_payload {
Some(expected) => assert_eq!(
std::fs::read(&local_payload)
.unwrap_or_else(|err| panic!("{} local payload: {err}", case.name)),
expected,
"{} local payload",
case.name
),
None => assert!(
!root.join(LOCAL_DIR).exists(),
"{} local dir should be absent",
case.name
),
}
assert!(
!root.join(INSTALLING_DIR).exists(),
"{} installing dir should be absent",
case.name
);
assert!(
!root.join(BACKUP_DIR).exists(),
"{} backup dir should be absent",
case.name
);
}
async fn assert_recovery_case(state_dir: &Path, case: RecoveryCase) {
let temp = TempDir::new("lanspread-install");
let root = temp.game_root();
seed_recovery_case(&root, &case);
write_intent(
state_dir,
"game",
&InstallIntent::new("game", case.intent_state.clone(), Some("20250101".into())),
)
.await
.unwrap_or_else(|err| panic!("{} intent should be written: {err}", case.name));
recover_game_root(&root, state_dir, "game")
.await
.unwrap_or_else(|err| panic!("{} recovery should succeed: {err}", case.name));
assert_recovered_case(&root, &case);
let intent = read_intent(state_dir, "game").await;
assert_eq!(intent.state, InstallIntentState::None, "{}", case.name);
assert_eq!(
intent.eti_version.as_deref(),
Some("20250101"),
"{}",
case.name
);
}
#[tokio::test]
async fn recovery_covers_install_matrix_rows() {
let state = test_state();
for case in recovery_cases() {
assert_recovery_case(state.path(), case).await;
}
} }
#[tokio::test] #[tokio::test]
@@ -374,11 +374,12 @@ mod tests {
} }
fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> Ctx { fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> Ctx {
let state_dir = game_dir.join(".test-state");
Ctx::new( Ctx::new(
Arc::new(RwLock::new(PeerGameDB::new())), Arc::new(RwLock::new(PeerGameDB::new())),
"peer".to_string(), "peer".to_string(),
game_dir.clone(), game_dir,
game_dir.join(".test-state"), state_dir,
Arc::new(NoopUnpacker), Arc::new(NoopUnpacker),
CancellationToken::new(), CancellationToken::new(),
TaskTracker::new(), TaskTracker::new(),
@@ -388,8 +389,8 @@ mod tests {
) )
} }
fn watch_event(path: PathBuf) -> notify::Result<Event> { fn watch_event(path: PathBuf) -> Event {
Ok(Event::new(EventKind::Any).add_path(path)) Event::new(EventKind::Any).add_path(path)
} }
async fn recv_local_update( async fn recv_local_update(
@@ -460,7 +461,7 @@ mod tests {
&ctx, &ctx,
&tx, &tx,
&gate, &gate,
watch_event(temp.path().join("game").join("version.ini")), Ok(watch_event(temp.path().join("game").join("version.ini"))),
) )
.await; .await;
ctx.task_tracker.close(); ctx.task_tracker.close();
+3 -2
View File
@@ -479,11 +479,12 @@ mod tests {
fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> PeerCtx { fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> PeerCtx {
let (tx_notify_ui, _rx) = mpsc::unbounded_channel(); let (tx_notify_ui, _rx) = mpsc::unbounded_channel();
let state_dir = game_dir.join(".test-state");
Ctx::new( Ctx::new(
Arc::new(RwLock::new(PeerGameDB::new())), Arc::new(RwLock::new(PeerGameDB::new())),
"peer".to_string(), "peer".to_string(),
game_dir.clone(), game_dir,
game_dir.join(".test-state"), state_dir,
Arc::new(NoopUnpacker), Arc::new(NoopUnpacker),
CancellationToken::new(), CancellationToken::new(),
TaskTracker::new(), TaskTracker::new(),
+9 -13
View File
@@ -838,7 +838,7 @@ mod tests {
#[test] #[test]
fn parses_unrar_technical_listing() { fn parses_unrar_technical_listing() {
let listing = parse_unrar_listing( let listing = parse_unrar_listing(
r#" r"
Archive: game.eti Archive: game.eti
Details: RAR 5, solid Details: RAR 5, solid
@@ -849,7 +849,7 @@ Details: RAR 5, solid
Name: bin Name: bin
Type: Directory Type: Directory
"#, ",
) )
.expect("listing should parse"); .expect("listing should parse");
@@ -876,14 +876,14 @@ Details: RAR 5, solid
#[test] #[test]
fn rejects_unrar_file_entries_without_crc32() { fn rejects_unrar_file_entries_without_crc32() {
let err = parse_unrar_listing( let err = parse_unrar_listing(
r#" r"
Archive: game.eti Archive: game.eti
Details: RAR 5 Details: RAR 5
Name: bin/payload.bin Name: bin/payload.bin
Type: File Type: File
Size: 123 Size: 123
"#, ",
) )
.expect_err("file entries without CRC32 should be rejected"); .expect_err("file entries without CRC32 should be rejected");
@@ -893,14 +893,14 @@ Details: RAR 5
#[test] #[test]
fn accepts_zero_size_unrar_file_entries_without_crc32() { fn accepts_zero_size_unrar_file_entries_without_crc32() {
let listing = parse_unrar_listing( let listing = parse_unrar_listing(
r#" r"
Archive: game.eti Archive: game.eti
Details: RAR 5 Details: RAR 5
Name: bin/empty.cfg Name: bin/empty.cfg
Type: File Type: File
Size: 0 Size: 0
"#, ",
) )
.expect("empty file without CRC32 should parse as CRC32 zero"); .expect("empty file without CRC32 should parse as CRC32 zero");
@@ -918,15 +918,11 @@ Details: RAR 5
#[test] #[test]
fn sender_archive_integrity_accepts_matching_size_and_crc32() { fn sender_archive_integrity_accepts_matching_size_and_crc32() {
let bytes = b"payload"; let bytes = b"payload";
let integrity = let byte_len = u64::try_from(bytes.len()).expect("test payload length should fit in u64");
SenderArchiveIntegrity::new(u64::try_from(bytes.len()).unwrap(), crc32_of(bytes)); let integrity = SenderArchiveIntegrity::new(byte_len, crc32_of(bytes));
integrity integrity
.verify( .verify("bin/payload.bin", byte_len, crc32_of(bytes))
"bin/payload.bin",
u64::try_from(bytes.len()).unwrap(),
crc32_of(bytes),
)
.expect("matching sender archive metadata should verify"); .expect("matching sender archive metadata should verify");
} }
@@ -2266,6 +2266,82 @@ fn handle_download_finished(app_handle: &AppHandle, id: String) {
); );
} }
#[allow(clippy::missing_panics_doc)]
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// channel to receive events from the peer
let (tx_peer_event, rx_peer_event) = tokio::sync::mpsc::unbounded_channel::<PeerEvent>();
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
request_games,
install_game,
stream_install_game,
run_game,
start_server,
game_directory_exists,
update_game_directory,
update_game,
uninstall_game,
remove_downloaded_game,
cancel_download,
open_game_files,
get_peer_count,
get_game_thumbnail,
get_unpack_logs,
get_main_logs
])
.manage(LanSpreadState::default())
.manage(PeerEventTx(tx_peer_event))
.setup(move |app| {
let state_dir = app.path().app_data_dir()?;
std::fs::create_dir_all(&state_dir)?;
let main_log_sink = MainLogSink::new(app.handle().clone(), main_log_path(&state_dir));
let state = app.state::<LanSpreadState>();
if state.main_log_sink.set(main_log_sink.clone()).is_err() {
log::warn!("main log sink was already initialized");
}
init_main_logging(main_log_sink)?;
let unpack_logs = load_unpack_logs(&state_dir);
tauri::async_runtime::block_on(async {
*state.unpack_logs.write().await = unpack_logs;
});
if state.state_dir.set(state_dir).is_err() {
log::warn!("app state directory was already initialized");
}
spawn_peer_event_loop(app.handle().clone(), rx_peer_event);
Ok(())
})
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
if matches!(event, tauri::RunEvent::Exit) {
shutdown_peer_runtime(app_handle);
}
});
}
fn shutdown_peer_runtime(app_handle: &AppHandle) {
let state = app_handle.state::<LanSpreadState>();
let peer_runtime = state.peer_runtime.clone();
tauri::async_runtime::block_on(async move {
let Some(mut handle) = peer_runtime.write().await.take() else {
return;
};
handle.shutdown();
if tokio::time::timeout(std::time::Duration::from_secs(2), handle.wait_stopped())
.await
.is_err()
{
log::warn!("Peer runtime did not stop within 2s of shutdown request");
}
});
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -2430,7 +2506,7 @@ mod tests {
trim_main_log_file_to_limit(&path, 21).expect("main log should trim"); trim_main_log_file_to_limit(&path, 21).expect("main log should trim");
let trimmed = std::fs::read_to_string(&path).expect("trimmed log should remain utf-8"); let trimmed = std::fs::read_to_string(&path).expect("trimmed log should remain utf-8");
assert!(trimmed.as_bytes().len() <= 21); assert!(trimmed.len() <= 21);
assert!(trimmed.starts_with('é')); assert!(trimmed.starts_with('é'));
assert!(trimmed.ends_with('é')); assert!(trimmed.ends_with('é'));
@@ -2735,79 +2811,3 @@ mod tests {
assert!(game_db.get_game_by_id("unknown").is_none()); assert!(game_db.get_game_by_id("unknown").is_none());
} }
} }
#[allow(clippy::missing_panics_doc)]
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// channel to receive events from the peer
let (tx_peer_event, rx_peer_event) = tokio::sync::mpsc::unbounded_channel::<PeerEvent>();
tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
request_games,
install_game,
stream_install_game,
run_game,
start_server,
game_directory_exists,
update_game_directory,
update_game,
uninstall_game,
remove_downloaded_game,
cancel_download,
open_game_files,
get_peer_count,
get_game_thumbnail,
get_unpack_logs,
get_main_logs
])
.manage(LanSpreadState::default())
.manage(PeerEventTx(tx_peer_event))
.setup(move |app| {
let state_dir = app.path().app_data_dir()?;
std::fs::create_dir_all(&state_dir)?;
let main_log_sink = MainLogSink::new(app.handle().clone(), main_log_path(&state_dir));
let state = app.state::<LanSpreadState>();
if state.main_log_sink.set(main_log_sink.clone()).is_err() {
log::warn!("main log sink was already initialized");
}
init_main_logging(main_log_sink)?;
let unpack_logs = load_unpack_logs(&state_dir);
tauri::async_runtime::block_on(async {
*state.unpack_logs.write().await = unpack_logs;
});
if state.state_dir.set(state_dir).is_err() {
log::warn!("app state directory was already initialized");
}
spawn_peer_event_loop(app.handle().clone(), rx_peer_event);
Ok(())
})
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
if matches!(event, tauri::RunEvent::Exit) {
shutdown_peer_runtime(app_handle);
}
});
}
fn shutdown_peer_runtime(app_handle: &AppHandle) {
let state = app_handle.state::<LanSpreadState>();
let peer_runtime = state.peer_runtime.clone();
tauri::async_runtime::block_on(async move {
let Some(mut handle) = peer_runtime.write().await.take() else {
return;
};
handle.shutdown();
if tokio::time::timeout(std::time::Duration::from_secs(2), handle.wait_stopped())
.await
.is_err()
{
log::warn!("Peer runtime did not stop within 2s of shutdown request");
}
});
}
+4 -4
View File
@@ -23,16 +23,16 @@ fmt:
just --fmt just --fmt
_fix: _fix:
cargo fix cargo fix --workspace --all-targets --all-features
cargo clippy --fix cargo clippy --workspace --all-targets --all-features --fix
fix: _fix fmt fix: _fix fmt
clippy: clippy:
cargo clippy cargo clippy --workspace --all-targets --all-features -- -D warnings
test: test:
cargo test --workspace cargo test --workspace --all-targets --all-features
frontend-test: frontend-test:
cd crates/lanspread-tauri-deno-ts && deno test --unstable-sloppy-imports tests cd crates/lanspread-tauri-deno-ts && deno test --unstable-sloppy-imports tests