features
This commit is contained in:
@@ -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<String>,
|
||||
|
||||
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.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 = {
|
||||
CentralPanel::default().show(ctx, |ui| {
|
||||
ui.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.separator();
|
||||
|
||||
if let Some(active_peer) = &self.active_peer {
|
||||
@@ -284,4 +334,3 @@ impl P2PClientApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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<u8> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<String>),
|
||||
FileTreeReceived(String, Vec<MerkleNode>), // 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<NetworkCommand>,
|
||||
event_tx: Sender<NetworkEvent>,
|
||||
) -> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
client-network/src/protocol.rs
Normal file
0
client-network/src/protocol.rs
Normal file
Reference in New Issue
Block a user