Compare commits
3
Commits
c00e6eae84
...
0693db94b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0693db94b2
|
||
|
|
80c50c0db3
|
||
|
|
05cac57f3a
|
@@ -225,12 +225,16 @@ mod tests {
|
||||
assert_eq!(chunk_counts.get(&peers[0]), 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 peer_b_bytes = byte_counts.get(&peers[1]).copied().unwrap_or_default();
|
||||
assert_eq!(peer_a_bytes + peer_b_bytes, file_size);
|
||||
let assigned_bytes = [
|
||||
byte_counts.get(&peers[0]).copied().unwrap_or_default(),
|
||||
byte_counts.get(&peers[1]).copied().unwrap_or_default(),
|
||||
];
|
||||
assert_eq!(assigned_bytes.iter().sum::<u64>(), file_size);
|
||||
assert!(
|
||||
peer_a_bytes.abs_diff(peer_b_bytes) <= CHUNK_SIZE,
|
||||
"large file bytes should be balanced within one chunk: {peer_a_bytes} vs {peer_b_bytes}"
|
||||
assigned_bytes[0].abs_diff(assigned_bytes[1]) <= CHUNK_SIZE,
|
||||
"large file bytes should be balanced within one chunk: {} vs {}",
|
||||
assigned_bytes[0],
|
||||
assigned_bytes[1]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1588,11 +1588,12 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_ctx(game_dir: PathBuf) -> Ctx {
|
||||
let state_dir = game_dir.join(".test-state");
|
||||
Ctx::new(
|
||||
Arc::new(RwLock::new(PeerGameDB::new())),
|
||||
"peer".to_string(),
|
||||
game_dir.clone(),
|
||||
game_dir.join(".test-state"),
|
||||
game_dir,
|
||||
state_dir,
|
||||
Arc::new(FakeUnpacker),
|
||||
CancellationToken::new(),
|
||||
TaskTracker::new(),
|
||||
@@ -1688,15 +1689,15 @@ mod tests {
|
||||
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 {
|
||||
panic!("expected ActiveOperationsChanged");
|
||||
};
|
||||
assert_eq!(active_operations, expected);
|
||||
}
|
||||
|
||||
fn active_update(id: &str, operation: ActiveOperationKind) -> Vec<ActiveOperation> {
|
||||
vec![ActiveOperation {
|
||||
fn active_update(id: &str, operation: ActiveOperationKind) -> [ActiveOperation; 1] {
|
||||
[ActiveOperation {
|
||||
id: id.to_string(),
|
||||
operation,
|
||||
}]
|
||||
@@ -1934,7 +1935,7 @@ mod tests {
|
||||
);
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
vec![ActiveOperation {
|
||||
&[ActiveOperation {
|
||||
id: "game".to_string(),
|
||||
operation: ActiveOperationKind::Updating,
|
||||
}],
|
||||
@@ -1971,12 +1972,12 @@ mod tests {
|
||||
assert!(token.is_cancelled());
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
vec![ActiveOperation {
|
||||
&[ActiveOperation {
|
||||
id: "game".to_string(),
|
||||
operation: ActiveOperationKind::Updating,
|
||||
}],
|
||||
);
|
||||
assert_active_update(recv_event(&mut rx).await, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(
|
||||
!ctx.active_operations.read().await.contains_key("game"),
|
||||
"timed-out drain should not leave the operation stuck active"
|
||||
@@ -2029,7 +2030,7 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Updating),
|
||||
&active_update("game", ActiveOperationKind::Updating),
|
||||
);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
@@ -2038,7 +2039,7 @@ mod tests {
|
||||
operation: InstallOperation::Updating
|
||||
} if id == "game"
|
||||
));
|
||||
assert_active_update(recv_event(&mut rx).await, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2060,7 +2061,7 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Installing),
|
||||
&active_update("game", ActiveOperationKind::Installing),
|
||||
);
|
||||
match recv_event(&mut rx).await {
|
||||
PeerEvent::InstallGameBegin { id, operation } => {
|
||||
@@ -2070,7 +2071,7 @@ mod tests {
|
||||
_ => panic!("expected InstallGameBegin"),
|
||||
}
|
||||
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!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2121,7 +2122,7 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Installing),
|
||||
&active_update("game", ActiveOperationKind::Installing),
|
||||
);
|
||||
match recv_event(&mut rx).await {
|
||||
PeerEvent::InstallGameBegin { id, operation } => {
|
||||
@@ -2131,7 +2132,7 @@ mod tests {
|
||||
_ => panic!("expected InstallGameBegin"),
|
||||
}
|
||||
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!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2181,7 +2182,7 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Updating),
|
||||
&active_update("game", ActiveOperationKind::Updating),
|
||||
);
|
||||
match recv_event(&mut rx).await {
|
||||
PeerEvent::InstallGameBegin { id, operation } => {
|
||||
@@ -2191,7 +2192,7 @@ mod tests {
|
||||
_ => panic!("expected InstallGameBegin"),
|
||||
}
|
||||
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!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2212,7 +2213,7 @@ mod tests {
|
||||
run_install_operation(&ctx, &tx, "game".to_string()).await;
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Installing),
|
||||
&active_update("game", ActiveOperationKind::Installing),
|
||||
);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
@@ -2223,7 +2224,7 @@ mod tests {
|
||||
));
|
||||
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, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2235,7 +2236,7 @@ mod tests {
|
||||
run_install_operation(&ctx, &tx, "game".to_string()).await;
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Updating),
|
||||
&active_update("game", ActiveOperationKind::Updating),
|
||||
);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
@@ -2246,7 +2247,7 @@ mod tests {
|
||||
));
|
||||
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, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::InstallGameFinished { id } if id == "game"
|
||||
@@ -2255,7 +2256,7 @@ mod tests {
|
||||
run_uninstall_operation(&ctx, &tx, "game".to_string()).await;
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Uninstalling),
|
||||
&active_update("game", ActiveOperationKind::Uninstalling),
|
||||
);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
@@ -2263,7 +2264,7 @@ mod tests {
|
||||
));
|
||||
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, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::UninstallGameFinished { id } if id == "game"
|
||||
@@ -2286,14 +2287,14 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::Uninstalling),
|
||||
&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, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::UninstallGameFinished { id } if id == "game"
|
||||
@@ -2321,7 +2322,7 @@ mod tests {
|
||||
|
||||
assert_active_update(
|
||||
recv_event(&mut rx).await,
|
||||
active_update("game", ActiveOperationKind::RemovingDownload),
|
||||
&active_update("game", ActiveOperationKind::RemovingDownload),
|
||||
);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
@@ -2331,7 +2332,7 @@ mod tests {
|
||||
panic!("expected LocalLibraryChanged");
|
||||
};
|
||||
assert!(games.is_empty());
|
||||
assert_active_update(recv_event(&mut rx).await, Vec::new());
|
||||
assert_active_update(recv_event(&mut rx).await, &[]);
|
||||
assert!(matches!(
|
||||
recv_event(&mut rx).await,
|
||||
PeerEvent::RemoveDownloadedGameFinished { id } if id == "game"
|
||||
|
||||
@@ -1044,53 +1044,8 @@ mod tests {
|
||||
const INSTALLING_PAYLOAD: &[u8] = b"installing";
|
||||
const BACKUP_PAYLOAD: &[u8] = b"backup";
|
||||
|
||||
fn seed_recovery_case(root: &Path, case: &RecoveryCase) {
|
||||
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 = [
|
||||
fn recovery_cases() -> [RecoveryCase; 10] {
|
||||
[
|
||||
RecoveryCase {
|
||||
name: "installing_commit_landed",
|
||||
intent_state: InstallIntentState::Installing,
|
||||
@@ -1171,35 +1126,86 @@ mod tests {
|
||||
has_backup: false,
|
||||
expected_local_payload: None,
|
||||
},
|
||||
];
|
||||
]
|
||||
}
|
||||
|
||||
let state = test_state();
|
||||
for case in cases {
|
||||
let temp = TempDir::new("lanspread-install");
|
||||
let root = temp.game_root();
|
||||
seed_recovery_case(&root, &case);
|
||||
write_intent(
|
||||
state.path(),
|
||||
"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.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
|
||||
fn seed_recovery_case(root: &Path, case: &RecoveryCase) {
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
@@ -374,11 +374,12 @@ mod tests {
|
||||
}
|
||||
|
||||
fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> Ctx {
|
||||
let state_dir = game_dir.join(".test-state");
|
||||
Ctx::new(
|
||||
Arc::new(RwLock::new(PeerGameDB::new())),
|
||||
"peer".to_string(),
|
||||
game_dir.clone(),
|
||||
game_dir.join(".test-state"),
|
||||
game_dir,
|
||||
state_dir,
|
||||
Arc::new(NoopUnpacker),
|
||||
CancellationToken::new(),
|
||||
TaskTracker::new(),
|
||||
@@ -388,8 +389,8 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn watch_event(path: PathBuf) -> notify::Result<Event> {
|
||||
Ok(Event::new(EventKind::Any).add_path(path))
|
||||
fn watch_event(path: PathBuf) -> Event {
|
||||
Event::new(EventKind::Any).add_path(path)
|
||||
}
|
||||
|
||||
async fn recv_local_update(
|
||||
@@ -460,7 +461,7 @@ mod tests {
|
||||
&ctx,
|
||||
&tx,
|
||||
&gate,
|
||||
watch_event(temp.path().join("game").join("version.ini")),
|
||||
Ok(watch_event(temp.path().join("game").join("version.ini"))),
|
||||
)
|
||||
.await;
|
||||
ctx.task_tracker.close();
|
||||
|
||||
@@ -479,11 +479,12 @@ mod tests {
|
||||
|
||||
fn test_ctx(game_dir: PathBuf, catalog: GameCatalog) -> PeerCtx {
|
||||
let (tx_notify_ui, _rx) = mpsc::unbounded_channel();
|
||||
let state_dir = game_dir.join(".test-state");
|
||||
Ctx::new(
|
||||
Arc::new(RwLock::new(PeerGameDB::new())),
|
||||
"peer".to_string(),
|
||||
game_dir.clone(),
|
||||
game_dir.join(".test-state"),
|
||||
game_dir,
|
||||
state_dir,
|
||||
Arc::new(NoopUnpacker),
|
||||
CancellationToken::new(),
|
||||
TaskTracker::new(),
|
||||
|
||||
@@ -838,7 +838,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parses_unrar_technical_listing() {
|
||||
let listing = parse_unrar_listing(
|
||||
r#"
|
||||
r"
|
||||
Archive: game.eti
|
||||
Details: RAR 5, solid
|
||||
|
||||
@@ -849,7 +849,7 @@ Details: RAR 5, solid
|
||||
|
||||
Name: bin
|
||||
Type: Directory
|
||||
"#,
|
||||
",
|
||||
)
|
||||
.expect("listing should parse");
|
||||
|
||||
@@ -876,14 +876,14 @@ Details: RAR 5, solid
|
||||
#[test]
|
||||
fn rejects_unrar_file_entries_without_crc32() {
|
||||
let err = parse_unrar_listing(
|
||||
r#"
|
||||
r"
|
||||
Archive: game.eti
|
||||
Details: RAR 5
|
||||
|
||||
Name: bin/payload.bin
|
||||
Type: File
|
||||
Size: 123
|
||||
"#,
|
||||
",
|
||||
)
|
||||
.expect_err("file entries without CRC32 should be rejected");
|
||||
|
||||
@@ -893,14 +893,14 @@ Details: RAR 5
|
||||
#[test]
|
||||
fn accepts_zero_size_unrar_file_entries_without_crc32() {
|
||||
let listing = parse_unrar_listing(
|
||||
r#"
|
||||
r"
|
||||
Archive: game.eti
|
||||
Details: RAR 5
|
||||
|
||||
Name: bin/empty.cfg
|
||||
Type: File
|
||||
Size: 0
|
||||
"#,
|
||||
",
|
||||
)
|
||||
.expect("empty file without CRC32 should parse as CRC32 zero");
|
||||
|
||||
@@ -918,15 +918,11 @@ Details: RAR 5
|
||||
#[test]
|
||||
fn sender_archive_integrity_accepts_matching_size_and_crc32() {
|
||||
let bytes = b"payload";
|
||||
let integrity =
|
||||
SenderArchiveIntegrity::new(u64::try_from(bytes.len()).unwrap(), crc32_of(bytes));
|
||||
let byte_len = u64::try_from(bytes.len()).expect("test payload length should fit in u64");
|
||||
let integrity = SenderArchiveIntegrity::new(byte_len, crc32_of(bytes));
|
||||
|
||||
integrity
|
||||
.verify(
|
||||
"bin/payload.bin",
|
||||
u64::try_from(bytes.len()).unwrap(),
|
||||
crc32_of(bytes),
|
||||
)
|
||||
.verify("bin/payload.bin", byte_len, crc32_of(bytes))
|
||||
.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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2430,7 +2506,7 @@ mod tests {
|
||||
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");
|
||||
assert!(trimmed.as_bytes().len() <= 21);
|
||||
assert!(trimmed.len() <= 21);
|
||||
assert!(trimmed.starts_with('é'));
|
||||
assert!(trimmed.ends_with('é'));
|
||||
|
||||
@@ -2735,79 +2811,3 @@ mod tests {
|
||||
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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,16 +23,16 @@ fmt:
|
||||
just --fmt
|
||||
|
||||
_fix:
|
||||
cargo fix
|
||||
cargo clippy --fix
|
||||
cargo fix --workspace --all-targets --all-features
|
||||
cargo clippy --workspace --all-targets --all-features --fix
|
||||
|
||||
fix: _fix fmt
|
||||
|
||||
clippy:
|
||||
cargo clippy
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
test:
|
||||
cargo test --workspace
|
||||
cargo test --workspace --all-targets --all-features
|
||||
|
||||
frontend-test:
|
||||
cd crates/lanspread-tauri-deno-ts && deno test --unstable-sloppy-imports tests
|
||||
|
||||
Reference in New Issue
Block a user