diff --git a/client-gui/src/gui_app.rs b/client-gui/src/gui_app.rs index 5957124..7b0c281 100644 --- a/client-gui/src/gui_app.rs +++ b/client-gui/src/gui_app.rs @@ -1,6 +1,12 @@ -use client_network::{filename_to_string, node_hash_to_hex_string, MerkleNode, MerkleTree, NetworkCommand, NetworkEvent, NodeHash}; +use client_network::{ + MerkleNode, MerkleTree, NetworkCommand, NetworkEvent, NodeHash, filename_to_string, + node_hash_to_hex_string, +}; use crossbeam_channel::{Receiver, Sender}; -use egui::{Align, Button, CentralPanel, CollapsingHeader, Context, Layout, ScrollArea, SidePanel, TopBottomPanel, Ui, ViewportCommand}; +use egui::{ + Align, Button, CentralPanel, CollapsingHeader, Context, Layout, ScrollArea, SidePanel, + TopBottomPanel, Ui, ViewportCommand, +}; use std::collections::HashMap; // --- Main Application Struct --- @@ -23,22 +29,17 @@ pub struct P2PClientApp { impl P2PClientApp { pub fn new(cmd_tx: Sender, event_rx: Receiver) -> Self { - let (root_hash, tree_content) = MerkleNode::generate_random_tree(5).expect("Couldn't generate tree"); - let mut peer_root_hash = HashMap::new(); - peer_root_hash.insert("bob".to_string(), "yoyoyoyo".to_string()); + let (root_hash, tree_content) = MerkleNode::generate_base_tree(); let mut loaded_fs = HashMap::new(); let tree = MerkleTree::new(tree_content, root_hash); loaded_fs.insert("bob".to_string(), tree); - Self { network_cmd_tx: cmd_tx, network_event_rx: event_rx, status_message: "Client Initialized. Awaiting network status...".to_string(), - known_peers: vec![ - "bob".to_string() - ], + known_peers: vec!["bob".to_string()], connect_address_input: "127.0.0.1:8080".to_string(), loaded_fs, active_peer: None, @@ -50,7 +51,6 @@ impl P2PClientApp { impl eframe::App for P2PClientApp { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - // 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() { @@ -119,33 +119,43 @@ impl eframe::App for P2PClientApp { }); // 3. Right-sided Panel (Known Peers) - SidePanel::right("right_panel").resizable(true).min_width(180.0).show(ctx, |ui| { - ui.heading("🌐 Known Peers"); - 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); // if peer.id == self.active_peer_id + SidePanel::right("right_panel") + .resizable(true) + .min_width(180.0) + .show(ctx, |ui| { + ui.heading("🌐 Known Peers"); + 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); // if peer.id == self.active_peer_id - if ui.selectable_label(is_active, format!("{}", peer)).clicked() { - // switch to displaying this peer's tree - self.active_peer = Some(peer.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::RequestDirectoryContent( - // peer.clone(), - // peer.clone(), - // )); + if ui + .selectable_label(is_active, format!("{}", peer)) + .clicked() + { + // switch to displaying this peer's tree + self.active_peer = Some(peer.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::RequestDirectoryContent( + // peer.clone(), + // peer.clone(), + // )); + } } } } - } + }); }); - }); // 4. Central Panel (Filesystem Tree) let heading = { @@ -185,72 +195,30 @@ impl eframe::App for P2PClientApp { // --- Helper for Drawing the Recursive File Tree --- impl P2PClientApp { - // fn draw_file_tree(&self, ui: &mut Ui, node: &FileNode, depth: usize) { - // let indent_space = 15.0 * depth as f32; - // let active_peer_id = self.active_peer_id.clone(); - // - // let entry_hash = &node.hash_id; - // let filename = &node.name; - // let is_dir = node.is_dir; - // - // if is_dir { - // // --- Directory Node: Check if content (children) is already loaded (stored in the map) --- - // - // if let Some(children) = node.children.as_ref() { - // // Content is already loaded: draw the collapsing header and recurse - // CollapsingHeader::new(format!("📁 {}", filename)) - // .default_open(false) - // .enabled(true) - // .show(ui, |ui| { - // // Recursive call: iterate over children and call draw_file_tree for each - // for child_node in children { - // self.draw_file_tree(ui, child_node, depth + 1); - // } - // }); - // } else { - // // Content is NOT loaded: show a clickable button to request loading - // let response = ui.with_layout(Layout::left_to_right(Align::Min), |ui| { - // ui.add_space(indent_space); - // ui.add(Button::new(format!("▶️ {} (Load)", filename)).small()).on_hover_text(format!("Hash: {}...", &entry_hash[..8])); - // }).response; - // - // if response.clicked() { - // if let Some(peer_id) = active_peer_id.clone() { - // let _ = self.network_cmd_tx.send(NetworkCommand::RequestDirectoryContent( - // peer_id, - // entry_hash.clone(), - // )); - // // self.status_message = format!("Requested directory content for: {}...", &entry_hash[..8]); - // } - // } - // } - // } else { - // // --- File Node (Chunk or Big) --- - // ui.with_layout(Layout::left_to_right(Align::Center), |ui| { - // ui.add_space(indent_space); - // if ui.selectable_label(false, format!("📄 {} (Hash: {}...)", filename, &entry_hash[..8])).on_hover_text("Click to request file chunks...").clicked() { - // if let Some(peer_id) = active_peer_id.clone() { - // let _ = self.network_cmd_tx.send(NetworkCommand::RequestChunk(peer_id, entry_hash.clone())); - // // self.status_message = format!("Requested file chunks for: {}...", &entry_hash[..8]); - // } - // } - // }); - // } - // } - fn draw_file_tree(&self, ui: &mut Ui, tree: &MerkleTree) { assert!(self.active_peer.is_some()); - assert!(self.loaded_fs.get(&self.active_peer.clone().unwrap()).is_some()); + assert!( + self.loaded_fs + .get(&self.active_peer.clone().unwrap()) + .is_some() + ); let root = tree.root; CollapsingHeader::new(format!("📁 root")) .default_open(true) .enabled(true) .show(ui, |ui| { - self.draw_file_node(ui, root, tree,0, None); + self.draw_file_node(ui, root, tree, 0, None); }); } - fn draw_file_node(&self, ui: &mut Ui, to_draw: NodeHash, tree: &MerkleTree, depth: usize, filename: Option<[u8; 32]>) { + fn draw_file_node( + &self, + ui: &mut Ui, + to_draw: NodeHash, + tree: &MerkleTree, + depth: usize, + filename: Option<[u8; 32]>, + ) { if depth >= 32 { return; } @@ -264,7 +232,11 @@ impl P2PClientApp { }; match current { MerkleNode::Chunk(node) => { - if ui.selectable_label(false, format!("📄 (C) {}...", name)).on_hover_text("Click to request file chunks...").clicked() { + if ui + .selectable_label(false, format!("📄 (C) {}...", name)) + .on_hover_text("Click to request file chunks...") + .clicked() + { todo!(); // if let Some(peer_id) = active_peer_id.clone() { // let _ = self.network_cmd_tx.send(NetworkCommand::RequestChunk(peer_id, entry_hash.clone())); @@ -278,12 +250,17 @@ impl P2PClientApp { .enabled(true) .show(ui, |ui| { for entry in &node.entries { - self.draw_file_node(ui, entry.content_hash, tree, depth + 1, Some(entry.filename)); + self.draw_file_node( + ui, + entry.content_hash, + tree, + depth + 1, + Some(entry.filename), + ); } }); } MerkleNode::Big(node) => { - CollapsingHeader::new(format!("📄 (B) {}", name)) .default_open(false) .enabled(true) @@ -306,4 +283,5 @@ impl P2PClientApp { } } } -} \ No newline at end of file +} + diff --git a/client-network/src/data.rs b/client-network/src/data.rs index 0781ef5..1909a8d 100644 --- a/client-network/src/data.rs +++ b/client-network/src/data.rs @@ -1,7 +1,7 @@ +use rand::{Rng, rng}; +use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; -use rand::{rng, Rng}; -use sha2::{Digest, Sha256}; // --- Constants --- const MAX_CHUNK_DATA_SIZE: usize = 1024; @@ -53,9 +53,7 @@ fn generate_random_filename() -> [u8; FILENAME_HASH_SIZE] { pub type NodeHash = [u8; FILENAME_HASH_SIZE]; pub fn node_hash_to_hex_string(hash: &NodeHash) -> String { - hash.iter() - .map(|b| format!("{:02x}", b)) - .collect() + hash.iter().map(|b| format!("{:02x}", b)).collect() } #[repr(u8)] @@ -83,7 +81,9 @@ impl MerkleTree { } } -fn generate_random_file_node(storage: &mut HashMap) -> Result { +fn generate_random_file_node( + storage: &mut HashMap, +) -> Result { let mut rng = rng(); let is_big = rng.random_bool(0.2); // 20% chance of being a big file @@ -116,7 +116,7 @@ fn generate_random_file_node(storage: &mut HashMap) -> Res fn generate_random_directory_node( depth: u32, max_depth: u32, - storage: &mut HashMap + storage: &mut HashMap, ) -> Result { let mut rng = rng(); let current_depth = depth + 1; @@ -157,7 +157,6 @@ fn generate_random_directory_node( let hash = hash(&node.serialize()); storage.insert(hash, node); Ok(hash) - } else { // Generate a BigDirectory Node (internal directory structure) let num_children = rng.random_range(MIN_BIG_CHILDREN..=MAX_BIG_CHILDREN.min(4)); // Limit children count @@ -215,7 +214,10 @@ pub struct DirectoryEntry { } pub fn filename_to_string(filename: [u8; FILENAME_HASH_SIZE]) -> String { - let end_index = filename.iter().position(|&b| b == 0).unwrap_or(FILENAME_HASH_SIZE); + let end_index = filename + .iter() + .position(|&b| b == 0) + .unwrap_or(FILENAME_HASH_SIZE); String::from_utf8_lossy(&filename[..end_index]).to_string() } @@ -312,7 +314,9 @@ impl MerkleNode { bytes } - pub fn generate_random_tree(max_depth: u32) -> Result<(NodeHash, HashMap), String> { + pub fn generate_random_tree( + max_depth: u32, + ) -> Result<(NodeHash, HashMap), String> { let mut storage = HashMap::new(); // Start tree generation from the root directory at depth 0 @@ -320,4 +324,56 @@ impl MerkleNode { Ok((root_hash, storage)) } + + pub fn generate_base_tree() -> (NodeHash, HashMap) { + let mut res = HashMap::new(); + + let node1 = MerkleNode::Chunk(ChunkNode::new_random()); + let hash1 = hash(&node1.serialize()); + + let node2 = MerkleNode::Chunk(ChunkNode::new_random()); + let hash2 = hash(&node2.serialize()); + + res.insert(hash1, node1); + res.insert(hash2, node2); + + let node3 = MerkleNode::Chunk(ChunkNode::new_random()); + let hash3 = hash(&node3.serialize()); + + res.insert(hash3, node3); + + let dir1 = MerkleNode::Directory(DirectoryNode { + entries: [DirectoryEntry { + filename: generate_random_filename(), + content_hash: hash3, + }] + .to_vec(), + }); + let hash_dir1 = hash(&dir1.serialize()); + + res.insert(hash_dir1, dir1); + + let root = MerkleNode::Directory(DirectoryNode { + entries: [ + DirectoryEntry { + filename: generate_random_filename(), + content_hash: hash1, + }, + DirectoryEntry { + filename: generate_random_filename(), + content_hash: hash2, + }, + DirectoryEntry { + filename: generate_random_filename(), + content_hash: hash_dir1, + }, + ] + .to_vec(), + }); + + let root_hash = hash(&root.serialize()); + res.insert(root_hash, root); + + (root_hash, res) + } }