feat: implement client search functionality

This commit is contained in:
Michel de Graaf
2025-06-30 00:03:46 +02:00
parent 7aaec67a30
commit c75bdd29c5
5 changed files with 94 additions and 41 deletions
+13 -4
View File
@@ -1,6 +1,7 @@
use crate::{ use crate::{
peer::{DefaultPeer, Peer}, peer::{DefaultPeer, Peer},
server::{PeerAddress, Server}, server::{PeerAddress, Server},
types::FileSearch,
utils::{md5, thread_pool::ThreadPool}, utils::{md5, thread_pool::ThreadPool},
}; };
use std::{ use std::{
@@ -19,10 +20,12 @@ use std::{
const MAX_THREADS: usize = 100; const MAX_THREADS: usize = 100;
pub enum ClientOperation { pub enum ClientOperation {
ConnectToPeer(Peer), ConnectToPeer(Peer),
SearchResult(FileSearch),
} }
struct ClientContext { struct ClientContext {
peers: HashMap<String, DefaultPeer>, peers: HashMap<String, DefaultPeer>,
sender: Option<Sender<ClientOperation>>, sender: Option<Sender<ClientOperation>>,
search_results: Vec<FileSearch>,
thread_pool: ThreadPool, thread_pool: ThreadPool,
} }
impl ClientContext { impl ClientContext {
@@ -30,6 +33,7 @@ impl ClientContext {
Self { Self {
peers: HashMap::new(), peers: HashMap::new(),
sender: None, sender: None,
search_results: Vec::new(),
thread_pool: ThreadPool::new(MAX_THREADS), thread_pool: ThreadPool::new(MAX_THREADS),
} }
} }
@@ -99,7 +103,7 @@ impl Client {
} }
} }
pub fn search(&self, query: &str, timeout: Duration) { pub fn search(&self, query: &str, timeout: Duration) -> Vec<FileSearch> {
println!("Searching for {}", query); println!("Searching for {}", query);
if let Some(server) = &self.server { if let Some(server) = &self.server {
let hash = md5::md5(query); let hash = md5::md5(query);
@@ -115,9 +119,7 @@ impl Client {
break; break;
} }
} }
if let Some(server) = &self.server { return self.context.lock().unwrap().search_results.clone();
println!("server {:?}", server)
}
} }
fn listen_to_client_operations( fn listen_to_client_operations(
@@ -130,6 +132,13 @@ impl Client {
ClientOperation::ConnectToPeer(peer) => { ClientOperation::ConnectToPeer(peer) => {
Self::connect_to_peer(peer, client_context.clone()) Self::connect_to_peer(peer, client_context.clone())
} }
ClientOperation::SearchResult(file_search) => {
client_context
.lock()
.unwrap()
.search_results
.push(file_search);
}
} }
} }
}); });
+3 -1
View File
@@ -3,6 +3,7 @@ mod dispatcher;
mod message; mod message;
mod peer; mod peer;
mod server; mod server;
mod types;
mod utils; mod utils;
use client::Client; use client::Client;
@@ -23,7 +24,8 @@ fn main() {
client.connect(); client.connect();
match client.login() { match client.login() {
Ok(_) => { Ok(_) => {
client.search("Fantazia", Duration::from_secs(20)); let results = client.search("Fantazia", Duration::from_secs(10));
println!("Search results: {:?}", results);
} }
Err(e) => { Err(e) => {
println!("Failed to login: {}", e); println!("Failed to login: {}", e);
+1 -17
View File
@@ -1,25 +1,10 @@
use crate::message::{Message, MessageHandler}; use crate::message::{Message, MessageHandler};
use crate::peer::PeerOperation; use crate::peer::PeerOperation;
use crate::types::{File, FileSearch};
use crate::utils::zlib::deflate; use crate::utils::zlib::deflate;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
#[derive(Debug)]
#[allow(dead_code)]
pub struct File {
pub username: String,
pub name: String,
pub size: i32,
pub attribs: HashMap<i32, i32>,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct FileSearch {
pub token: String,
pub files: Vec<File>,
pub slots: i8,
pub speed: i32,
}
fn decompress_buffer(buffer: &[u8]) -> Result<Vec<u8>, String> { fn decompress_buffer(buffer: &[u8]) -> Result<Vec<u8>, String> {
deflate(buffer) deflate(buffer)
@@ -72,7 +57,6 @@ impl MessageHandler<PeerOperation> for FileSearchResponse {
9 9
} }
fn handle(&self, message: &mut Message, sender: Sender<PeerOperation>) { fn handle(&self, message: &mut Message, sender: Sender<PeerOperation>) {
// println!("{:?}", message);
let file_search = FileSearch::new_from_message(message); let file_search = FileSearch::new_from_message(message);
sender sender
+56 -18
View File
@@ -1,14 +1,15 @@
use crate::dispatcher::MessageDispatcher; use crate::dispatcher::MessageDispatcher;
use crate::message::peer::{FileSearch, FileSearchResponse}; use crate::message::peer::FileSearchResponse;
use crate::message::server::MessageFactory; use crate::message::server::MessageFactory;
use crate::message::{Handlers, Message, MessageReader}; use crate::message::{Handlers, Message, MessageReader};
use crate::types::FileSearch;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
use crate::peer::Peer;
use crate::client::ClientOperation; use crate::client::ClientOperation;
use crate::peer::Peer;
use std::io::{self, Write}; use std::io::{self, Write};
use std::net::TcpStream; use std::net::TcpStream;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@@ -18,6 +19,7 @@ use std::time::Duration;
pub struct DefaultPeer { pub struct DefaultPeer {
peer: Peer, peer: Peer,
peer_channel: Option<Sender<PeerOperation>>, peer_channel: Option<Sender<PeerOperation>>,
client_channel: Sender<ClientOperation>,
// Store thread handles for proper lifecycle management // Store thread handles for proper lifecycle management
read_thread: Option<JoinHandle<()>>, read_thread: Option<JoinHandle<()>>,
write_thread: Option<JoinHandle<()>>, write_thread: Option<JoinHandle<()>>,
@@ -29,10 +31,11 @@ pub enum PeerOperation {
FileSearchResult(FileSearch), FileSearchResult(FileSearch),
} }
impl DefaultPeer { impl DefaultPeer {
pub fn new(peer: Peer, _client_channel: Sender<ClientOperation>) -> Self { pub fn new(peer: Peer, client_channel: Sender<ClientOperation>) -> Self {
Self { Self {
peer, peer,
peer_channel: None, peer_channel: None,
client_channel,
// Initialize handles as None // Initialize handles as None
read_thread: None, read_thread: None,
write_thread: None, write_thread: None,
@@ -68,7 +71,7 @@ impl DefaultPeer {
let peer = self.peer.clone(); let peer = self.peer.clone();
// Spawn the reader thread // Spawn the reader thread
let read_handle = thread::spawn(move || { self.read_thread = Some(thread::spawn(move || {
let mut handlers = Handlers::new(); let mut handlers = Handlers::new();
handlers.register_handler(FileSearchResponse); handlers.register_handler(FileSearchResponse);
@@ -95,29 +98,36 @@ impl DefaultPeer {
match buffered_reader.extract_message() { match buffered_reader.extract_message() {
Ok(Some(mut message)) => dispatcher.dispatch(&mut message), Ok(Some(mut message)) => dispatcher.dispatch(&mut message),
Err(e) => { Err(e) => {
println!("Error extracting message in default peer: {}. Terminating read loop.", e); println!(
"Error extracting message in default peer: {}. Terminating read loop.",
e
);
break; break;
} }
Ok(None) => continue, Ok(None) => continue,
} }
} }
}); }));
// Spawn the writer thread let client_channel = self.client_channel.clone();
let write_handle = thread::spawn(move || { self.write_thread = Some(thread::spawn(move || {
loop { loop {
match peer_reader.recv() { match peer_reader.recv() {
Ok(operation) => match operation { Ok(operation) => {
PeerOperation::SendMessage(message) => { match operation {
if let Err(e) = write_stream.write_all(&message.get_buffer()) { PeerOperation::SendMessage(message) => {
eprintln!("Error writing message to stream: {}. Terminating write loop.", e); if let Err(e) = write_stream.write_all(&message.get_buffer()) {
break; eprintln!("Error writing message to stream: {}. Terminating write loop.", e);
break;
}
}
PeerOperation::FileSearchResult(file_search) => {
client_channel
.send(ClientOperation::SearchResult(file_search))
.unwrap();
} }
} }
PeerOperation::FileSearchResult(file_search) => { }
println!("{:?}", file_search)
}
},
Err(_) => { Err(_) => {
// The sender has been dropped, the peer is shutting down. // The sender has been dropped, the peer is shutting down.
println!("Peer channel closed. Terminating write loop."); println!("Peer channel closed. Terminating write loop.");
@@ -125,8 +135,36 @@ impl DefaultPeer {
} }
} }
} }
}); }));
Ok(()) Ok(())
} }
} }
impl Drop for DefaultPeer {
fn drop(&mut self) {
// Signal threads to shut down (e.g., by dropping the sender half of the channel)
// For now, we'll just drop the peer_channel, which will cause the writer thread
// to exit its loop. A more robust solution might involve a shared AtomicBool
// or a dedicated shutdown channel.
self.peer_channel = None;
// Join the read thread
if let Some(handle) = self.read_thread.take() {
println!("Joining read thread...");
match handle.join() {
Ok(_) => println!("Read thread joined successfully."),
Err(e) => eprintln!("Read thread panicked: {:?}", e),
}
}
// Join the write thread
if let Some(handle) = self.write_thread.take() {
println!("Joining write thread...");
match handle.join() {
Ok(_) => println!("Write thread joined successfully."),
Err(e) => eprintln!("Write thread panicked: {:?}", e),
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
use std::collections::HashMap;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct File {
pub username: String,
pub name: String,
pub size: i32,
pub attribs: HashMap<i32, i32>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FileSearch {
pub token: String,
pub files: Vec<File>,
pub slots: i8,
pub speed: i32,
}