v0.0.20: file transfer in groups
/file <path> 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) <noreply@anthropic.com>
This commit is contained in:
10
warzone/Cargo.lock
generated
10
warzone/Cargo.lock
generated
@@ -2789,7 +2789,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2822,7 +2822,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2831,7 +2831,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -2856,7 +2856,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -2883,7 +2883,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.18"
|
version = "0.0.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.19"
|
version = "0.0.20"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -683,19 +683,82 @@ impl App {
|
|||||||
let file_id = uuid::Uuid::new_v4().to_string();
|
let file_id = uuid::Uuid::new_v4().to_string();
|
||||||
let total_chunks = ((file_data.len() + CHUNK_SIZE - 1) / CHUNK_SIZE) as u32;
|
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 {
|
let peer = match &self.peer_fp {
|
||||||
Some(p) if !p.starts_with('#') => p.clone(),
|
Some(p) => p.clone(),
|
||||||
_ => {
|
None => {
|
||||||
self.add_message(ChatLine {
|
self.add_message(ChatLine {
|
||||||
sender: "system".into(),
|
sender: "system".into(),
|
||||||
text: "File transfer requires a DM peer. Use /peer <fingerprint>".into(),
|
text: "Set a peer or group first".into(),
|
||||||
is_system: true, is_self: false, message_id: None,
|
is_system: true, is_self: false, message_id: None,
|
||||||
});
|
});
|
||||||
return;
|
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::<serde_json::Value>().await {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => return,
|
||||||
|
},
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
let my_fp = normfp(&self.our_fp);
|
||||||
|
let members: Vec<String> = 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) {
|
let peer_fp = match Fingerprint::from_hex(&peer) {
|
||||||
Ok(fp) => fp,
|
Ok(fp) => fp,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user