From 721d52a028503e01047cec591bbcdbc744348910 Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sun, 30 Nov 2025 14:49:16 +0100 Subject: [PATCH] features --- client-gui/src/gui_app.rs | 87 ++++++++++++++++++++++++++-------- client-gui/src/main.rs | 7 +-- client-network/src/data.rs | 6 --- client-network/src/lib.rs | 56 +++++++++++++++------- client-network/src/protocol.rs | 0 5 files changed, 111 insertions(+), 45 deletions(-) create mode 100644 client-network/src/protocol.rs diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 7b0c281..b9e8968 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -4,11 +4,17 @@ use client_network::{ }; use crossbeam_channel::{Receiver, Sender}; use egui::{ - Align, Button, CentralPanel, CollapsingHeader, Context, Layout, ScrollArea, SidePanel, - TopBottomPanel, Ui, ViewportCommand, + Align, Align2, Button, CentralPanel, CollapsingHeader, Context, Id, LayerId, Layout, Order, + Popup, ScrollArea, SidePanel, TopBottomPanel, Ui, ViewportCommand, }; use std::collections::HashMap; +enum ServerStatus { + Loading, + NotConnected, + Connected, +} + // --- Main Application Struct --- pub struct P2PClientApp { // Communication channels @@ -25,6 +31,8 @@ pub struct P2PClientApp { // Current peer tree displayed active_peer: Option, + + server_status: ServerStatus, } impl P2PClientApp { @@ -43,6 +51,7 @@ impl P2PClientApp { connect_address_input: "127.0.0.1:8080".to_string(), loaded_fs, active_peer: None, + server_status: ServerStatus::Loading, } } } @@ -89,8 +98,12 @@ impl eframe::App for P2PClientApp { // root_hash, // )); } - // Handle other events like Disconnect, Error, etc. - _ => {} + NetworkEvent::Connected() => { + self.server_status = ServerStatus::Connected; + } + NetworkEvent::Disconnected() => todo!(), + NetworkEvent::Error() => todo!(), + NetworkEvent::DataReceived(_, merkle_node) => todo!(), } } @@ -98,6 +111,10 @@ 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("Quit").clicked() { // Use ViewportCommand to request a close ctx.send_viewport_cmd(ViewportCommand::Close); @@ -106,11 +123,14 @@ impl eframe::App for P2PClientApp { ui.menu_button("Network", |ui| { ui.horizontal(|ui| { - ui.label("Connect to:"); + ui.label("Server IP:"); ui.text_edit_singleline(&mut self.connect_address_input); if ui.button("Connect").clicked() { let addr = self.connect_address_input.clone(); - let _ = self.network_cmd_tx.send(NetworkCommand::ConnectPeer(addr)); + let _ = self + .network_cmd_tx + .send(NetworkCommand::ConnectToServer(addr)); + self.server_status = ServerStatus::Loading; ui.close(); } }); @@ -118,12 +138,44 @@ impl eframe::App for P2PClientApp { }); }); - // 3. Right-sided Panel (Known Peers) + TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { + ui.horizontal(|ui| { + match self.server_status { + ServerStatus::Loading => { + ui.spinner(); + } + ServerStatus::Connected => { + ui.label("📡"); + } + ServerStatus::NotConnected => { + ui.label("No connection.."); + } + } + ui.add_space(ui.available_width() - 30.0); + ui.label("30:00"); + }); + }); + SidePanel::right("right_panel") - .resizable(true) + .resizable(false) .min_width(180.0) .show(ctx, |ui| { - ui.heading("🌐 Known Peers"); + ui.horizontal(|ui| { + ui.heading("🌐 Known Peers"); + ui.add_space(20.0); + if ui.button("🔄").clicked() { + let res = self.network_cmd_tx.send(NetworkCommand::FetchPeerList( + self.connect_address_input.clone(), + )); + if let Some(error) = res.err() { + println!( + "[GUI] Error while sending crossbeam message to Network: {}", + error.to_string() + ); + } + } + }); + ui.separator(); ScrollArea::vertical().show(ui, |ui| { if self.known_peers.is_empty() { @@ -157,16 +209,14 @@ impl eframe::App for P2PClientApp { }); }); - // 4. Central Panel (Filesystem Tree) - let heading = { - if let Some(peer) = &self.active_peer { - format!("📂 {}'s tree", peer) - } else { - "📂 p2p-merkel client".to_string() - } - }; CentralPanel::default().show(ctx, |ui| { - ui.heading(heading); + ui.heading({ + if let Some(peer) = &self.active_peer { + format!("📂 {}'s tree", peer) + } else { + "📂 p2p-merkel client".to_string() + } + }); ui.separator(); if let Some(active_peer) = &self.active_peer { @@ -284,4 +334,3 @@ impl P2PClientApp { } } } - diff --git a/client-gui/src/main.rs b/client-gui/src/main.rs index 2d8d10b..0b48e8c 100644 --- a/client-gui/src/main.rs +++ b/client-gui/src/main.rs @@ -1,5 +1,5 @@ -use client_network::{start_p2p_executor, NetworkCommand, NetworkEvent}; use crate::gui_app::P2PClientApp; +use client_network::{NetworkCommand, NetworkEvent, start_p2p_executor}; mod gui_app; @@ -16,8 +16,9 @@ async fn main() -> eframe::Result<()> { // 3. Configure and Run the Eframe/Egui GUI let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([1000.0, 700.0]) + .with_inner_size([700.0, 500.0]) .with_min_inner_size([700.0, 500.0]) + .with_resizable(false) .with_icon( eframe::icon_data::from_png_bytes(include_bytes!("../assets/icon.png")) .expect("Failed to load icon"), @@ -33,4 +34,4 @@ async fn main() -> eframe::Result<()> { Ok(Box::new(app)) }), ) -} \ No newline at end of file +} diff --git a/client-network/src/data.rs b/client-network/src/data.rs index 1909a8d..edf9400 100644 --- a/client-network/src/data.rs +++ b/client-network/src/data.rs @@ -1,5 +1,4 @@ use rand::{Rng, rng}; -use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; @@ -283,29 +282,24 @@ impl MerkleNode { pub fn serialize(&self) -> Vec { let mut bytes = Vec::new(); - // 1. Add the type byte bytes.push(self.get_type_byte()); - // 2. Add the node-specific data match self { MerkleNode::Chunk(node) => { bytes.extend_from_slice(&node.data); } MerkleNode::Directory(node) => { - // The data is the sequence of directory entries for entry in &node.entries { bytes.extend_from_slice(&entry.filename); bytes.extend_from_slice(&entry.content_hash); } } MerkleNode::Big(node) => { - // The data is the list of child hashes for hash in &node.children_hashes { bytes.extend_from_slice(hash); } } MerkleNode::BigDirectory(node) => { - // The data is the list of child hashes for hash in &node.children_hashes { bytes.extend_from_slice(hash); } diff --git a/client-network/src/lib.rs b/client-network/src/lib.rs index 3576747..8801e0d 100644 --- a/client-network/src/lib.rs +++ b/client-network/src/lib.rs @@ -2,8 +2,12 @@ mod data; /// Messages sent to the Network thread by the GUI. pub enum NetworkCommand { - ConnectPeer(String), // e.g., IP:PORT - RequestFileTree(String), // e.g., peer_id + ConnectToServer(String), // ServerIP + FetchPeerList(String), // ServerIP + RegisterAsPeer(String), + Ping(), + ConnectPeer(String), // IP:PORT + RequestFileTree(String), // peer_id RequestDirectoryContent(String, String), RequestChunk(String, String), // ... @@ -11,19 +15,19 @@ pub enum NetworkCommand { /// Messages sent to the GUI by the Network thread. pub enum NetworkEvent { + Connected(), + Disconnected(), + Error(), PeerConnected(String), PeerListUpdated(Vec), FileTreeReceived(String, Vec), // peer_id, content DataReceived(String, MerkleNode), FileTreeRootReceived(String, String), - // ... } - - -use crossbeam_channel::{Receiver, Sender}; pub use crate::data::*; +use crossbeam_channel::{Receiver, Sender}; use sha2::{Digest, Sha256}; pub fn calculate_chunk_id(data: &[u8]) -> String { @@ -41,16 +45,12 @@ pub fn calculate_chunk_id(data: &[u8]) -> String { hex::encode(hash_bytes) } - - pub fn start_p2p_executor( cmd_rx: Receiver, event_tx: Sender, ) -> tokio::task::JoinHandle<()> { - // Use tokio to spawn the asynchronous networking logic tokio::task::spawn(async move { - // P2P/Networking Setup goes here println!("Network executor started."); @@ -66,18 +66,40 @@ pub fn start_p2p_executor( // Network logic to connect... // If successful, send an event back: // event_tx.send(NetworkEvent::PeerConnected(addr)).unwrap(); - }, + } NetworkCommand::RequestFileTree(_) => { println!("[Network] RequestFileTree() called"); - }, - - // ... handle other commands + } NetworkCommand::RequestDirectoryContent(_, _) => { println!("[Network] RequestDirectoryContent() called"); - }, + } NetworkCommand::RequestChunk(_, _) => { println!("[Network] RequestChunk() called"); - }, + } + NetworkCommand::ConnectToServer(ip) => { + println!("[Network] ConnectToServer() called"); + + // Actual server connection + + tokio::time::sleep(std::time::Duration::from_millis(5000)).await; + + let res = event_tx.send(NetworkEvent::Connected()); + if let Some(error) = res.err() { + println!( + "[Network] Couldn't send crossbeam message to GUI: {}", + error.to_string() + ); + } + } + NetworkCommand::FetchPeerList(ip) => { + println!("[Network] FetchPeerList() called"); + } + NetworkCommand::RegisterAsPeer(_) => { + println!("[Network] RegisterAsPeer() called"); + } + NetworkCommand::Ping() => { + println!("[Network] Ping() called"); + } } } @@ -90,4 +112,4 @@ pub fn start_p2p_executor( tokio::time::sleep(std::time::Duration::from_millis(50)).await; } }) -} \ No newline at end of file +} diff --git a/client-network/src/protocol.rs b/client-network/src/protocol.rs new file mode 100644 index 0000000..e69de29