From fb987da8aca68fb423d8c8e751965ba7dd63e9bc Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Fri, 27 Mar 2026 20:23:19 +0400 Subject: [PATCH] v0.0.20: file transfer in groups /file now works in group mode (#group): - Sends file header + chunks to each group member - Same fan-out approach as group text messages - Each member receives and reassembles independently - Progress shown: "Sending 'file.pdf' to group #ops..." Co-Authored-By: Claude Opus 4.6 (1M context) --- warzone/Cargo.lock | 10 +-- warzone/Cargo.toml | 2 +- warzone/crates/warzone-client/src/tui/app.rs | 71 ++++++++++++++++++-- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/warzone/Cargo.lock b/warzone/Cargo.lock index 2c31188..70d1213 100644 --- a/warzone/Cargo.lock +++ b/warzone/Cargo.lock @@ -2789,7 +2789,7 @@ dependencies = [ [[package]] name = "warzone-client" -version = "0.0.18" +version = "0.0.19" dependencies = [ "anyhow", "argon2", @@ -2822,7 +2822,7 @@ dependencies = [ [[package]] name = "warzone-mule" -version = "0.0.18" +version = "0.0.19" dependencies = [ "anyhow", "clap", @@ -2831,7 +2831,7 @@ dependencies = [ [[package]] name = "warzone-protocol" -version = "0.0.18" +version = "0.0.19" dependencies = [ "base64", "bincode", @@ -2856,7 +2856,7 @@ dependencies = [ [[package]] name = "warzone-server" -version = "0.0.18" +version = "0.0.19" dependencies = [ "anyhow", "axum", @@ -2883,7 +2883,7 @@ dependencies = [ [[package]] name = "warzone-wasm" -version = "0.0.18" +version = "0.0.19" dependencies = [ "base64", "bincode", diff --git a/warzone/Cargo.toml b/warzone/Cargo.toml index 9758c53..143bef8 100644 --- a/warzone/Cargo.toml +++ b/warzone/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "0.0.19" +version = "0.0.20" edition = "2021" license = "MIT" rust-version = "1.75" diff --git a/warzone/crates/warzone-client/src/tui/app.rs b/warzone/crates/warzone-client/src/tui/app.rs index 87ce590..990e774 100644 --- a/warzone/crates/warzone-client/src/tui/app.rs +++ b/warzone/crates/warzone-client/src/tui/app.rs @@ -683,19 +683,82 @@ impl App { let file_id = uuid::Uuid::new_v4().to_string(); let total_chunks = ((file_data.len() + CHUNK_SIZE - 1) / CHUNK_SIZE) as u32; - // Resolve peer + // Resolve peer (or group members) let peer = match &self.peer_fp { - Some(p) if !p.starts_with('#') => p.clone(), - _ => { + Some(p) => p.clone(), + None => { self.add_message(ChatLine { sender: "system".into(), - text: "File transfer requires a DM peer. Use /peer ".into(), + text: "Set a peer or group first".into(), is_system: true, is_self: false, message_id: None, }); return; } }; + // Group file transfer: send to each member + if peer.starts_with('#') { + let group_name = &peer[1..]; + self.add_message(ChatLine { + sender: "system".into(), + text: format!("Sending '{}' to group #{}...", filename, group_name), + is_system: true, is_self: false, message_id: None, + }); + + // Get members + let url = format!("{}/v1/groups/{}", client.base_url, group_name); + let group_data = match client.client.get(&url).send().await { + Ok(resp) => match resp.json::().await { + Ok(d) => d, + Err(_) => return, + }, + Err(_) => return, + }; + let my_fp = normfp(&self.our_fp); + let members: Vec = group_data.get("members") + .and_then(|v| v.as_array()) + .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) + .unwrap_or_default(); + + for member in &members { + if *member == my_fp { continue; } + // Send file header + chunks to each member via HTTP + let header = WireMessage::FileHeader { + id: file_id.clone(), + sender_fingerprint: self.our_fp.clone(), + filename: filename.clone(), + file_size, + total_chunks, + sha256: sha256.clone(), + }; + if let Ok(encoded) = bincode::serialize(&header) { + let _ = client.send_message(member, Some(&self.our_fp), &encoded).await; + } + for i in 0..total_chunks { + let start = i as usize * CHUNK_SIZE; + let end = ((i as usize + 1) * CHUNK_SIZE).min(file_data.len()); + let chunk_msg = WireMessage::FileChunk { + id: file_id.clone(), + sender_fingerprint: self.our_fp.clone(), + filename: filename.clone(), + chunk_index: i, + total_chunks, + data: file_data[start..end].to_vec(), + }; + if let Ok(encoded) = bincode::serialize(&chunk_msg) { + let _ = client.send_message(member, Some(&self.our_fp), &encoded).await; + } + } + } + + self.add_message(ChatLine { + sender: "system".into(), + text: format!("File '{}' sent to group #{}", filename, group_name), + is_system: true, is_self: false, message_id: None, + }); + return; + }; + let peer_fp = match Fingerprint::from_hex(&peer) { Ok(fp) => fp, Err(_) => {