features
This commit is contained in:
@@ -4,11 +4,17 @@ use client_network::{
|
|||||||
};
|
};
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Button, CentralPanel, CollapsingHeader, Context, Layout, ScrollArea, SidePanel,
|
Align, Align2, Button, CentralPanel, CollapsingHeader, Context, Id, LayerId, Layout, Order,
|
||||||
TopBottomPanel, Ui, ViewportCommand,
|
Popup, ScrollArea, SidePanel, TopBottomPanel, Ui, ViewportCommand,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
enum ServerStatus {
|
||||||
|
Loading,
|
||||||
|
NotConnected,
|
||||||
|
Connected,
|
||||||
|
}
|
||||||
|
|
||||||
// --- Main Application Struct ---
|
// --- Main Application Struct ---
|
||||||
pub struct P2PClientApp {
|
pub struct P2PClientApp {
|
||||||
// Communication channels
|
// Communication channels
|
||||||
@@ -25,6 +31,8 @@ pub struct P2PClientApp {
|
|||||||
|
|
||||||
// Current peer tree displayed
|
// Current peer tree displayed
|
||||||
active_peer: Option<String>,
|
active_peer: Option<String>,
|
||||||
|
|
||||||
|
server_status: ServerStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl P2PClientApp {
|
impl P2PClientApp {
|
||||||
@@ -43,6 +51,7 @@ impl P2PClientApp {
|
|||||||
connect_address_input: "127.0.0.1:8080".to_string(),
|
connect_address_input: "127.0.0.1:8080".to_string(),
|
||||||
loaded_fs,
|
loaded_fs,
|
||||||
active_peer: None,
|
active_peer: None,
|
||||||
|
server_status: ServerStatus::Loading,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,8 +98,12 @@ impl eframe::App for P2PClientApp {
|
|||||||
// root_hash,
|
// 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| {
|
TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
|
if ui.button("Settings").clicked() {
|
||||||
|
//show settings
|
||||||
|
}
|
||||||
|
|
||||||
if ui.button("Quit").clicked() {
|
if ui.button("Quit").clicked() {
|
||||||
// Use ViewportCommand to request a close
|
// Use ViewportCommand to request a close
|
||||||
ctx.send_viewport_cmd(ViewportCommand::Close);
|
ctx.send_viewport_cmd(ViewportCommand::Close);
|
||||||
@@ -106,11 +123,14 @@ impl eframe::App for P2PClientApp {
|
|||||||
|
|
||||||
ui.menu_button("Network", |ui| {
|
ui.menu_button("Network", |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Connect to:");
|
ui.label("Server IP:");
|
||||||
ui.text_edit_singleline(&mut self.connect_address_input);
|
ui.text_edit_singleline(&mut self.connect_address_input);
|
||||||
if ui.button("Connect").clicked() {
|
if ui.button("Connect").clicked() {
|
||||||
let addr = self.connect_address_input.clone();
|
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();
|
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")
|
SidePanel::right("right_panel")
|
||||||
.resizable(true)
|
.resizable(false)
|
||||||
.min_width(180.0)
|
.min_width(180.0)
|
||||||
.show(ctx, |ui| {
|
.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();
|
ui.separator();
|
||||||
ScrollArea::vertical().show(ui, |ui| {
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
if self.known_peers.is_empty() {
|
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| {
|
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();
|
ui.separator();
|
||||||
|
|
||||||
if let Some(active_peer) = &self.active_peer {
|
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 crate::gui_app::P2PClientApp;
|
||||||
|
use client_network::{NetworkCommand, NetworkEvent, start_p2p_executor};
|
||||||
|
|
||||||
mod gui_app;
|
mod gui_app;
|
||||||
|
|
||||||
@@ -16,8 +16,9 @@ async fn main() -> eframe::Result<()> {
|
|||||||
// 3. Configure and Run the Eframe/Egui GUI
|
// 3. Configure and Run the Eframe/Egui GUI
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
viewport: egui::ViewportBuilder::default()
|
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_min_inner_size([700.0, 500.0])
|
||||||
|
.with_resizable(false)
|
||||||
.with_icon(
|
.with_icon(
|
||||||
eframe::icon_data::from_png_bytes(include_bytes!("../assets/icon.png"))
|
eframe::icon_data::from_png_bytes(include_bytes!("../assets/icon.png"))
|
||||||
.expect("Failed to load icon"),
|
.expect("Failed to load icon"),
|
||||||
@@ -33,4 +34,4 @@ async fn main() -> eframe::Result<()> {
|
|||||||
Ok(Box::new(app))
|
Ok(Box::new(app))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use rand::{Rng, rng};
|
use rand::{Rng, rng};
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
|
||||||
@@ -283,29 +282,24 @@ impl MerkleNode {
|
|||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
// 1. Add the type byte
|
|
||||||
bytes.push(self.get_type_byte());
|
bytes.push(self.get_type_byte());
|
||||||
|
|
||||||
// 2. Add the node-specific data
|
|
||||||
match self {
|
match self {
|
||||||
MerkleNode::Chunk(node) => {
|
MerkleNode::Chunk(node) => {
|
||||||
bytes.extend_from_slice(&node.data);
|
bytes.extend_from_slice(&node.data);
|
||||||
}
|
}
|
||||||
MerkleNode::Directory(node) => {
|
MerkleNode::Directory(node) => {
|
||||||
// The data is the sequence of directory entries
|
|
||||||
for entry in &node.entries {
|
for entry in &node.entries {
|
||||||
bytes.extend_from_slice(&entry.filename);
|
bytes.extend_from_slice(&entry.filename);
|
||||||
bytes.extend_from_slice(&entry.content_hash);
|
bytes.extend_from_slice(&entry.content_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MerkleNode::Big(node) => {
|
MerkleNode::Big(node) => {
|
||||||
// The data is the list of child hashes
|
|
||||||
for hash in &node.children_hashes {
|
for hash in &node.children_hashes {
|
||||||
bytes.extend_from_slice(hash);
|
bytes.extend_from_slice(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MerkleNode::BigDirectory(node) => {
|
MerkleNode::BigDirectory(node) => {
|
||||||
// The data is the list of child hashes
|
|
||||||
for hash in &node.children_hashes {
|
for hash in &node.children_hashes {
|
||||||
bytes.extend_from_slice(hash);
|
bytes.extend_from_slice(hash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ mod data;
|
|||||||
|
|
||||||
/// Messages sent to the Network thread by the GUI.
|
/// Messages sent to the Network thread by the GUI.
|
||||||
pub enum NetworkCommand {
|
pub enum NetworkCommand {
|
||||||
ConnectPeer(String), // e.g., IP:PORT
|
ConnectToServer(String), // ServerIP
|
||||||
RequestFileTree(String), // e.g., peer_id
|
FetchPeerList(String), // ServerIP
|
||||||
|
RegisterAsPeer(String),
|
||||||
|
Ping(),
|
||||||
|
ConnectPeer(String), // IP:PORT
|
||||||
|
RequestFileTree(String), // peer_id
|
||||||
RequestDirectoryContent(String, String),
|
RequestDirectoryContent(String, String),
|
||||||
RequestChunk(String, String),
|
RequestChunk(String, String),
|
||||||
// ...
|
// ...
|
||||||
@@ -11,19 +15,19 @@ pub enum NetworkCommand {
|
|||||||
|
|
||||||
/// Messages sent to the GUI by the Network thread.
|
/// Messages sent to the GUI by the Network thread.
|
||||||
pub enum NetworkEvent {
|
pub enum NetworkEvent {
|
||||||
|
Connected(),
|
||||||
|
Disconnected(),
|
||||||
|
Error(),
|
||||||
PeerConnected(String),
|
PeerConnected(String),
|
||||||
PeerListUpdated(Vec<String>),
|
PeerListUpdated(Vec<String>),
|
||||||
FileTreeReceived(String, Vec<MerkleNode>), // peer_id, content
|
FileTreeReceived(String, Vec<MerkleNode>), // peer_id, content
|
||||||
DataReceived(String, MerkleNode),
|
DataReceived(String, MerkleNode),
|
||||||
FileTreeRootReceived(String, String),
|
FileTreeRootReceived(String, String),
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
|
||||||
pub use crate::data::*;
|
pub use crate::data::*;
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub fn calculate_chunk_id(data: &[u8]) -> String {
|
pub fn calculate_chunk_id(data: &[u8]) -> String {
|
||||||
@@ -41,16 +45,12 @@ pub fn calculate_chunk_id(data: &[u8]) -> String {
|
|||||||
hex::encode(hash_bytes)
|
hex::encode(hash_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn start_p2p_executor(
|
pub fn start_p2p_executor(
|
||||||
cmd_rx: Receiver<NetworkCommand>,
|
cmd_rx: Receiver<NetworkCommand>,
|
||||||
event_tx: Sender<NetworkEvent>,
|
event_tx: Sender<NetworkEvent>,
|
||||||
) -> tokio::task::JoinHandle<()> {
|
) -> tokio::task::JoinHandle<()> {
|
||||||
|
|
||||||
// Use tokio to spawn the asynchronous networking logic
|
// Use tokio to spawn the asynchronous networking logic
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
|
|
||||||
// P2P/Networking Setup goes here
|
// P2P/Networking Setup goes here
|
||||||
|
|
||||||
println!("Network executor started.");
|
println!("Network executor started.");
|
||||||
@@ -66,18 +66,40 @@ pub fn start_p2p_executor(
|
|||||||
// Network logic to connect...
|
// Network logic to connect...
|
||||||
// If successful, send an event back:
|
// If successful, send an event back:
|
||||||
// event_tx.send(NetworkEvent::PeerConnected(addr)).unwrap();
|
// event_tx.send(NetworkEvent::PeerConnected(addr)).unwrap();
|
||||||
},
|
}
|
||||||
NetworkCommand::RequestFileTree(_) => {
|
NetworkCommand::RequestFileTree(_) => {
|
||||||
println!("[Network] RequestFileTree() called");
|
println!("[Network] RequestFileTree() called");
|
||||||
},
|
}
|
||||||
|
|
||||||
// ... handle other commands
|
|
||||||
NetworkCommand::RequestDirectoryContent(_, _) => {
|
NetworkCommand::RequestDirectoryContent(_, _) => {
|
||||||
println!("[Network] RequestDirectoryContent() called");
|
println!("[Network] RequestDirectoryContent() called");
|
||||||
},
|
}
|
||||||
NetworkCommand::RequestChunk(_, _) => {
|
NetworkCommand::RequestChunk(_, _) => {
|
||||||
println!("[Network] RequestChunk() called");
|
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;
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
0
client-network/src/protocol.rs
Normal file
0
client-network/src/protocol.rs
Normal file
Reference in New Issue
Block a user