feat: implement client search functionality
This commit is contained in:
+13
-4
@@ -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
@@ -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,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
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user