From f69629cd526f9ba36825884abfd69883c7e9a40d Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 00:02:03 +0100 Subject: [PATCH 1/9] [feature] better socket address fetching logic --- client-gui/src/gui_app.rs | 311 +++++++++++++------------ client-network/src/lib.rs | 88 ++++++- client-network/src/message_handling.rs | 2 +- client-network/src/registration.rs | 86 ++++--- 4 files changed, 300 insertions(+), 187 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 3e380aa..ae916af 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -453,92 +453,103 @@ impl eframe::App for P2PClientApp { }); ui.separator(); - ScrollArea::vertical().show(ui, |ui| { - if self.known_peers.is_empty() { - ui.add_space(10.0); - ui.label("No active peers."); - } else { - for peer in &self.known_peers { - let is_active = - self.active_peer.as_ref().map_or(false, |id| id == &peer.0); // if peer.id == self.active_peer_id + ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.style_mut().visuals.widgets.inactive.bg_fill = + ui.style().visuals.widgets.inactive.bg_fill; // no-op to get mutable borrow + if self.known_peers.is_empty() { + ui.add_space(10.0); + ui.label("No active peers."); + } else { + for peer in &self.known_peers { + let is_active = + self.active_peer.as_ref().map_or(false, |id| id == &peer.0); // if peer.id == self.active_peer_id - let selectable: Response; - // if &self.active_server == &peer.0 { - // // Create a frame with green background and render the selectable inside it. - // // Adjust rounding, padding and stroke as desired. - // let frame = Frame { - // fill: Color32::DARK_BLUE, - // stroke: Stroke::default(), - // corner_radius: CornerRadius::from(0.5), - // ..Default::default() - // }; - // let internal = frame.show(ui, |ui| { - // // horizontal row: label on the left, spinner on the right - // ui.horizontal(|ui| { - // // let selectable label take remaining space - // ui.with_layout(Layout::left_to_right(Align::Center), |ui| { - // ui.add_space(0.0); // ensure layout established - // return ui.selectable_label( - // is_active, - // format!("{}", peer.0), - // ); - // }) - // .inner // return Response from the inner closure if your egui version does so - // }) - // }); - // selectable = internal.inner.inner; - // } else { - // selectable = ui.selectable_label(is_active, format!("{}", peer.0)); - // } + let selectable: Response; + // if &self.active_server == &peer.0 { + // // Create a frame with green background and render the selectable inside it. + // // Adjust rounding, padding and stroke as desired. + // let frame = Frame { + // fill: Color32::DARK_BLUE, + // stroke: Stroke::default(), + // corner_radius: CornerRadius::from(0.5), + // ..Default::default() + // }; + // let internal = frame.show(ui, |ui| { + // // horizontal row: label on the left, spinner on the right + // ui.horizontal(|ui| { + // // let selectable label take remaining space + // ui.with_layout(Layout::left_to_right(Align::Center), |ui| { + // ui.add_space(0.0); // ensure layout established + // return ui.selectable_label( + // is_active, + // format!("{}", peer.0), + // ); + // }) + // .inner // return Response from the inner closure if your egui version does so + // }) + // }); + // selectable = internal.inner.inner; + // } else { + // selectable = ui.selectable_label(is_active, format!("{}", peer.0)); + // } - // place spinner to the right of the label - ui.horizontal(|ui| { - // Use same width for the label widget as the selectable we already created: - // Recreate selectable inline so both label and spinner share the same row. - let resp = if &self.active_server == &peer.0 { - // draw with frame inline - let frame = Frame { - fill: Color32::DARK_BLUE, - stroke: Stroke::default(), - corner_radius: CornerRadius::from(0.5), - ..Default::default() + // place spinner to the right of the label + ui.horizontal(|ui| { + // Use same width for the label widget as the selectable we already created: + // Recreate selectable inline so both label and spinner share the same row. + let resp = if &self.active_server == &peer.0 { + // draw with frame inline + let frame = Frame { + fill: Color32::DARK_BLUE, + stroke: Stroke::default(), + corner_radius: CornerRadius::from(0.5), + ..Default::default() + }; + frame + .show(ui, |ui| { + ui.selectable_label( + is_active, + format!("{}", peer.0), + ) + }) + .inner + } else { + ui.selectable_label(is_active, format!("{}", peer.0)) }; - frame - .show(ui, |ui| { - ui.selectable_label(is_active, format!("{}", peer.0)) - }) - .inner - } else { - ui.selectable_label(is_active, format!("{}", peer.0)) - }; - ui.add_space(4.0); // small gap + ui.add_space(4.0); // small gap - if self.loading_peers.contains(&peer.0) { - // push spinner to right by expanding a spacer before it - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - ui.spinner(); - }); - } - - // use resp (click handling etc.) - if resp.clicked() { - // switch to displaying this peer's tree - self.active_peer = Some(peer.0.clone()); - // Request root content if not loaded - if !self - .loaded_fs - .contains_key(self.active_peer.as_ref().unwrap()) - { - //todo!(); - let _ = self.network_cmd_tx.send(NetworkCommand::Discover( - peer.0.clone(), - "root".to_string(), - self.connected_address.clone(), - )); + if self.loading_peers.contains(&peer.0) { + // push spinner to right by expanding a spacer before it + ui.with_layout( + Layout::right_to_left(Align::Center), + |ui| { + ui.spinner(); + }, + ); } - } - resp.context_menu(|ui| { + + // use resp (click handling etc.) + if resp.clicked() { + // switch to displaying this peer's tree + self.active_peer = Some(peer.0.clone()); + // Request root content if not loaded + if !self + .loaded_fs + .contains_key(self.active_peer.as_ref().unwrap()) + { + //todo!(); + let _ = + self.network_cmd_tx.send(NetworkCommand::Discover( + peer.0.clone(), + "root".to_string(), + self.connected_address.clone(), + )); + } + } + resp.context_menu(|ui| { // ... action match self.server_status { ServerStatus::Connected => { @@ -589,78 +600,78 @@ impl eframe::App for P2PClientApp { // ... autres boutons }); - }); + }); - // if self.loading_peers.contains(&peer.0) { - // ui.spinner(); - // } + // if self.loading_peers.contains(&peer.0) { + // ui.spinner(); + // } - //if selectable.clicked() { - // switch to displaying this peer's tree - //self.active_peer = Some(peer.0.clone()); - //// Request root content if not loaded - //if !self - // .loaded_fs - // .contains_key(self.active_peer.as_ref().unwrap()) - //{ - // //todo!(); - // let _ = self.network_cmd_tx.send(NetworkCommand::Discover( - // peer.0.clone(), - // "root".to_string(), - // self.connected_address.clone(), - // )); - //} - //} - //selectable.context_menu(|ui| { - // // ... action - // match self.server_status { - // ServerStatus::Connected => { - // if ui - // .button("Utiliser le peer en tant que serveur") - // .clicked() - // { - // self.active_server = peer.0.to_string(); - // let res = self.network_cmd_tx.send( - // NetworkCommand::ServerHandshake( - // peer.0.to_string(), - // self.connected_address.clone(), - // ), - // ); - // } - // } - // _ => {} - // } - // if ui.button("Send Ping").clicked() { - // let res = self.network_cmd_tx.send(NetworkCommand::Ping( - // peer.0.to_string(), - // self.connected_address.clone(), - // )); + //if selectable.clicked() { + // switch to displaying this peer's tree + //self.active_peer = Some(peer.0.clone()); + //// Request root content if not loaded + //if !self + // .loaded_fs + // .contains_key(self.active_peer.as_ref().unwrap()) + //{ + // //todo!(); + // let _ = self.network_cmd_tx.send(NetworkCommand::Discover( + // peer.0.clone(), + // "root".to_string(), + // self.connected_address.clone(), + // )); + //} + //} + //selectable.context_menu(|ui| { + // // ... action + // match self.server_status { + // ServerStatus::Connected => { + // if ui + // .button("Utiliser le peer en tant que serveur") + // .clicked() + // { + // self.active_server = peer.0.to_string(); + // let res = self.network_cmd_tx.send( + // NetworkCommand::ServerHandshake( + // peer.0.to_string(), + // self.connected_address.clone(), + // ), + // ); + // } + // } + // _ => {} + // } + // if ui.button("Send Ping").clicked() { + // let res = self.network_cmd_tx.send(NetworkCommand::Ping( + // peer.0.to_string(), + // self.connected_address.clone(), + // )); - // self.loading_peers.push(peer.0.to_owned()); - // } - // if ui.button("Send Nat Traversal Request").clicked() { - // match self.network_cmd_tx.send(NetworkCommand::NatTraversal( - // peer.0.to_string(), - // self.connected_address.clone(), - // )) { - // Ok(_) => { - // print!("[+] successfully sent nat traversal request") - // } - // Err(_) => { - // print!("[-] failed to send nat traversal request") - // } - // } - // } - // if ui.button("Infos").clicked() { - // // action 3 - // ui.close(); - // } + // self.loading_peers.push(peer.0.to_owned()); + // } + // if ui.button("Send Nat Traversal Request").clicked() { + // match self.network_cmd_tx.send(NetworkCommand::NatTraversal( + // peer.0.to_string(), + // self.connected_address.clone(), + // )) { + // Ok(_) => { + // print!("[+] successfully sent nat traversal request") + // } + // Err(_) => { + // print!("[-] failed to send nat traversal request") + // } + // } + // } + // if ui.button("Infos").clicked() { + // // action 3 + // ui.close(); + // } - // // ... autres boutons - //}); + // // ... autres boutons + //}); + } } - } - }); + }); }); CentralPanel::default().show(ctx, |ui| { @@ -680,7 +691,11 @@ impl eframe::App for P2PClientApp { self.draw_file_tree(ui, tree); }); } else { - ui.label(format!("Loading root for peer: {}", active_peer)); + ui.horizontal(|ui| { + ui.label(format!("Loading root for peer: {}", active_peer)); + ui.add_space(8.0); + ui.spinner(); // or conditional: if is_loading { ui.spinner(); } + }); } } else { ui.label("Connect to a peer to view a file tree."); diff --git a/client-network/src/lib.rs b/client-network/src/lib.rs index d4d80b2..635eb34 100644 --- a/client-network/src/lib.rs +++ b/client-network/src/lib.rs @@ -340,9 +340,53 @@ pub fn start_p2p_executor( sd.messages_list(), sd.handshake_peers.get_username_peerinfo_map(), ); + let server_address = { + match get_server_address(username.to_owned(), ip.to_owned()).await { + Some(addr) => addr.to_string(), + None => { + match event_tx.send(NetworkEvent::Error( + "Couldn't fetch server socket address.".to_owned(), + username.to_owned(), + )) { + Ok(_) => {} + Err(e) => { + println!("Network Event Error : {}", e.to_string()); + } + } + "".to_owned() + } + } + }; + if server_address.to_owned().eq(&"".to_owned()) { + continue; + } - let res = - perform_handshake(&sd, username, ip, event_tx.clone(), true).await; + sd.set_servername(username.to_owned()); + sd.set_serveraddress(server_address.to_string()); + println!("SET SERVERADDRESS"); + + match perform_handshake( + &sd, + username.to_owned(), + ip, + event_tx.clone(), + (true, server_address.to_string()), + ) + .await + { + true => { + match event_tx.send(NetworkEvent::Success( + "Handshake established ✔️".to_string(), + username.to_owned(), + )) { + Ok(_) => {} + Err(err) => { + println!("Network Event Error : {}", err.to_string()); + } + }; + } + false => {} + }; } else { println!("no shared data"); } @@ -360,7 +404,9 @@ pub fn start_p2p_executor( NetworkCommand::Discover(username, hash, ip) => { // envoie un handshake au peer, puis un root request if let Some(sd) = shared_data.as_ref() { - let res = sd.handshake_peers.get_peer_info_username(username.clone()); + let res = sd + .handshake_peers + .get_peer_info_username(username.to_owned()); match res { Some(peerinfo) => { let id = generate_id(); @@ -395,14 +441,31 @@ pub fn start_p2p_executor( } None => { // envoyer un handshake - let res = perform_handshake( + match perform_handshake( &sd, - username, + username.to_owned(), ip, event_tx.clone(), - false, + (false, "".to_string()), ) - .await; + .await + { + true => { + match event_tx.send(NetworkEvent::Success( + "Handshake established ✔️".to_string(), + username.to_owned(), + )) { + Ok(_) => {} + Err(err) => { + println!( + "Network Event Error : {}", + err.to_string() + ); + } + }; + } + false => {} + } } } } else { @@ -756,7 +819,16 @@ pub async fn get_socket_address( } }; - let addresses = parse_addresses(&s); // assumes parse_addresses: &str -> Vec + let addresses: Vec = { + let temp = parse_addresses(&s); + temp.iter() + .filter_map(|a| match a { + SocketAddr::V4(_) => Some(*a), + SocketAddr::V6(_) => None, + }) + .collect() + }; + if addresses.is_empty() { return Err(FetchSocketAddressError::NoRegisteredAddresses); } else if !addresses.iter().any(|a| matches!(a, SocketAddr::V4(_))) { diff --git a/client-network/src/message_handling.rs b/client-network/src/message_handling.rs index 96a41db..c10530c 100644 --- a/client-network/src/message_handling.rs +++ b/client-network/src/message_handling.rs @@ -314,7 +314,7 @@ pub fn parse_message( ERROR => { if let Ok(err_received) = - String::from_utf8(received_message[LENGTH..(msg_length + LENGTH + 4)].to_vec()) + String::from_utf8(received_message[LENGTH..(msg_length + LENGTH)].to_vec()) { let err_msg = format!("Error received from peer {} : {}", ip, err_received); let _ = cmd_tx_clone.send(NetworkEvent::Error(err_msg, "".to_owned())); diff --git a/client-network/src/registration.rs b/client-network/src/registration.rs index 7df1e06..a94c11e 100644 --- a/client-network/src/registration.rs +++ b/client-network/src/registration.rs @@ -2,6 +2,7 @@ use crate::NetworkEvent; use crate::P2PSharedData; use crate::cryptographic_signature::CryptographicSignature; use crate::get_server_address; +use crate::get_socket_address; use crate::message_handling::EventType; use crate::messages_structure::construct_message; use crate::server_communication::generate_id; @@ -55,47 +56,71 @@ pub async fn perform_handshake( username: String, ip: String, event_tx: Sender, - is_server_handshake: bool, -) { + is_server_handshake: (bool, String), +) -> bool { println!("username: {}, ip: {}", username.clone(), ip.clone()); let crypto_pair = sd.cryptopair_ref(); let senders = sd.senders_ref(); let id = generate_id(); - let server_addr_query = get_server_address(username.clone(), ip.clone()); - match server_addr_query.await { - Some(sockaddr_bytes) => { - sd.set_servername(username); - // first: &SocketAddr - let mut payload = Vec::new(); - payload.extend_from_slice(&0u32.to_be_bytes()); - payload.extend_from_slice(&crypto_pair.username.clone().as_bytes()); - let hello_handshake = construct_message(1, payload, id, crypto_pair); - if is_server_handshake { - sd.add_message(id, EventType::Hello); - sd.set_serveraddress(sockaddr_bytes.to_string()); - } else { - sd.add_message(id, EventType::HelloThenRootRequest); - } - match hello_handshake { - Some(handshake_message) => { - senders.send_dispatch( - handshake_message, - sockaddr_bytes.to_string(), - is_server_handshake, - sd.messages_list(), - ); + let address = { + if is_server_handshake.0 { + is_server_handshake.1 + } else { + let server_addr_query = + get_socket_address(username.clone(), ip.clone(), Some(sd)).await; + + match server_addr_query { + Ok(sockaddr_bytes) => sockaddr_bytes.to_string(), + Err(err_msg) => { + match event_tx.send(NetworkEvent::Error( + err_msg.to_string(), + username.to_owned(), + )) { + Ok(_) => {} + Err(err) => { + println!("Network Event Error : {}", err.to_string()); + } + } + "".to_string() } - None => {} } } - None => { - let err_msg = format!("failed to retreive socket address:").to_string(); - let res = event_tx.send(NetworkEvent::Error(err_msg, "".to_owned())); - } + }; + + if address.eq(&"".to_string()) { + return false; } + let mut payload = Vec::new(); + payload.extend_from_slice(&0u32.to_be_bytes()); + payload.extend_from_slice(&crypto_pair.username.clone().as_bytes()); + let hello_handshake = construct_message(1, payload, id, crypto_pair); + if is_server_handshake.0 { + sd.add_message(id, EventType::Hello); + } else { + sd.add_message(id, EventType::HelloThenRootRequest); + } + + match hello_handshake { + Some(handshake_message) => { + senders.send_dispatch( + handshake_message, + address, + is_server_handshake.0, + sd.messages_list(), + ); + } + None => {} + } + + //let server_addr_query = get_socket_address(username.clone(), ip.clone(), Some(sd)).await; + //match server_addr_query { + // Ok(sockaddr_bytes) => {} + // Err(err_msg) => {} + //} + /*let mut list = messages_list.lock().expect("Failed to lock messages_list"); match list.get(&id) { Some(_) => { @@ -112,6 +137,7 @@ pub async fn perform_handshake( let hello_handshake_received = UDPMessage::parse(buf.to_vec()); hello_handshake_received.display();*/ //TODO + return true; } #[cfg(test)] From fc7886c94c0b61ec34df55d59e01de22668d3770 Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 00:54:54 +0100 Subject: [PATCH 2/9] progress bar --- client-gui/src/gui_app.rs | 85 ++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index ae916af..7c5faf4 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -5,8 +5,9 @@ use client_network::{ }; use crossbeam_channel::{Receiver, Sender}; use egui::{ - Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Layout, Response, - ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, ViewportCommand, + Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Layout, + ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, Vec2, + ViewportCommand, }; use std::collections::HashSet; use std::{collections::HashMap, fmt::format, io::Seek}; @@ -54,6 +55,13 @@ pub struct P2PClientApp { current_downloading_file_map: MerkleTree, remaining_chunks: HashSet<[u8; 32]>, + + // total number of chunks expected for the current download (set when download starts) + current_total_chunks: Option, + + // number of chunks received so far (count of removed remaining_chunks) + current_received_chunks: usize, + root_downloading_file: String, } @@ -87,6 +95,8 @@ impl P2PClientApp { active_server: "".to_string(), shared_tree: generate_base_tree(), current_downloading_file_map: current_downloading_file_map, + current_total_chunks: None, + current_received_chunks: 0, root_downloading_file: "".to_string(), remaining_chunks: HashSet::new(), } @@ -103,6 +113,14 @@ impl P2PClientApp { pub fn clear_success(&mut self) { self.success_message = None; } + + fn set_current_total_chunks(&mut self, len: Option) { + self.current_total_chunks = len + } + + fn set_current_received_chunks(&mut self, arg: usize) { + self.current_received_chunks = arg + } } // --- eframe::App Trait Implementation --- @@ -291,6 +309,7 @@ impl eframe::App for P2PClientApp { true, )); self.remaining_chunks.insert(entry); + self.set_current_total_chunks(Some(self.remaining_chunks.len())); } self.remaining_chunks.remove(&hash); } @@ -299,6 +318,13 @@ impl eframe::App for P2PClientApp { } _ => {} } + + if let Some(total) = self.current_total_chunks { + // recompute received (safer than incrementing) + let received = total.saturating_sub(self.remaining_chunks.len()); + self.current_received_chunks = received; + } + if self.remaining_chunks.is_empty() { /*let file = OpenOptions::new() .append(true) @@ -323,6 +349,8 @@ impl eframe::App for P2PClientApp { } } }*/ + self.current_total_chunks = None; + self.current_received_chunks = 0; println!("bigfile téléchargé"); } } @@ -403,26 +431,35 @@ impl eframe::App for P2PClientApp { TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { ui.horizontal(|ui| { match self.server_status { - ServerStatus::Loading => { - ui.spinner(); - } - ServerStatus::Connected => { - ui.label("Registered but no server peer chosen..."); - } - ServerStatus::NotConnected => { - ui.label("No connection.."); - } - ServerStatus::ConnectedHandshake => { - let str = format!("📡"); - ui.label(str); - } + ServerStatus::Loading => ui.spinner(), + ServerStatus::Connected => ui.label("Registered but no server peer chosen..."), + ServerStatus::NotConnected => ui.label("No connection.."), + ServerStatus::ConnectedHandshake => ui.label("📡"), + }; + + ui.add_space(8.0); // small gap + + // desired progress bar width + let bar_width = 220.0f32; + // push it to the right by adding space equal to remaining width minus bar width + let push = (ui.available_width() - bar_width).max(0.0); + ui.add_space(push); + + if let Some(total) = self.current_total_chunks { + let received = self.current_received_chunks; + let frac = if total == 0 { + 1.0 + } else { + received as f32 / total as f32 + }; + + ui.add( + ProgressBar::new(frac) + .show_percentage() + .animate(true) + .desired_height(10.0), + ); } - ui.add_space(ui.available_width() - 30.0); - // formater mm:ss - let secs = self.remaining.as_secs(); - let minutes = secs / 60; - let seconds = secs % 60; - ui.label(format!("{:02}:{:02}", minutes, seconds)); }); }); @@ -685,7 +722,7 @@ impl eframe::App for P2PClientApp { ui.separator(); if let Some(active_peer) = &self.active_peer { - if let Some(tree) = self.loaded_fs.get(active_peer) { + if let Some(tree) = self.loaded_fs.clone().get(active_peer) { ScrollArea::vertical().show(ui, |ui| { // Start drawing the tree from the root hash self.draw_file_tree(ui, tree); @@ -745,7 +782,7 @@ impl eframe::App for P2PClientApp { // --- Helper for Drawing the Recursive File Tree --- impl P2PClientApp { - fn draw_file_tree(&self, ui: &mut Ui, tree: &MerkleTree) { + fn draw_file_tree(&mut self, ui: &mut Ui, tree: &MerkleTree) { assert!(self.active_peer.is_some()); assert!( self.loaded_fs @@ -762,7 +799,7 @@ impl P2PClientApp { } fn draw_file_node( - &self, + &mut self, ui: &mut Ui, to_draw: NodeHash, tree: &MerkleTree, From 61edd8cd24e28aea8269e552cc0caf3d7d5c8334 Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 01:31:15 +0100 Subject: [PATCH 3/9] [feature] server selection --- client-gui/src/gui_app.rs | 165 ++++++++++++++++++++++++++++++++------ 1 file changed, 139 insertions(+), 26 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index ae916af..1aafbf4 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -46,8 +46,6 @@ pub struct P2PClientApp { server_status: ServerStatus, - show_network_popup: bool, // gérer selon besoin - error_message: Option<(String, String)>, // Some(message) -> afficher, None -> rien success_message: Option<(String, String)>, // Some(message) -> afficher, None -> rien active_server: String, @@ -55,6 +53,8 @@ pub struct P2PClientApp { current_downloading_file_map: MerkleTree, remaining_chunks: HashSet<[u8; 32]>, root_downloading_file: String, + show_network_window: bool, + show_choose_server_window: bool, } impl P2PClientApp { @@ -80,7 +80,6 @@ impl P2PClientApp { loaded_fs, active_peer: None, server_status: ServerStatus::NotConnected, - show_network_popup: false, error_message: None, success_message: None, connect_name_input: "bob".to_string(), @@ -89,6 +88,8 @@ impl P2PClientApp { current_downloading_file_map: current_downloading_file_map, root_downloading_file: "".to_string(), remaining_chunks: HashSet::new(), + show_network_window: false, + show_choose_server_window: false, } } pub fn show_error(&mut self, msg: impl Into, peer_username: impl Into) { @@ -241,6 +242,7 @@ impl eframe::App for P2PClientApp { NetworkEvent::Connected(ip) => { self.server_status = ServerStatus::Connected; self.connected_address = ip.clone(); + self.show_choose_server_window = true; let _ = self.network_cmd_tx.send(NetworkCommand::FetchPeerList( self.connected_address.clone(), )); @@ -341,6 +343,68 @@ impl eframe::App for P2PClientApp { } } + if self.show_choose_server_window { + egui::Window::new("Choose the server") + .resizable(false) + .show(ctx, |ui| { + ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.style_mut().visuals.widgets.inactive.bg_fill = + ui.style().visuals.widgets.inactive.bg_fill; // no-op to get mutable borrow + if self.known_peers.is_empty() { + ui.add_space(10.0); + ui.label("No active peers."); + } else { + for peer in &self.known_peers { + let is_active = + self.active_peer.as_ref().map_or(false, |id| id == &peer.0); // if peer.id == self.active_peer_id + + // place spinner to the right of the label + ui.horizontal(|ui| { + // Use same width for the label widget as the selectable we already created: + // Recreate selectable inline so both label and spinner share the same row. + let resp = if &self.active_server == &peer.0 { + // draw with frame inline + let frame = Frame { + fill: Color32::DARK_BLUE, + stroke: Stroke::default(), + corner_radius: CornerRadius::from(0.5), + ..Default::default() + }; + frame + .show(ui, |ui| { + ui.selectable_label( + is_active, + format!("{}", peer.0), + ) + }) + .inner + } else { + ui.selectable_label(is_active, format!("{}", peer.0)) + }; + + ui.add_space(4.0); // small gap + + // use resp (click handling etc.) + if resp.clicked() { + self.active_server = peer.0.to_string(); + let res = self.network_cmd_tx.send( + NetworkCommand::ServerHandshake( + peer.0.to_string(), + self.connected_address.clone(), + ), + ); + ui.close(); + self.show_choose_server_window = false; + } + }); + } + } + }); + }); + } + // 2. Menu Bar TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { @@ -355,7 +419,55 @@ impl eframe::App for P2PClientApp { } }); - ui.menu_button("Network", |ui| { + //ui.menu_button("Network", |ui| { + // match self.server_status { + // ServerStatus::Connected | ServerStatus::ConnectedHandshake => { + // let desired = egui::vec2(300.0, 0.0); // width 300, auto-height if 0 + // ui.set_min_size(desired); + // ui.vertical(|ui| { + // if ui.button("Disconnect").clicked() { + // println!("Disconnecting..."); + // let _ = self.network_cmd_tx.send(NetworkCommand::Disconnect()); + // self.server_status = ServerStatus::NotConnected; + // self.remaining = std::time::Duration::from_secs(0); + // self.timer_started = false; + // ui.close(); + // } + // }); + // } + // ServerStatus::NotConnected => { + // let desired = egui::vec2(0.0, 0.0); // width 300, auto-height if 0 + // ui.set_min_size(desired); + // ui.vertical(|ui| { + // ui.horizontal(|ui| { + // ui.label("Server IP:"); + // ui.text_edit_singleline(&mut self.connect_address_input); + // }); + // ui.horizontal(|ui| { + // ui.label("Name:"); + // ui.text_edit_singleline(&mut self.connect_name_input); + // }); + // if ui.button("Connect").clicked() { + // let addr = self.connect_address_input.clone(); + // let name = self.connect_name_input.clone(); + // let _ = self + // .network_cmd_tx + // .send(NetworkCommand::ConnectToServerPut(addr, name)); + // self.server_status = ServerStatus::Loading; + // ui.close(); + // } + // }); + // } + // _ => {} + // } + //}); + // open trigger (e.g., in a menu bar) + + if ui.button("Network").clicked() { + self.show_network_window = true; + } + + if self.show_network_window { match self.server_status { ServerStatus::Connected | ServerStatus::ConnectedHandshake => { let desired = egui::vec2(300.0, 0.0); // width 300, auto-height if 0 @@ -367,36 +479,37 @@ impl eframe::App for P2PClientApp { self.server_status = ServerStatus::NotConnected; self.remaining = std::time::Duration::from_secs(0); self.timer_started = false; - ui.close(); + self.show_network_window = false; } }); } ServerStatus::NotConnected => { - let desired = egui::vec2(0.0, 0.0); // width 300, auto-height if 0 - ui.set_min_size(desired); - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.label("Server IP:"); - ui.text_edit_singleline(&mut self.connect_address_input); + egui::Window::new("Network") + .resizable(false) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Server IP:"); + ui.text_edit_singleline(&mut self.connect_address_input); + }); + ui.horizontal(|ui| { + ui.label("Name:"); + ui.text_edit_singleline(&mut self.connect_name_input); + }); + if ui.button("Connect").clicked() { + let addr = self.connect_address_input.clone(); + let name = self.connect_name_input.clone(); + let _ = self + .network_cmd_tx + .send(NetworkCommand::ConnectToServerPut(addr, name)); + self.server_status = ServerStatus::Loading; + ui.close(); + self.show_network_window = false; + } }); - ui.horizontal(|ui| { - ui.label("Name:"); - ui.text_edit_singleline(&mut self.connect_name_input); - }); - if ui.button("Connect").clicked() { - let addr = self.connect_address_input.clone(); - let name = self.connect_name_input.clone(); - let _ = self - .network_cmd_tx - .send(NetworkCommand::ConnectToServerPut(addr, name)); - self.server_status = ServerStatus::Loading; - ui.close(); - } - }); } _ => {} } - }); + } }); }); From 2283ef5f334152733558d13ce540964c64bb5cff Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 00:54:54 +0100 Subject: [PATCH 4/9] progress bar --- client-gui/src/gui_app.rs | 85 ++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 1aafbf4..58c6b01 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -5,8 +5,9 @@ use client_network::{ }; use crossbeam_channel::{Receiver, Sender}; use egui::{ - Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Layout, Response, - ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, ViewportCommand, + Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Layout, + ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, Vec2, + ViewportCommand, }; use std::collections::HashSet; use std::{collections::HashMap, fmt::format, io::Seek}; @@ -52,6 +53,13 @@ pub struct P2PClientApp { current_downloading_file_map: MerkleTree, remaining_chunks: HashSet<[u8; 32]>, + + // total number of chunks expected for the current download (set when download starts) + current_total_chunks: Option, + + // number of chunks received so far (count of removed remaining_chunks) + current_received_chunks: usize, + root_downloading_file: String, show_network_window: bool, show_choose_server_window: bool, @@ -86,6 +94,8 @@ impl P2PClientApp { active_server: "".to_string(), shared_tree: generate_base_tree(), current_downloading_file_map: current_downloading_file_map, + current_total_chunks: None, + current_received_chunks: 0, root_downloading_file: "".to_string(), remaining_chunks: HashSet::new(), show_network_window: false, @@ -104,6 +114,14 @@ impl P2PClientApp { pub fn clear_success(&mut self) { self.success_message = None; } + + fn set_current_total_chunks(&mut self, len: Option) { + self.current_total_chunks = len + } + + fn set_current_received_chunks(&mut self, arg: usize) { + self.current_received_chunks = arg + } } // --- eframe::App Trait Implementation --- @@ -293,6 +311,7 @@ impl eframe::App for P2PClientApp { true, )); self.remaining_chunks.insert(entry); + self.set_current_total_chunks(Some(self.remaining_chunks.len())); } self.remaining_chunks.remove(&hash); } @@ -301,6 +320,13 @@ impl eframe::App for P2PClientApp { } _ => {} } + + if let Some(total) = self.current_total_chunks { + // recompute received (safer than incrementing) + let received = total.saturating_sub(self.remaining_chunks.len()); + self.current_received_chunks = received; + } + if self.remaining_chunks.is_empty() { /*let file = OpenOptions::new() .append(true) @@ -325,6 +351,8 @@ impl eframe::App for P2PClientApp { } } }*/ + self.current_total_chunks = None; + self.current_received_chunks = 0; println!("bigfile téléchargé"); } } @@ -516,26 +544,35 @@ impl eframe::App for P2PClientApp { TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { ui.horizontal(|ui| { match self.server_status { - ServerStatus::Loading => { - ui.spinner(); - } - ServerStatus::Connected => { - ui.label("Registered but no server peer chosen..."); - } - ServerStatus::NotConnected => { - ui.label("No connection.."); - } - ServerStatus::ConnectedHandshake => { - let str = format!("📡"); - ui.label(str); - } + ServerStatus::Loading => ui.spinner(), + ServerStatus::Connected => ui.label("Registered but no server peer chosen..."), + ServerStatus::NotConnected => ui.label("No connection.."), + ServerStatus::ConnectedHandshake => ui.label("📡"), + }; + + ui.add_space(8.0); // small gap + + // desired progress bar width + let bar_width = 220.0f32; + // push it to the right by adding space equal to remaining width minus bar width + let push = (ui.available_width() - bar_width).max(0.0); + ui.add_space(push); + + if let Some(total) = self.current_total_chunks { + let received = self.current_received_chunks; + let frac = if total == 0 { + 1.0 + } else { + received as f32 / total as f32 + }; + + ui.add( + ProgressBar::new(frac) + .show_percentage() + .animate(true) + .desired_height(10.0), + ); } - ui.add_space(ui.available_width() - 30.0); - // formater mm:ss - let secs = self.remaining.as_secs(); - let minutes = secs / 60; - let seconds = secs % 60; - ui.label(format!("{:02}:{:02}", minutes, seconds)); }); }); @@ -798,7 +835,7 @@ impl eframe::App for P2PClientApp { ui.separator(); if let Some(active_peer) = &self.active_peer { - if let Some(tree) = self.loaded_fs.get(active_peer) { + if let Some(tree) = self.loaded_fs.clone().get(active_peer) { ScrollArea::vertical().show(ui, |ui| { // Start drawing the tree from the root hash self.draw_file_tree(ui, tree); @@ -858,7 +895,7 @@ impl eframe::App for P2PClientApp { // --- Helper for Drawing the Recursive File Tree --- impl P2PClientApp { - fn draw_file_tree(&self, ui: &mut Ui, tree: &MerkleTree) { + fn draw_file_tree(&mut self, ui: &mut Ui, tree: &MerkleTree) { assert!(self.active_peer.is_some()); assert!( self.loaded_fs @@ -875,7 +912,7 @@ impl P2PClientApp { } fn draw_file_node( - &self, + &mut self, ui: &mut Ui, to_draw: NodeHash, tree: &MerkleTree, From 929c386b09d5d1b5cba72c467452e7dcf953fc7a Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 01:39:15 +0100 Subject: [PATCH 5/9] magnifique --- client-gui/src/gui_app.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 58c6b01..681179a 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -5,7 +5,7 @@ use client_network::{ }; use crossbeam_channel::{Receiver, Sender}; use egui::{ - Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Layout, + Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Id, Layout, ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, Vec2, ViewportCommand, }; @@ -372,8 +372,28 @@ impl eframe::App for P2PClientApp { } if self.show_choose_server_window { + let full_rect = ctx.input(|i| i.screen_rect()); + let modal_size = egui::vec2(400.0, 160.0); + let modal_pos = (full_rect.center() - modal_size * 0.5).to_vec2(); + + // 1) blocker (background order) — captures input except modal area + egui::Area::new(Id::new("modal_blocker_bg")) + .order(egui::Order::Background) + .show(ctx, |ui| { + let painter = ui.painter(); + painter.rect_filled(full_rect, 0.0, egui::Color32::from_black_alpha(160)); + + // create an interactable that covers the full rect so clicks are eaten + // but leave the modal rect uncovered by marking it non-interactive below. + let sense = egui::Sense::click_and_drag(); + ui.allocate_exact_size(full_rect.size(), sense); + }); egui::Window::new("Choose the server") .resizable(false) + .collapsible(false) + .title_bar(true) + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO) + .fixed_size(modal_size) .show(ctx, |ui| { ScrollArea::vertical() .auto_shrink([false; 2]) From 54cd6ebc41932740e21ce91c5cd59ef32026029c Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 02:16:22 +0100 Subject: [PATCH 6/9] tidy --- client-gui/src/gui_app.rs | 206 ++++++-------------------------------- client-network/src/lib.rs | 1 - 2 files changed, 30 insertions(+), 177 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 681179a..231e72d 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -1,18 +1,16 @@ use client_network::{ ChunkNode, MerkleNode, MerkleTree, NetworkCommand, NetworkEvent, NodeHash, - big_or_chunk_to_file, filename_to_string, generate_base_tree, node_hash_to_hex_string, - node_to_file, remove_null_bytes, + big_or_chunk_to_file, generate_base_tree, node_hash_to_hex_string, remove_null_bytes, }; use crossbeam_channel::{Receiver, Sender}; use egui::{ Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Id, Layout, - ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, Vec2, - ViewportCommand, + ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, ViewportCommand, }; +use std::collections::HashMap; use std::collections::HashSet; -use std::{collections::HashMap, fmt::format, io::Seek}; -use std::fs::{File, OpenOptions, create_dir}; +use std::fs::{OpenOptions, create_dir}; enum ServerStatus { Loading, @@ -21,24 +19,19 @@ enum ServerStatus { ConnectedHandshake, } -// --- Main Application Struct --- pub struct P2PClientApp { remaining: std::time::Duration, // temps restant last_update: std::time::Instant, // pour calculer delta timer_started: bool, - // Communication channels network_cmd_tx: Sender, network_event_rx: Receiver, - // GUI State - status_message: String, known_peers: Vec<(String, bool)>, loading_peers: Vec, connect_address_input: String, connected_address: String, connect_name_input: String, - // Key: Parent Directory Hash (String), Value: List of children FileNode loaded_fs: HashMap, shared_tree: MerkleTree, @@ -67,12 +60,8 @@ pub struct P2PClientApp { impl P2PClientApp { pub fn new(cmd_tx: Sender, event_rx: Receiver) -> Self { - //let (root_hash, tree_content) = MerkleNode::generate_base_tree(); - - let mut loaded_fs = HashMap::new(); - let mut current_downloading_file_map = MerkleTree::new(HashMap::new(), [0; 32]); - //let tree = MerkleTree::new(tree_content, root_hash); - //loaded_fs.insert("bob".to_string(), tree); + let loaded_fs = HashMap::new(); + let current_downloading_file_map = MerkleTree::new(HashMap::new(), [0; 32]); Self { remaining: std::time::Duration::from_secs(0), @@ -80,7 +69,6 @@ impl P2PClientApp { last_update: std::time::Instant::now(), network_cmd_tx: cmd_tx, network_event_rx: event_rx, - status_message: "Client Initialized. Awaiting network status...".to_string(), known_peers: Vec::new(), loading_peers: Vec::new(), connect_address_input: "https://jch.irif.fr:8443".to_string(), @@ -114,18 +102,8 @@ impl P2PClientApp { pub fn clear_success(&mut self) { self.success_message = None; } - - fn set_current_total_chunks(&mut self, len: Option) { - self.current_total_chunks = len - } - - fn set_current_received_chunks(&mut self, arg: usize) { - self.current_received_chunks = arg - } } -// --- eframe::App Trait Implementation --- - impl eframe::App for P2PClientApp { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { if matches!(self.server_status, ServerStatus::Connected) && !self.timer_started { @@ -134,7 +112,6 @@ impl eframe::App for P2PClientApp { self.timer_started = true; } - // in update (every frame) let now = std::time::Instant::now(); let delta = now.saturating_duration_since(self.last_update); self.last_update = now; @@ -145,18 +122,8 @@ impl eframe::App for P2PClientApp { self.remaining = self.remaining.saturating_sub(delta); } - // 1. Process incoming Network Events - // We poll the channel and update the GUI state for every event received. while let Ok(event) = self.network_event_rx.try_recv() { match event { - NetworkEvent::PeerConnected(addr) => { - todo!(); - - self.status_message = format!("✅ Peer connected: {}", addr); - if !self.known_peers.contains(&(addr, true)) { - self.known_peers.push((addr, true)); - } - } NetworkEvent::RootRequest(addr) => { let root = self.shared_tree.root; let _ = self @@ -182,8 +149,6 @@ impl eframe::App for P2PClientApp { } } NetworkEvent::PeerListUpdated(peers) => { - //todo!(); - self.known_peers = peers; } @@ -223,14 +188,6 @@ impl eframe::App for P2PClientApp { } } NetworkEvent::FileTreeRootReceived(peer_id, root_hash) => { - // todo!(); - - /*self.status_message = format!( - "🔄 Received Merkle Root from {}: {}", - peer_id, - &root_hash[..8] - );*/ - if let Ok(chunknode) = ChunkNode::new(Vec::new()) { let data_map: HashMap = HashMap::new(); //data_map.insert(root_hash, MerkleNode::Chunk(chunknode)); @@ -249,13 +206,6 @@ impl eframe::App for P2PClientApp { println!("tree created"); } - - //self.active_peer_id = Some(peer_id.clone()); - - // Request the content of the root directory immediately - /*let _ = self - .network_cmd_tx - .send(NetworkCommand::RequestDirectoryContent(peer_id, root_hash));*/ } NetworkEvent::Connected(ip) => { self.server_status = ServerStatus::Connected; @@ -311,11 +261,11 @@ impl eframe::App for P2PClientApp { true, )); self.remaining_chunks.insert(entry); - self.set_current_total_chunks(Some(self.remaining_chunks.len())); + self.current_total_chunks = Some(self.remaining_chunks.len()); } self.remaining_chunks.remove(&hash); } - MerkleNode::Chunk(chunk) => { + MerkleNode::Chunk(_) => { self.remaining_chunks.remove(&hash); } _ => {} @@ -328,30 +278,6 @@ impl eframe::App for P2PClientApp { } if self.remaining_chunks.is_empty() { - /*let file = OpenOptions::new() - .append(true) - .create(true) - .open(self.root_downloading_file.clone()); - - if let Some(current) = self - .current_downloading_file_map - .data - .get(&self.current_downloading_file_map.root) - { - match file { - Ok(mut fileok) => { - big_or_chunk_to_file( - &self.current_downloading_file_map, - current, - &mut fileok, - ); - } - Err(e) => { - eprintln!("error creaation file: {}", e); - } - } - }*/ - self.current_total_chunks = None; self.current_received_chunks = 0; println!("bigfile téléchargé"); } @@ -366,25 +292,26 @@ impl eframe::App for P2PClientApp { self.server_status = ServerStatus::NotConnected; let err_msg = format!("Failed to connect to the server: {}", err); self.show_error(err_msg, ""); - let res = self.network_cmd_tx.send(NetworkCommand::ResetServerPeer()); + match self.network_cmd_tx.send(NetworkCommand::ResetServerPeer()) { + Ok(_) => {} + Err(err) => { + println!("GUI Error : {}", err.to_string()); + } + }; } } } if self.show_choose_server_window { - let full_rect = ctx.input(|i| i.screen_rect()); + let full_rect = ctx.input(|i| i.content_rect()); let modal_size = egui::vec2(400.0, 160.0); - let modal_pos = (full_rect.center() - modal_size * 0.5).to_vec2(); - // 1) blocker (background order) — captures input except modal area egui::Area::new(Id::new("modal_blocker_bg")) .order(egui::Order::Background) .show(ctx, |ui| { let painter = ui.painter(); painter.rect_filled(full_rect, 0.0, egui::Color32::from_black_alpha(160)); - // create an interactable that covers the full rect so clicks are eaten - // but leave the modal rect uncovered by marking it non-interactive below. let sense = egui::Sense::click_and_drag(); ui.allocate_exact_size(full_rect.size(), sense); }); @@ -437,12 +364,17 @@ impl eframe::App for P2PClientApp { // use resp (click handling etc.) if resp.clicked() { self.active_server = peer.0.to_string(); - let res = self.network_cmd_tx.send( + match self.network_cmd_tx.send( NetworkCommand::ServerHandshake( peer.0.to_string(), self.connected_address.clone(), ), - ); + ) { + Ok(_) => {} + Err(e) => { + println!("GUI Error : {}", e.to_string()); + } + }; ui.close(); self.show_choose_server_window = false; } @@ -457,60 +389,13 @@ impl eframe::App for P2PClientApp { TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { - if ui.button("Settings").clicked() { - //show settings - } + if ui.button("Settings").clicked() {} if ui.button("Quit").clicked() { - // Use ViewportCommand to request a close ctx.send_viewport_cmd(ViewportCommand::Close); } }); - //ui.menu_button("Network", |ui| { - // match self.server_status { - // ServerStatus::Connected | ServerStatus::ConnectedHandshake => { - // let desired = egui::vec2(300.0, 0.0); // width 300, auto-height if 0 - // ui.set_min_size(desired); - // ui.vertical(|ui| { - // if ui.button("Disconnect").clicked() { - // println!("Disconnecting..."); - // let _ = self.network_cmd_tx.send(NetworkCommand::Disconnect()); - // self.server_status = ServerStatus::NotConnected; - // self.remaining = std::time::Duration::from_secs(0); - // self.timer_started = false; - // ui.close(); - // } - // }); - // } - // ServerStatus::NotConnected => { - // let desired = egui::vec2(0.0, 0.0); // width 300, auto-height if 0 - // ui.set_min_size(desired); - // ui.vertical(|ui| { - // ui.horizontal(|ui| { - // ui.label("Server IP:"); - // ui.text_edit_singleline(&mut self.connect_address_input); - // }); - // ui.horizontal(|ui| { - // ui.label("Name:"); - // ui.text_edit_singleline(&mut self.connect_name_input); - // }); - // if ui.button("Connect").clicked() { - // let addr = self.connect_address_input.clone(); - // let name = self.connect_name_input.clone(); - // let _ = self - // .network_cmd_tx - // .send(NetworkCommand::ConnectToServerPut(addr, name)); - // self.server_status = ServerStatus::Loading; - // ui.close(); - // } - // }); - // } - // _ => {} - // } - //}); - // open trigger (e.g., in a menu bar) - if ui.button("Network").clicked() { self.show_network_window = true; } @@ -636,41 +521,9 @@ impl eframe::App for P2PClientApp { let is_active = self.active_peer.as_ref().map_or(false, |id| id == &peer.0); // if peer.id == self.active_peer_id - let selectable: Response; - // if &self.active_server == &peer.0 { - // // Create a frame with green background and render the selectable inside it. - // // Adjust rounding, padding and stroke as desired. - // let frame = Frame { - // fill: Color32::DARK_BLUE, - // stroke: Stroke::default(), - // corner_radius: CornerRadius::from(0.5), - // ..Default::default() - // }; - // let internal = frame.show(ui, |ui| { - // // horizontal row: label on the left, spinner on the right - // ui.horizontal(|ui| { - // // let selectable label take remaining space - // ui.with_layout(Layout::left_to_right(Align::Center), |ui| { - // ui.add_space(0.0); // ensure layout established - // return ui.selectable_label( - // is_active, - // format!("{}", peer.0), - // ); - // }) - // .inner // return Response from the inner closure if your egui version does so - // }) - // }); - // selectable = internal.inner.inner; - // } else { - // selectable = ui.selectable_label(is_active, format!("{}", peer.0)); - // } - // place spinner to the right of the label ui.horizontal(|ui| { - // Use same width for the label widget as the selectable we already created: - // Recreate selectable inline so both label and spinner share the same row. let resp = if &self.active_server == &peer.0 { - // draw with frame inline let frame = Frame { fill: Color32::DARK_BLUE, stroke: Stroke::default(), @@ -692,7 +545,6 @@ impl eframe::App for P2PClientApp { ui.add_space(4.0); // small gap if self.loading_peers.contains(&peer.0) { - // push spinner to right by expanding a spacer before it ui.with_layout( Layout::right_to_left(Align::Center), |ui| { @@ -701,9 +553,7 @@ impl eframe::App for P2PClientApp { ); } - // use resp (click handling etc.) if resp.clicked() { - // switch to displaying this peer's tree self.active_peer = Some(peer.0.clone()); // Request root content if not loaded if !self @@ -720,7 +570,6 @@ impl eframe::App for P2PClientApp { } } resp.context_menu(|ui| { - // ... action match self.server_status { ServerStatus::Connected => { if ui @@ -728,12 +577,17 @@ impl eframe::App for P2PClientApp { .clicked() { self.active_server = peer.0.to_string(); - let res = self.network_cmd_tx.send( + match self.network_cmd_tx.send( NetworkCommand::ServerHandshake( peer.0.to_string(), self.connected_address.clone(), ), - ); + ) { + Ok(_) => {} + Err(e) => { + println!("GUI Error : {}", e.to_string()); + }, + }; } } _ => {} diff --git a/client-network/src/lib.rs b/client-network/src/lib.rs index 635eb34..9edbb55 100644 --- a/client-network/src/lib.rs +++ b/client-network/src/lib.rs @@ -196,7 +196,6 @@ pub enum NetworkEvent { Disconnected(), Error(String, String), Success(String, String), - PeerConnected(String), PeerListUpdated(Vec<(String, bool)>), FileTreeReceived([u8; 32], MerkleNode, String), // peer_id, content DataReceived([u8; 32], MerkleNode, String), From c0708fc4b9a9f2d91341241f90961d1911e9c83f Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 03:17:47 +0100 Subject: [PATCH 7/9] tidy --- client-gui/src/gui_app.rs | 192 ++++++++++++++------------------------ 1 file changed, 72 insertions(+), 120 deletions(-) diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 231e72d..b657e9f 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -5,7 +5,7 @@ use client_network::{ use crossbeam_channel::{Receiver, Sender}; use egui::{ Align, CentralPanel, CollapsingHeader, Color32, Context, CornerRadius, Frame, Id, Layout, - ProgressBar, Response, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, ViewportCommand, + ProgressBar, ScrollArea, SidePanel, Stroke, TopBottomPanel, Ui, ViewportCommand, }; use std::collections::HashMap; use std::collections::HashSet; @@ -278,6 +278,7 @@ impl eframe::App for P2PClientApp { } if self.remaining_chunks.is_empty() { + self.current_total_chunks = None; self.current_received_chunks = 0; println!("bigfile téléchargé"); } @@ -431,12 +432,17 @@ impl eframe::App for P2PClientApp { if ui.button("Connect").clicked() { let addr = self.connect_address_input.clone(); let name = self.connect_name_input.clone(); - let _ = self - .network_cmd_tx - .send(NetworkCommand::ConnectToServerPut(addr, name)); + let _ = self.network_cmd_tx.send( + NetworkCommand::ConnectToServerPut( + addr, + name.to_string(), + ), + ); self.server_status = ServerStatus::Loading; ui.close(); self.show_network_window = false; + self.loaded_fs + .insert(name.to_string(), self.shared_tree.clone()); } }); } @@ -520,56 +526,69 @@ impl eframe::App for P2PClientApp { for peer in &self.known_peers { let is_active = self.active_peer.as_ref().map_or(false, |id| id == &peer.0); // if peer.id == self.active_peer_id - - // place spinner to the right of the label - ui.horizontal(|ui| { - let resp = if &self.active_server == &peer.0 { - let frame = Frame { - fill: Color32::DARK_BLUE, - stroke: Stroke::default(), - corner_radius: CornerRadius::from(0.5), - ..Default::default() - }; - frame - .show(ui, |ui| { - ui.selectable_label( - is_active, - format!("{}", peer.0), - ) - }) - .inner - } else { - ui.selectable_label(is_active, format!("{}", peer.0)) - }; - - ui.add_space(4.0); // small gap - - if self.loading_peers.contains(&peer.0) { - ui.with_layout( - Layout::right_to_left(Align::Center), - |ui| { - ui.spinner(); - }, + // + if peer.0.eq(&self.connect_name_input) { + ui.horizontal(|ui| { + let resp = ui.selectable_label( + is_active, + format!("{} (you)", peer.0), ); - } - if resp.clicked() { - self.active_peer = Some(peer.0.clone()); - // Request root content if not loaded - if !self - .loaded_fs - .contains_key(self.active_peer.as_ref().unwrap()) - { - //todo!(); - let _ = - self.network_cmd_tx.send(NetworkCommand::Discover( - peer.0.clone(), - "root".to_string(), - self.connected_address.clone(), - )); + if resp.clicked() { + self.active_peer = Some(peer.0.clone()); } - } - resp.context_menu(|ui| { + }); + } else { + // place spinner to the right of the label + ui.horizontal(|ui| { + let resp = if &self.active_server == &peer.0 { + let frame = Frame { + fill: Color32::DARK_BLUE, + stroke: Stroke::default(), + corner_radius: CornerRadius::from(0.5), + ..Default::default() + }; + frame + .show(ui, |ui| { + ui.selectable_label( + is_active, + format!("{}", peer.0), + ) + }) + .inner + } else { + ui.selectable_label(is_active, format!("{}", peer.0)) + }; + + ui.add_space(4.0); // small gap + + if self.loading_peers.contains(&peer.0) { + ui.with_layout( + Layout::right_to_left(Align::Center), + |ui| { + ui.spinner(); + }, + ); + } + + if resp.clicked() { + self.active_peer = Some(peer.0.clone()); + // Request root content if not loaded + if !self + .loaded_fs + .contains_key(self.active_peer.as_ref().unwrap()) + { + //todo!(); + let _ = self.network_cmd_tx.send( + NetworkCommand::Discover( + peer.0.clone(), + "root".to_string(), + self.connected_address.clone(), + ), + ); + } + } + resp.context_menu(|ui| { match self.server_status { ServerStatus::Connected => { if ui @@ -624,75 +643,8 @@ impl eframe::App for P2PClientApp { // ... autres boutons }); - }); - - // if self.loading_peers.contains(&peer.0) { - // ui.spinner(); - // } - - //if selectable.clicked() { - // switch to displaying this peer's tree - //self.active_peer = Some(peer.0.clone()); - //// Request root content if not loaded - //if !self - // .loaded_fs - // .contains_key(self.active_peer.as_ref().unwrap()) - //{ - // //todo!(); - // let _ = self.network_cmd_tx.send(NetworkCommand::Discover( - // peer.0.clone(), - // "root".to_string(), - // self.connected_address.clone(), - // )); - //} - //} - //selectable.context_menu(|ui| { - // // ... action - // match self.server_status { - // ServerStatus::Connected => { - // if ui - // .button("Utiliser le peer en tant que serveur") - // .clicked() - // { - // self.active_server = peer.0.to_string(); - // let res = self.network_cmd_tx.send( - // NetworkCommand::ServerHandshake( - // peer.0.to_string(), - // self.connected_address.clone(), - // ), - // ); - // } - // } - // _ => {} - // } - // if ui.button("Send Ping").clicked() { - // let res = self.network_cmd_tx.send(NetworkCommand::Ping( - // peer.0.to_string(), - // self.connected_address.clone(), - // )); - - // self.loading_peers.push(peer.0.to_owned()); - // } - // if ui.button("Send Nat Traversal Request").clicked() { - // match self.network_cmd_tx.send(NetworkCommand::NatTraversal( - // peer.0.to_string(), - // self.connected_address.clone(), - // )) { - // Ok(_) => { - // print!("[+] successfully sent nat traversal request") - // } - // Err(_) => { - // print!("[-] failed to send nat traversal request") - // } - // } - // } - // if ui.button("Infos").clicked() { - // // action 3 - // ui.close(); - // } - - // // ... autres boutons - //}); + }); + } } } }); From 9ecc944857ca86a10c68874b755f3ffe51f64917 Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 03:18:09 +0100 Subject: [PATCH 8/9] rapport link --- rapport.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 rapport.txt diff --git a/rapport.txt b/rapport.txt new file mode 100644 index 0000000..d2c1184 --- /dev/null +++ b/rapport.txt @@ -0,0 +1 @@ +https://docs.google.com/document/d/1emhrAfjJyJTWpBYx4IJGcCz0_iLVjDRAAdq2EZFchKo/edit?usp=sharing From 10b77f8635d2713a2b03b921721d19fa03bd6c65 Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 25 Jan 2026 03:25:54 +0100 Subject: [PATCH 9/9] tidy --- client-gui/src/main.rs | 2 +- client-network/src/lib.rs | 28 ++++++++++------------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/client-gui/src/main.rs b/client-gui/src/main.rs index ed372a1..5384f17 100644 --- a/client-gui/src/main.rs +++ b/client-gui/src/main.rs @@ -32,7 +32,7 @@ async fn main() -> eframe::Result<()> { eframe::run_native( "p2p-merkle client", options, - Box::new(|cc| { + Box::new(|_| { let app = P2PClientApp::new(network_cmd_tx, network_event_rx); Ok(Box::new(app)) }), diff --git a/client-network/src/lib.rs b/client-network/src/lib.rs index 9edbb55..a5b928f 100644 --- a/client-network/src/lib.rs +++ b/client-network/src/lib.rs @@ -576,14 +576,21 @@ pub fn start_p2p_executor( current.push(i); } } - let res = - event_tx.send(NetworkEvent::PeerListUpdated(peers)); + match event_tx.send(NetworkEvent::PeerListUpdated(peers)) { + Ok(_) => {} + Err(err) => { + println!( + "Network Event Error : {}", + err.to_string() + ); + } + }; } Err(e) => { eprintln!("invalid UTF-8 in socket address bytes: {}", e); } }, - Err(e) => println!("error"), + Err(e) => println!("error : {}", e), } } } @@ -595,25 +602,10 @@ pub fn start_p2p_executor( if let Some(sd) = shared_data.as_ref() { let id = generate_id(); sd.add_message(id, EventType::Ping); - let pingrequest = - construct_message(PING, Vec::new(), id, sd.cryptopair_ref()); let peer_address = get_socket_address(str.to_owned(), ip, shared_data.as_ref()).await; match peer_address { Ok(addr) => { - //if let Some(ping) = pingrequest { - // sd.senders_ref().add_message_to_retry_queue( - // ping.clone(), - // addr.to_string(), - // false, - // ); - // sd.senders_ref().send_dispatch( - // ping, - // addr.to_string(), - // false, - // sd.messages_list(), - // ); - //} match event_tx.send(NetworkEvent::Success( format!( "Successfully sent ping message to {}.",