chore: formatting

This commit is contained in:
Michel de Graaf
2026-01-04 20:37:03 +01:00
parent 94b33d4bd6
commit dfe6f532fa
22 changed files with 582 additions and 507 deletions
+5 -2
View File
@@ -1,5 +1,5 @@
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc; use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::trace; use crate::trace;
@@ -161,7 +161,10 @@ impl ActorSystem {
} }
} }
} }
trace!("[actor_system] run_actor_loop ENDED - processed {} messages, {} ticks", message_count, tick_count); trace!(
"[actor_system] run_actor_loop ENDED - processed {} messages, {} ticks",
message_count, tick_count
);
} }
} }
+15 -10
View File
@@ -159,9 +159,12 @@ impl PeerActor {
if let Some(ref handle) = self.self_handle if let Some(ref handle) = self.self_handle
&& let Err(e) = && let Err(e) =
handle.send(PeerMessage::SendMessage(transfer_response)) handle.send(PeerMessage::SendMessage(transfer_response))
{ {
error!("[peer:{}] Failed to send TransferResponse message: {}", username, e); error!(
} "[peer:{}] Failed to send TransferResponse message: {}",
username, e
);
}
} }
PeerMessage::TransferResponse { PeerMessage::TransferResponse {
token, token,
@@ -177,15 +180,15 @@ impl PeerActor {
if !allowed { if !allowed {
if let Some(reason_text) = reason { if let Some(reason_text) = reason {
debug!( debug!(
"[peer:{}] Transfer rejected: {} - token {}, waiting for TransferRequest...", "[peer:{}] Transfer rejected: {} - token {}, waiting for TransferRequest...",
username, reason_text, token username, reason_text, token
); );
} }
} else { } else {
debug!( debug!(
"[peer:{}] Transfer allowed, ready to connect with token {:}", "[peer:{}] Transfer allowed, ready to connect with token {:}",
username, token username, token
); );
self.client_channel self.client_channel
.send(ClientOperation::DownloadFromPeer( .send(ClientOperation::DownloadFromPeer(
token, token,
@@ -258,7 +261,9 @@ impl PeerActor {
let username = self.peer.read().unwrap().username.clone(); let username = self.peer.read().unwrap().username.clone();
error!( error!(
"[peer:{}] Error reading from peer: {} (kind: {:?}). Disconnecting.", "[peer:{}] Error reading from peer: {} (kind: {:?}). Disconnecting.",
username, e, e.kind() username,
e,
e.kind()
); );
self.disconnect_with_error(e); self.disconnect_with_error(e);
return; return;
+28 -20
View File
@@ -24,7 +24,7 @@ use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::{debug, error, info, trace, warn, SoulseekRs}; use crate::{SoulseekRs, debug, error, info, trace, warn};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PeerAddress { pub struct PeerAddress {
@@ -483,8 +483,8 @@ impl ServerActor {
obfuscated_port, obfuscated_port,
} => { } => {
debug!( debug!(
"[server] Received GetPeerAddress response for {}: {}:{} (obf_type: {}, obf_port: {})" "[server] Received GetPeerAddress response for {}: {}:{} (obf_type: {}, obf_port: {})",
, username, host, port, obfuscation_type, obfuscated_port username, host, port, obfuscation_type, obfuscated_port
); );
if let Err(e) = self.client_channel.send( if let Err(e) = self.client_channel.send(
@@ -496,7 +496,10 @@ impl ServerActor {
obfuscated_port, obfuscated_port,
}, },
) { ) {
error!("[server] Error forwarding GetPeerAddress response to client: {}", e); error!(
"[server] Error forwarding GetPeerAddress response to client: {}",
e
);
} }
} }
ServerMessage::ProcessRead => { ServerMessage::ProcessRead => {
@@ -515,23 +518,27 @@ impl ServerActor {
let timeout = Duration::from_secs(5); let timeout = Duration::from_secs(5);
let context = self.context.clone(); let context = self.context.clone();
std::thread::spawn(move || loop { std::thread::spawn(move || {
if start.elapsed() >= timeout { loop {
let _ = response.send(Err(SoulseekRs::Timeout)); if start.elapsed() >= timeout {
break; let _ = response.send(Err(SoulseekRs::Timeout));
} break;
}
if let Some(logged_in) = context.read().unwrap().logged_in { if let Some(logged_in) =
let result = if logged_in { context.read().unwrap().logged_in
Ok(true) {
} else { let result = if logged_in {
Err(SoulseekRs::AuthenticationFailed) Ok(true)
}; } else {
let _ = response.send(result); Err(SoulseekRs::AuthenticationFailed)
break; };
} let _ = response.send(result);
break;
}
std::thread::sleep(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(100));
}
}); });
} }
ServerMessage::FileSearch { token, query } => { ServerMessage::FileSearch { token, query } => {
@@ -560,7 +567,8 @@ impl ServerActor {
Err(e) => { Err(e) => {
error!( error!(
"[server] Error reading from server: {} (kind: {:?}). Disconnecting.", "[server] Error reading from server: {} (kind: {:?}). Disconnecting.",
e, e.kind() e,
e.kind()
); );
self.disconnect_with_error(e); self.disconnect_with_error(e);
return; return;
+336 -290
View File
@@ -1,27 +1,27 @@
use crate::actor::server_actor::{PeerAddress, ServerActor, ServerMessage};
use crate::actor::ActorHandle; use crate::actor::ActorHandle;
use crate::actor::server_actor::{PeerAddress, ServerActor, ServerMessage};
use crate::types::DownloadStatus; use crate::types::DownloadStatus;
use crate::utils::logger; use crate::utils::logger;
use crate::{ use crate::{
actor::{peer_registry::PeerRegistry, ActorSystem}, Transfer,
actor::{ActorSystem, peer_registry::PeerRegistry},
error::{Result, SoulseekRs}, error::{Result, SoulseekRs},
peer::{listen::Listen, ConnectionType, DownloadPeer, NewPeer, Peer}, peer::{ConnectionType, DownloadPeer, NewPeer, Peer, listen::Listen},
types::{Download, Search, SearchResult}, types::{Download, Search, SearchResult},
utils::{md5, thread_pool::ThreadPool}, utils::{md5, thread_pool::ThreadPool},
Transfer,
}; };
use std::{ use std::{
collections::HashMap, collections::HashMap,
net::TcpStream, net::TcpStream,
sync::{ sync::{
RwLock,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
mpsc::{Receiver, Sender}, mpsc::{Receiver, Sender},
RwLock,
}, },
thread::{self, sleep}, thread::{self, sleep},
}; };
use std::{ use std::{
sync::{mpsc, Arc}, sync::{Arc, mpsc},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@@ -286,9 +286,10 @@ impl Client {
pub fn remove_peer(&self, username: &str) { pub fn remove_peer(&self, username: &str) {
let context = self.context.read().unwrap(); let context = self.context.read().unwrap();
if let Some(ref registry) = context.peer_registry if let Some(ref registry) = context.peer_registry
&& let Some(handle) = registry.remove_peer(username) { && let Some(handle) = registry.remove_peer(username)
let _ = handle.stop(); {
} let _ = handle.stop();
}
} }
pub fn search( pub fn search(
@@ -334,10 +335,11 @@ impl Client {
// Check if cancelled // Check if cancelled
if let Some(ref flag) = cancel_flag if let Some(ref flag) = cancel_flag
&& flag.load(Ordering::Relaxed) { && flag.load(Ordering::Relaxed)
info!("Search cancelled by user"); {
break; info!("Search cancelled by user");
} break;
}
// Check if timeout reached // Check if timeout reached
if start.elapsed() >= timeout { if start.elapsed() >= timeout {
@@ -470,225 +472,12 @@ impl Client {
client_context: Arc<RwLock<ClientContext>>, client_context: Arc<RwLock<ClientContext>>,
own_username: String, own_username: String,
) { ) {
thread::spawn(move || loop { thread::spawn(move || {
match reader.recv() { loop {
Ok(operation) => { match reader.recv() {
match operation { Ok(operation) => {
ClientOperation::ConnectToPeer(peer) => { match operation {
let client_context_clone = client_context.clone(); ClientOperation::ConnectToPeer(peer) => {
let own_username_clone = own_username.clone();
thread::spawn(move || {
Self::connect_to_peer(
peer,
client_context_clone,
own_username_clone,
None,
);
});
}
ClientOperation::SearchResult(search_result) => {
trace!("[client] SearchResult {:?}", search_result);
let mut context = client_context.write().unwrap();
let result_token = search_result.token;
// Find the search with matching token
for search in context.searches.values_mut() {
if search.token == result_token {
search.results.push(search_result);
break;
}
}
}
ClientOperation::PeerDisconnected(username, error) => {
let context = client_context.read().unwrap();
if let Some(ref registry) = context.peer_registry
&& let Some(handle) =
registry.remove_peer(&username)
{
let _ = handle.stop();
}
if let Some(error) = error {
warn!(
"[client] Peer {} disconnected with error: {:?}",
username, error
);
Self::process_failed_uploads(
client_context.clone(),
&username,
None,
);
}
}
ClientOperation::PierceFireWall(peer) => {
Self::pierce_firewall(
peer,
client_context.clone(),
own_username.clone(),
);
}
ClientOperation::DownloadFromPeer(
token,
peer,
allowed,
) => {
let maybe_download = {
let client_context =
client_context.read().unwrap();
client_context
.get_download_by_token(token)
.cloned()
};
let own_username = own_username.clone();
let client_context_clone = client_context.clone();
trace!(
"[client] DownloadFromPeer token: {} peer: {:?}",
token,
peer
);
match maybe_download {
Some(download) => {
thread::spawn(move || {
let download_peer = DownloadPeer::new(
download.username.clone(),
peer.host.clone(),
peer.port,
token,
allowed,
own_username,
);
let filename: Option<&str> = download
.filename
.split('\\')
.next_back();
match filename {
Some(filename) => {
match download_peer.download_file(
client_context_clone.clone(),
Some(download.clone()),
None
) {
Ok((download, filename)) => {
let _ = download.sender.send(DownloadStatus::Completed);
client_context_clone.write().unwrap().update_download_with_status(download.token, DownloadStatus::Completed);
info!("Successfully downloaded {} bytes to {}", download.size, filename);
}
Err(e) => {
let _ = download.sender.send(DownloadStatus::Failed);
client_context_clone.write().unwrap().update_download_with_status(download.token, DownloadStatus::Failed);
error!(
"Failed to download file '{}' from {}:{} (token: {}) - Error: {}",
filename, peer.host, peer.port, download.token, e
);
}
}
}
None => error!("Cant find filename to save download: {:?}", download.filename),
}
});
}
None => {
error!(
"Can't find download with token {:?}",
token
);
}
};
}
ClientOperation::NewPeer(new_peer) => {
let peer_exists = client_context
.read()
.unwrap()
.peer_registry
.as_ref()
.map(|r| r.contains(&new_peer.username))
.unwrap_or(false);
if peer_exists {
debug!(
"Already connected to {}",
new_peer.username
);
} else if let Some(server_sender) =
&client_context.read().unwrap().server_sender
{
server_sender
.send(ServerMessage::GetPeerAddress(
new_peer.username.clone(),
))
.unwrap();
}
let addr = new_peer.tcp_stream.peer_addr().unwrap();
let host = addr.ip().to_string();
let port: u32 = addr.port().into();
let peer = Peer {
username: new_peer.username.clone(),
connection_type: new_peer.connection_type,
host,
port,
token: Some(new_peer.token),
privileged: None,
obfuscated_port: None,
unknown: None,
};
Self::connect_to_peer(
peer,
client_context.clone(),
own_username.clone(),
Some(new_peer.tcp_stream),
);
}
ClientOperation::GetPeerAddressResponse {
username,
host,
port,
obfuscation_type,
obfuscated_port,
} => {
debug!(
"Received peer address for {}: {}:{} (obf_type: {}, obf_port: {})",
username, host, port, obfuscation_type, obfuscated_port
);
let peer_exists = client_context
.read()
.unwrap()
.peer_registry
.as_ref()
.map(|r| r.contains(&username))
.unwrap_or(false);
if peer_exists {
// don't know if i should update? and or reconnect the peer
// debug!(
// "existing peer: {:?}, new peer details:
// username: {},
// host: {},
// port: {}
// obfuscation_type: {}
// obfuscated_port: {}",
// peer,
// username,
// host,
// port,
// obfuscation_type,
// obfuscated_port,
// );
} else {
let peer = Peer::new(
username,
ConnectionType::P,
host,
port,
None,
0,
obfuscation_type.try_into().unwrap(),
obfuscated_port.try_into().unwrap(),
);
let client_context_clone = let client_context_clone =
client_context.clone(); client_context.clone();
let own_username_clone = own_username.clone(); let own_username_clone = own_username.clone();
@@ -702,62 +491,319 @@ impl Client {
); );
}); });
} }
} ClientOperation::SearchResult(search_result) => {
ClientOperation::UpdateDownloadTokens(
transfer,
username,
) => {
let mut context = client_context.write().unwrap();
let download_to_update =
context.get_downloads().iter().find_map(|d| {
if d.username == username
&& d.filename == transfer.filename
{
Some((d.token, d.clone()))
} else {
None
}
});
if let Some((old_token, download)) =
download_to_update
{
trace!( trace!(
"[client] UpdateDownloadTokens found {old_token}, transfer: {:?}", "[client] SearchResult {:?}",
transfer search_result
); );
let mut context =
client_context.write().unwrap();
let result_token = search_result.token;
context.add_download(Download { // Find the search with matching token
username: username.clone(), for search in context.searches.values_mut() {
filename: transfer.filename, if search.token == result_token {
token: transfer.token, search.results.push(search_result);
size: transfer.size, break;
download_directory: download }
.download_directory, }
status: download.status.clone(), }
sender: download.sender.clone(), ClientOperation::PeerDisconnected(
}); username,
context.remove_download(old_token); error,
) => {
let context = client_context.read().unwrap();
if let Some(ref registry) =
context.peer_registry
&& let Some(handle) =
registry.remove_peer(&username)
{
let _ = handle.stop();
}
if let Some(error) = error {
warn!(
"[client] Peer {} disconnected with error: {:?}",
username, error
);
Self::process_failed_uploads(
client_context.clone(),
&username,
None,
);
}
}
ClientOperation::PierceFireWall(peer) => {
Self::pierce_firewall(
peer,
client_context.clone(),
own_username.clone(),
);
}
ClientOperation::DownloadFromPeer(
token,
peer,
allowed,
) => {
let maybe_download = {
let client_context =
client_context.read().unwrap();
client_context
.get_download_by_token(token)
.cloned()
};
let own_username = own_username.clone();
let client_context_clone =
client_context.clone();
trace!(
"[client] DownloadFromPeer token: {} peer: {:?}",
token, peer
);
match maybe_download {
Some(download) => {
thread::spawn(move || {
let download_peer =
DownloadPeer::new(
download.username.clone(),
peer.host.clone(),
peer.port,
token,
allowed,
own_username,
);
let filename: Option<&str> =
download
.filename
.split('\\')
.next_back();
match filename {
Some(filename) => {
match download_peer
.download_file(
client_context_clone
.clone(),
Some(download.clone()),
None,
) {
Ok((
download,
filename,
)) => {
let _ = download.sender.send(DownloadStatus::Completed);
client_context_clone.write().unwrap().update_download_with_status(download.token, DownloadStatus::Completed);
info!(
"Successfully downloaded {} bytes to {}",
download.size,
filename
);
}
Err(e) => {
let _ = download.sender.send(DownloadStatus::Failed);
client_context_clone.write().unwrap().update_download_with_status(download.token, DownloadStatus::Failed);
error!(
"Failed to download file '{}' from {}:{} (token: {}) - Error: {}",
filename,
peer.host,
peer.port,
download.token,
e
);
}
}
}
None => error!(
"Cant find filename to save download: {:?}",
download.filename
),
}
});
}
None => {
error!(
"Can't find download with token {:?}",
token
);
}
};
}
ClientOperation::NewPeer(new_peer) => {
let peer_exists = client_context
.read()
.unwrap()
.peer_registry
.as_ref()
.map(|r| r.contains(&new_peer.username))
.unwrap_or(false);
if peer_exists {
debug!(
"Already connected to {}",
new_peer.username
);
} else if let Some(server_sender) =
&client_context
.read()
.unwrap()
.server_sender
{
server_sender
.send(ServerMessage::GetPeerAddress(
new_peer.username.clone(),
))
.unwrap();
}
let addr =
new_peer.tcp_stream.peer_addr().unwrap();
let host = addr.ip().to_string();
let port: u32 = addr.port().into();
let peer = Peer {
username: new_peer.username.clone(),
connection_type: new_peer.connection_type,
host,
port,
token: Some(new_peer.token),
privileged: None,
obfuscated_port: None,
unknown: None,
};
Self::connect_to_peer(
peer,
client_context.clone(),
own_username.clone(),
Some(new_peer.tcp_stream),
);
}
ClientOperation::GetPeerAddressResponse {
username,
host,
port,
obfuscation_type,
obfuscated_port,
} => {
debug!(
"Received peer address for {}: {}:{} (obf_type: {}, obf_port: {})",
username,
host,
port,
obfuscation_type,
obfuscated_port
);
let peer_exists = client_context
.read()
.unwrap()
.peer_registry
.as_ref()
.map(|r| r.contains(&username))
.unwrap_or(false);
if peer_exists {
// don't know if i should update? and or reconnect the peer
// debug!(
// "existing peer: {:?}, new peer details:
// username: {},
// host: {},
// port: {}
// obfuscation_type: {}
// obfuscated_port: {}",
// peer,
// username,
// host,
// port,
// obfuscation_type,
// obfuscated_port,
// );
} else {
let peer = Peer::new(
username,
ConnectionType::P,
host,
port,
None,
0,
obfuscation_type.try_into().unwrap(),
obfuscated_port.try_into().unwrap(),
);
let client_context_clone =
client_context.clone();
let own_username_clone =
own_username.clone();
thread::spawn(move || {
Self::connect_to_peer(
peer,
client_context_clone,
own_username_clone,
None,
);
});
}
}
ClientOperation::UpdateDownloadTokens(
transfer,
username,
) => {
let mut context =
client_context.write().unwrap();
let download_to_update = context
.get_downloads()
.iter()
.find_map(|d| {
if d.username == username
&& d.filename == transfer.filename
{
Some((d.token, d.clone()))
} else {
None
}
});
if let Some((old_token, download)) =
download_to_update
{
trace!(
"[client] UpdateDownloadTokens found {old_token}, transfer: {:?}",
transfer
);
context.add_download(Download {
username: username.clone(),
filename: transfer.filename,
token: transfer.token,
size: transfer.size,
download_directory: download
.download_directory,
status: download.status.clone(),
sender: download.sender.clone(),
});
context.remove_download(old_token);
}
}
ClientOperation::UploadFailed(
username,
filename,
) => {
Self::process_failed_uploads(
client_context.clone(),
&username,
Some(&filename),
);
}
ClientOperation::SetServerSender(sender) => {
client_context.write().unwrap().server_sender =
Some(sender);
debug!("[client] Server sender initialized");
} }
} }
ClientOperation::UploadFailed(username, filename) => {
Self::process_failed_uploads(
client_context.clone(),
&username,
Some(&filename),
);
}
ClientOperation::SetServerSender(sender) => {
client_context.write().unwrap().server_sender =
Some(sender);
debug!("[client] Server sender initialized");
}
} }
} Err(e) => {
Err(e) => { error!("[client] Channel recv error: {:?}", e);
error!("[client] Channel recv error: {:?}", e); break;
break; }
} }
} }
}); });
@@ -772,7 +818,10 @@ impl Client {
let client_context = client_context.clone(); let client_context = client_context.clone();
let peer_clone = peer.clone(); let peer_clone = peer.clone();
trace!("[client] connecting to {}, with connection_type: {}, and token {:?}", peer.username, peer.connection_type, peer.token); trace!(
"[client] connecting to {}, with connection_type: {}, and token {:?}",
peer.username, peer.connection_type, peer.token
);
match peer.connection_type { match peer.connection_type {
ConnectionType::P => { ConnectionType::P => {
let username = peer.username.clone(); let username = peer.username.clone();
@@ -784,8 +833,7 @@ impl Client {
Err(e) => { Err(e) => {
trace!( trace!(
"Failed to spawn peer actor for {:?}: {:?}", "Failed to spawn peer actor for {:?}: {:?}",
username, username, e
e
); );
} }
} }
@@ -797,8 +845,7 @@ impl Client {
ConnectionType::F => { ConnectionType::F => {
trace!( trace!(
"[client] downloading from: {}, {:?}", "[client] downloading from: {}, {:?}",
peer.username, peer.username, peer.token
peer.token
); );
let download_peer = DownloadPeer::new( let download_peer = DownloadPeer::new(
peer.username, peer.username,
@@ -817,8 +864,7 @@ impl Client {
Ok((download, filename)) => { Ok((download, filename)) => {
trace!( trace!(
"[client] downloaded {} bytes {:?} ", "[client] downloaded {} bytes {:?} ",
filename, filename, download.size
download.size
); );
let _ = download.sender.send(DownloadStatus::Completed); let _ = download.sender.send(DownloadStatus::Completed);
client_context client_context
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::message::{handlers::Handlers, Message}; use crate::message::{Message, handlers::Handlers};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use crate::warn; use crate::warn;
@@ -1,5 +1,5 @@
use crate::{ use crate::{
message::{server::MessageFactory, Message, MessageHandler}, message::{Message, MessageHandler, server::MessageFactory},
peer::PeerMessage, peer::PeerMessage,
}; };
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
@@ -20,9 +20,7 @@ impl MessageHandler<PeerMessage> for PeerInit {
let token = message.read_int32(); let token = message.read_int32();
trace!( trace!(
"PeerInit: username: {}, connection_type: {}, token: {}", "PeerInit: username: {}, connection_type: {}, token: {}",
username, username, connection_type, token
connection_type,
token
); );
sender.send(PeerMessage::SetUsername(username)).unwrap(); sender.send(PeerMessage::SetUsername(username)).unwrap();
@@ -2,8 +2,8 @@ use crate::{debug, info};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use crate::{ use crate::{
actor::server_actor::ServerMessage, message::handlers::MessageHandler, actor::server_actor::ServerMessage, message::Message,
message::Message, message::handlers::MessageHandler,
}; };
pub struct FileSearchHandler; pub struct FileSearchHandler;
+29 -32
View File
@@ -195,8 +195,7 @@ impl DownloadPeer {
) -> Result<(), DownloadError> { ) -> Result<(), DownloadError> {
trace!( trace!(
"[download_peer:{}] performing handshake no_pierce: {}", "[download_peer:{}] performing handshake no_pierce: {}",
self.username, self.username, self.no_pierce
self.no_pierce
); );
if !self.no_pierce { if !self.no_pierce {
@@ -231,8 +230,7 @@ impl DownloadPeer {
trace!( trace!(
"[download_peer:{}] got token: {} from data chunk", "[download_peer:{}] got token: {} from data chunk",
self.username, self.username, token_u32
token_u32
); );
stream stream
@@ -277,8 +275,7 @@ impl DownloadPeer {
Ok(0) => { Ok(0) => {
trace!( trace!(
"[download_peer:{}] connection closed by peer. bytes read: {}", "[download_peer:{}] connection closed by peer. bytes read: {}",
self.username, self.username, processor.total_bytes
processor.total_bytes
); );
break; break;
} }
@@ -294,9 +291,7 @@ impl DownloadPeer {
)?; )?;
trace!( trace!(
"[download_peer:{}] got download info for token: {} - filename: {}", "[download_peer:{}] got download info for token: {} - filename: {}",
self.username, self.username, self.token, new_download.filename
self.token,
new_download.filename
); );
download = Some(new_download); download = Some(new_download);
processor.received = true; processor.received = true;
@@ -307,30 +302,30 @@ impl DownloadPeer {
chunk_counter += 1; chunk_counter += 1;
if let Some(ref dl) = download if let Some(ref dl) = download
&& chunk_counter % PROGRESS_UPDATE_CHUNKS == 0 { && chunk_counter % PROGRESS_UPDATE_CHUNKS == 0
let elapsed = {
last_update_time.elapsed().as_secs_f64(); let elapsed = last_update_time.elapsed().as_secs_f64();
let bytes_since_last_update = let bytes_since_last_update =
PROGRESS_UPDATE_CHUNKS * READ_BUFFER_SIZE; PROGRESS_UPDATE_CHUNKS * READ_BUFFER_SIZE;
let speed = if elapsed > 0.0 { let speed = if elapsed > 0.0 {
bytes_since_last_update as f64 / elapsed bytes_since_last_update as f64 / elapsed
} else { } else {
0.0 0.0
}; };
let status = DownloadStatus::InProgress { let status = DownloadStatus::InProgress {
bytes_downloaded: processor.total_bytes as u64, bytes_downloaded: processor.total_bytes as u64,
total_bytes: dl.size, total_bytes: dl.size,
speed_bytes_per_sec: speed, speed_bytes_per_sec: speed,
}; };
let _ = dl.sender.send(status.clone()); let _ = dl.sender.send(status.clone());
client_context client_context
.write() .write()
.unwrap() .unwrap()
.update_download_with_status(dl.token, status); .update_download_with_status(dl.token, status);
last_update_time = Instant::now(); last_update_time = Instant::now();
} }
let expected_size = download let expected_size = download
.as_ref() .as_ref()
@@ -486,7 +481,9 @@ mod tests {
"file.mp3" "file.mp3"
); );
assert_eq!( assert_eq!(
FileManager::extract_filename_from_path("@@bhfrv\\Soulseek Downloads\\complete\\Beatport Top Deep House (2021)\\michel test file.mp3"), FileManager::extract_filename_from_path(
"@@bhfrv\\Soulseek Downloads\\complete\\Beatport Top Deep House (2021)\\michel test file.mp3"
),
"michel test file.mp3" "michel test file.mp3"
); );
assert_eq!( assert_eq!(
+13 -8
View File
@@ -9,7 +9,7 @@ use crate::client::{ClientContext, ClientOperation};
use crate::message::{Message, MessageReader}; use crate::message::{Message, MessageReader};
use crate::peer::{ConnectionType, DownloadPeer, Peer}; use crate::peer::{ConnectionType, DownloadPeer, Peer};
use crate::types::Download; use crate::types::Download;
use crate::{debug, error, info, trace, DownloadStatus}; use crate::{DownloadStatus, debug, error, info, trace};
const PEER_INIT_MESSAGE_CODE: u8 = 1; const PEER_INIT_MESSAGE_CODE: u8 = 1;
@@ -79,8 +79,7 @@ fn extract_download_from_buffer(
let token = parse_token_from_buffer(&buffer, username)?; let token = parse_token_from_buffer(&buffer, username)?;
trace!( trace!(
"[listener:{}] got transfer_token: {} from data chunk", "[listener:{}] got transfer_token: {} from data chunk",
username, username, token
token
); );
let context = client_context.read().unwrap(); let context = client_context.read().unwrap();
@@ -88,7 +87,10 @@ fn extract_download_from_buffer(
if download.is_none() { if download.is_none() {
let download_tokens = context.get_download_tokens(); let download_tokens = context.get_download_tokens();
trace!("[listener:{peer_ip}:{peer_port}] download token not found: {:?}, download tokens: {:?}", token, download_tokens); trace!(
"[listener:{peer_ip}:{peer_port}] download token not found: {:?}, download tokens: {:?}",
token, download_tokens
);
} }
download download
@@ -129,8 +131,7 @@ fn handle_file_connection(
) { ) {
trace!( trace!(
"[client] DownloadFromPeer token: {} peer: {:?}", "[client] DownloadFromPeer token: {} peer: {:?}",
token, token, peer
peer
); );
let download = extract_download_from_buffer( let download = extract_download_from_buffer(
@@ -198,7 +199,9 @@ fn handle_incoming_connection(stream: TcpStream, context: ConnectionContext) {
}; };
let Some(init_data) = parse_peer_init_message(message) else { let Some(init_data) = parse_peer_init_message(message) else {
error!("[listener:{peer_ip}:{peer_port}] Invalid or unknown peer init message"); error!(
"[listener:{peer_ip}:{peer_port}] Invalid or unknown peer init message"
);
return; return;
}; };
@@ -241,7 +244,9 @@ fn handle_incoming_connection(stream: TcpStream, context: ConnectionContext) {
}); });
} }
ConnectionType::D => { ConnectionType::D => {
debug!("[listener:{peer_ip}:{peer_port}] connection type is D, not supported yet, closing connection. "); debug!(
"[listener:{peer_ip}:{peer_port}] connection type is D, not supported yet, closing connection. "
);
} }
} }
} }
+6 -5
View File
@@ -2,8 +2,8 @@ use std::env;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::Write; use std::io::Write;
use std::sync::{ use std::sync::{
atomic::{AtomicBool, Ordering},
Mutex, Once, Mutex, Once,
atomic::{AtomicBool, Ordering},
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -168,10 +168,11 @@ pub fn log(level: LogLevel, message: &str) {
} }
// But ALSO write to file if configured (file logging bypasses buffering) // But ALSO write to file if configured (file logging bypasses buffering)
if let Ok(mut log_file) = LOG_FILE.lock() if let Ok(mut log_file) = LOG_FILE.lock()
&& let Some(file) = log_file.as_mut() { && let Some(file) = log_file.as_mut()
let _ = writeln!(file, "{}", formatted_message_plain); {
let _ = file.flush(); let _ = writeln!(file, "{}", formatted_message_plain);
} let _ = file.flush();
}
} else { } else {
// Write to file if configured, otherwise to stderr // Write to file if configured, otherwise to stderr
if let Ok(mut log_file) = LOG_FILE.lock() { if let Ok(mut log_file) = LOG_FILE.lock() {
+2 -3
View File
@@ -369,7 +369,7 @@ fn bit_padding(input: &str) -> Vec<u8> {
//check if bit length % 512 is 448 (64 less than 512) //check if bit length % 512 is 448 (64 less than 512)
while (input_vector.len() * 8) % 512 != 448 { while (input_vector.len() * 8) % 512 != 448 {
input_vector.push(0_u8); // push in another 8-bit 0 padded value until the correct input_vector.push(0_u8); // push in another 8-bit 0 padded value until the correct
// result is reached; // result is reached;
} }
let length_bits_as_u8_array = split_u64_to_u8_array(bit_length); let length_bits_as_u8_array = split_u64_to_u8_array(bit_length);
@@ -467,8 +467,7 @@ fn all_alphanumeric_correct_hash() {
#[test] #[test]
fn repeated_numbers_correct_hash() { fn repeated_numbers_correct_hash() {
let repeated_number_str = let repeated_number_str = "12345678901234567890123456789012345678901234567890123456789012345678901234567890";
"12345678901234567890123456789012345678901234567890123456789012345678901234567890";
assert_eq!("57edf4a22be3c955ac49da2e2107b67a", md5(repeated_number_str)); assert_eq!("57edf4a22be3c955ac49da2e2107b67a", md5(repeated_number_str));
} }
+32 -30
View File
@@ -1,5 +1,5 @@
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{mpsc, Arc, Mutex}; use std::sync::{Arc, Mutex, mpsc};
use std::thread; use std::thread;
type Job = Box<dyn FnOnce() + Send + 'static>; type Job = Box<dyn FnOnce() + Send + 'static>;
@@ -89,37 +89,39 @@ impl Worker {
_active_threads: Arc<AtomicUsize>, _active_threads: Arc<AtomicUsize>,
_total_threads: usize, _total_threads: usize,
) -> Worker { ) -> Worker {
let thread = thread::spawn(move || loop { let thread = thread::spawn(move || {
let message = receiver.lock().unwrap().recv(); loop {
match message { let message = receiver.lock().unwrap().recv();
Ok(Message::NewJob(job)) => { match message {
// let active = Ok(Message::NewJob(job)) => {
// active_threads.fetch_add(1, Ordering::SeqCst) + 1; // let active =
// trace!( // active_threads.fetch_add(1, Ordering::SeqCst) + 1;
// "Thread {} started job (active: {}/{})", // trace!(
// id, // "Thread {} started job (active: {}/{})",
// active, // id,
// total_threads // active,
// ); // total_threads
// );
job(); job();
// let active = // let active =
// active_threads.fetch_sub(1, Ordering::SeqCst) - 1; // active_threads.fetch_sub(1, Ordering::SeqCst) - 1;
// trace!( // trace!(
// "Thread {} finished job (active: {}/{})", // "Thread {} finished job (active: {}/{})",
// id, // id,
// active, // active,
// total_threads // total_threads
// ); // );
} }
Ok(Message::Terminate) => { Ok(Message::Terminate) => {
// Received termination signal // Received termination signal
break; break;
} }
Err(_) => { Err(_) => {
// Channel disconnected // Channel disconnected
break; break;
}
} }
} }
}); });
+16 -8
View File
@@ -4,16 +4,16 @@ mod models;
mod ui; mod ui;
use clap::Parser; use clap::Parser;
use cli::{parse_server_address, Cli, Commands}; use cli::{Cli, Commands, parse_server_address};
use color_eyre::Result; use color_eyre::Result;
use config::SearchConfig; use config::SearchConfig;
use soulseek_rs::{Client, ClientSettings, PeerAddress}; use soulseek_rs::{Client, ClientSettings, PeerAddress};
use std::{ use std::{
env, env,
sync::{atomic::AtomicBool, Arc}, sync::{Arc, atomic::AtomicBool},
time::Duration, time::Duration,
}; };
use ui::{launch_main_tui, show_multi_download_progress, FileSelector}; use ui::{FileSelector, launch_main_tui, show_multi_download_progress};
fn main() -> Result<()> { fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
@@ -33,14 +33,22 @@ fn main() -> Result<()> {
// Set LOG_FILE env var if provided via CLI // Set LOG_FILE env var if provided via CLI
if let Some(log_file) = &cli.log_file { if let Some(log_file) = &cli.log_file {
// SAFETY: Called before any threads are spawned // SAFETY: Called before any threads are spawned
unsafe { env::set_var("LOG_FILE", log_file.to_string_lossy().to_string()) }; unsafe {
env::set_var("LOG_FILE", log_file.to_string_lossy().to_string())
};
} }
let username = cli.username let username = cli.username.ok_or_else(|| {
.ok_or_else(|| color_eyre::eyre::eyre!("Username required: use --username or set SOULSEEK_USERNAME env var"))?; color_eyre::eyre::eyre!(
"Username required: use --username or set SOULSEEK_USERNAME env var"
)
})?;
let password = cli.password let password = cli.password.ok_or_else(|| {
.ok_or_else(|| color_eyre::eyre::eyre!("Password required: use --password or set SOULSEEK_PASSWORD env var"))?; color_eyre::eyre::eyre!(
"Password required: use --password or set SOULSEEK_PASSWORD env var"
)
})?;
let (server_host, server_port) = parse_server_address(&cli.server)?; let (server_host, server_port) = parse_server_address(&cli.server)?;
+2 -2
View File
@@ -1,8 +1,8 @@
use crate::models::FileDisplayData; use crate::models::FileDisplayData;
use ratatui::{layout::Rect, widgets::TableState}; use ratatui::{layout::Rect, widgets::TableState};
use soulseek_rs::{types::Download, DownloadStatus}; use soulseek_rs::{DownloadStatus, types::Download};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::{mpsc::Receiver, mpsc::Sender, Arc}; use std::sync::{Arc, mpsc::Receiver, mpsc::Sender};
use std::time::Instant; use std::time::Instant;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
+26 -25
View File
@@ -1,27 +1,27 @@
use crate::models::FileDisplayData; use crate::models::FileDisplayData;
use crate::ui::{ use crate::ui::{
border_style, border_type, format_bytes, format_shortcuts_styled, HIGHLIGHT_SYMBOL, border_style, border_type, format_bytes,
get_bitrate, get_spinner_char, header_style, highlight_style, format_shortcuts_styled, get_bitrate, get_spinner_char, header_style,
primary_style, success_style, warning_style, HIGHLIGHT_SYMBOL, highlight_style, primary_style, success_style, warning_style,
}; };
use color_eyre::Result; use color_eyre::Result;
use ratatui::text::{Line, Span}; use ratatui::text::{Line, Span};
use ratatui::{ use ratatui::{
crossterm::event::{self, poll, Event, KeyCode, KeyEvent, KeyEventKind}, DefaultTerminal, Frame,
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, poll},
layout::{Alignment, Constraint, Layout}, layout::{Alignment, Constraint, Layout},
style::Style, style::Style,
widgets::{ widgets::{
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, StatefulWidget, Block, Borders, Cell, HighlightSpacing, Paragraph, Row, StatefulWidget,
Table, TableState, Wrap, Table, TableState, Wrap,
}, },
DefaultTerminal, Frame,
}; };
use soulseek_rs::Client; use soulseek_rs::Client;
use std::{ use std::{
collections::HashSet, collections::HashSet,
sync::{ sync::{
atomic::{AtomicBool, Ordering},
Arc, Arc,
atomic::{AtomicBool, Ordering},
}, },
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@@ -114,9 +114,10 @@ impl FileSelector {
}; };
if poll(timeout)? if poll(timeout)?
&& let Event::Key(key) = event::read()? { && let Event::Key(key) = event::read()?
self.handle_key(key); {
} self.handle_key(key);
}
} }
Ok((terminal, self.selected_indices.iter().copied().collect())) Ok((terminal, self.selected_indices.iter().copied().collect()))
@@ -158,13 +159,13 @@ impl FileSelector {
fn toggle_selection(&mut self) { fn toggle_selection(&mut self) {
if let Some(filtered_idx) = self.state.selected() if let Some(filtered_idx) = self.state.selected()
&& let Some(&original_idx) = self.filtered_indices.get(filtered_idx) && let Some(&original_idx) = self.filtered_indices.get(filtered_idx)
{ {
if self.selected_indices.contains(&original_idx) { if self.selected_indices.contains(&original_idx) {
self.selected_indices.remove(&original_idx); self.selected_indices.remove(&original_idx);
} else { } else {
self.selected_indices.insert(original_idx); self.selected_indices.insert(original_idx);
}
} }
}
} }
fn handle_key(&mut self, key: KeyEvent) { fn handle_key(&mut self, key: KeyEvent) {
@@ -194,11 +195,11 @@ impl FileSelector {
// If nothing selected, select the current item under cursor // If nothing selected, select the current item under cursor
if self.selected_indices.is_empty() if self.selected_indices.is_empty()
&& let Some(filtered_idx) = self.state.selected() && let Some(filtered_idx) = self.state.selected()
&& let Some(&original_idx) = && let Some(&original_idx) =
self.filtered_indices.get(filtered_idx) self.filtered_indices.get(filtered_idx)
{ {
self.selected_indices.insert(original_idx); self.selected_indices.insert(original_idx);
} }
self.should_exit = true; self.should_exit = true;
} }
KeyCode::Up | KeyCode::Down => { KeyCode::Up | KeyCode::Down => {
@@ -236,11 +237,11 @@ impl FileSelector {
// If nothing selected, select the current item under cursor // If nothing selected, select the current item under cursor
if self.selected_indices.is_empty() if self.selected_indices.is_empty()
&& let Some(filtered_idx) = self.state.selected() && let Some(filtered_idx) = self.state.selected()
&& let Some(&original_idx) = && let Some(&original_idx) =
self.filtered_indices.get(filtered_idx) self.filtered_indices.get(filtered_idx)
{ {
self.selected_indices.insert(original_idx); self.selected_indices.insert(original_idx);
} }
self.should_exit = true; self.should_exit = true;
} }
KeyCode::Esc | KeyCode::Char('q') => { KeyCode::Esc | KeyCode::Char('q') => {
+21 -20
View File
@@ -1,13 +1,14 @@
use crate::models::DownloadEntry; use crate::models::DownloadEntry;
use crate::ui::{ use crate::ui::{
border_style, border_type, error_style, format_bytes_progress, COLOR_PRIMARY, HIGHLIGHT_SYMBOL, border_style, border_type, error_style,
format_progress_bar, format_shortcuts_styled, format_speed, header_style, format_bytes_progress, format_progress_bar, format_shortcuts_styled,
highlight_style, inactive_style, primary_style, warning_style, format_speed, header_style, highlight_style, inactive_style, primary_style,
COLOR_PRIMARY, HIGHLIGHT_SYMBOL, warning_style,
}; };
use color_eyre::Result; use color_eyre::Result;
use ratatui::{ use ratatui::{
crossterm::event::{self, poll, Event, KeyCode, KeyEvent, KeyEventKind}, DefaultTerminal, Frame,
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, poll},
layout::{Alignment, Constraint, Layout}, layout::{Alignment, Constraint, Layout},
style::{Modifier, Style}, style::{Modifier, Style},
text::{Line, Span}, text::{Line, Span},
@@ -15,11 +16,10 @@ use ratatui::{
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, StatefulWidget, Block, Borders, Cell, HighlightSpacing, Paragraph, Row, StatefulWidget,
Table, TableState, Table, TableState,
}, },
DefaultTerminal, Frame,
}; };
use soulseek_rs::{Client, DownloadStatus}; use soulseek_rs::{Client, DownloadStatus};
use std::{ use std::{
sync::{mpsc, mpsc::Receiver, Arc}, sync::{Arc, mpsc, mpsc::Receiver},
thread, thread,
time::Duration, time::Duration,
}; };
@@ -82,18 +82,18 @@ impl MultiDownloadProgress {
let mut need_start_next = false; let mut need_start_next = false;
for download_entry in &mut self.downloads { for download_entry in &mut self.downloads {
if let Some(receiver) = &download_entry.receiver if let Some(receiver) = &download_entry.receiver
&& let Ok(status) = receiver.try_recv() { && let Ok(status) = receiver.try_recv()
let was_active = !download_entry.download.is_finished(); {
download_entry.download.status = status; let was_active = !download_entry.download.is_finished();
let is_finished = download_entry.download.is_finished(); download_entry.download.status = status;
let is_finished = download_entry.download.is_finished();
// If download just finished, decrement active count // If download just finished, decrement active count
if was_active && is_finished { if was_active && is_finished {
self.active_count = self.active_count = self.active_count.saturating_sub(1);
self.active_count.saturating_sub(1); need_start_next = true;
need_start_next = true;
}
} }
}
} }
// Start next batch after iteration completes // Start next batch after iteration completes
@@ -103,9 +103,10 @@ impl MultiDownloadProgress {
// Handle keyboard input // Handle keyboard input
if poll(Duration::from_millis(100))? if poll(Duration::from_millis(100))?
&& let Event::Key(key) = event::read()? { && let Event::Key(key) = event::read()?
self.handle_key(key); {
} self.handle_key(key);
}
// Exit when user cancels or all downloads finished // Exit when user cancels or all downloads finished
if self.should_exit || self.all_finished() { if self.should_exit || self.all_finished() {
+35 -34
View File
@@ -3,28 +3,28 @@ use crate::models::{
SearchStatus, SearchStatus,
}; };
use crate::ui::panes::{ use crate::ui::panes::{
render_downloads_pane, render_results_pane, render_searches_pane, ResultsPaneParams, render_downloads_pane, render_results_pane,
ResultsPaneParams, render_searches_pane,
}; };
use crate::ui::{ use crate::ui::{
border_style, border_type, format_shortcuts_styled, render_download_stats, border_style, border_type, format_shortcuts_styled, render_download_stats,
}; };
use color_eyre::Result; use color_eyre::Result;
use ratatui::{ use ratatui::{
DefaultTerminal, Frame,
crossterm::event::{ crossterm::event::{
self, poll, Event, KeyCode, KeyEvent, KeyEventKind, MouseButton, self, Event, KeyCode, KeyEvent, KeyEventKind, MouseButton, MouseEvent,
MouseEvent, MouseEventKind, MouseEventKind, poll,
}, },
layout::{Constraint, Layout}, layout::{Constraint, Layout},
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
DefaultTerminal, Frame,
}; };
use soulseek_rs::{Client, DownloadStatus}; use soulseek_rs::{Client, DownloadStatus};
use std::{ use std::{
sync::{ sync::{
Arc,
atomic::AtomicBool, atomic::AtomicBool,
mpsc::{self}, mpsc::{self},
Arc,
}, },
thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
@@ -521,35 +521,35 @@ impl MainTui {
// Check if click is within searches pane // Check if click is within searches pane
if let Some(area) = self.state.searches_pane_area if let Some(area) = self.state.searches_pane_area
&& col >= area.x && col >= area.x
&& col < area.x + area.width && col < area.x + area.width
&& row >= area.y && row >= area.y
&& row < area.y + area.height && row < area.y + area.height
{ {
self.state.focused_pane = FocusedPane::Searches; self.state.focused_pane = FocusedPane::Searches;
return Ok(()); return Ok(());
} }
// Check if click is within results pane // Check if click is within results pane
if let Some(area) = self.state.results_pane_area if let Some(area) = self.state.results_pane_area
&& col >= area.x && col >= area.x
&& col < area.x + area.width && col < area.x + area.width
&& row >= area.y && row >= area.y
&& row < area.y + area.height && row < area.y + area.height
{ {
self.state.focused_pane = FocusedPane::Results; self.state.focused_pane = FocusedPane::Results;
return Ok(()); return Ok(());
} }
// Check if click is within downloads pane // Check if click is within downloads pane
if let Some(area) = self.state.downloads_pane_area if let Some(area) = self.state.downloads_pane_area
&& col >= area.x && col >= area.x
&& col < area.x + area.width && col < area.x + area.width
&& row >= area.y && row >= area.y
&& row < area.y + area.height && row < area.y + area.height
{ {
self.state.focused_pane = FocusedPane::Downloads; self.state.focused_pane = FocusedPane::Downloads;
return Ok(()); return Ok(());
} }
} }
Ok(()) Ok(())
@@ -747,13 +747,14 @@ impl MainTui {
// Update selected search if this is the active one // Update selected search if this is the active one
if let Some(selected_idx) = selected_search_index if let Some(selected_idx) = selected_search_index
&& selected_idx == idx { && selected_idx == idx
self.state.results_items = search.results.clone(); {
self.state.results_filtered_items = self.state.results_items = search.results.clone();
search.results.clone(); self.state.results_filtered_items =
self.state.results_filtered_indices = search.results.clone();
(0..search.results.len()).collect(); self.state.results_filtered_indices =
} (0..search.results.len()).collect();
}
} }
// Mark as completed after timeout // Mark as completed after timeout
+4 -4
View File
@@ -1,16 +1,16 @@
use crate::models::DownloadEntry; use crate::models::DownloadEntry;
use crate::ui::{ use crate::ui::{
border_style, border_type, error_style, format_bytes, format_speed, HIGHLIGHT_SYMBOL, border_style, border_type, error_style, format_bytes,
header_style, highlight_style, inactive_style, success_style, format_speed, header_style, highlight_style, inactive_style, success_style,
warning_style, HIGHLIGHT_SYMBOL, warning_style,
}; };
use ratatui::{ use ratatui::{
Frame,
layout::Rect, layout::Rect,
widgets::{ widgets::{
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table,
TableState, TableState,
}, },
Frame,
}; };
use soulseek_rs::DownloadStatus; use soulseek_rs::DownloadStatus;
+1 -1
View File
@@ -3,5 +3,5 @@ mod results_pane;
mod searches_pane; mod searches_pane;
pub use downloads_pane::render_downloads_pane; pub use downloads_pane::render_downloads_pane;
pub use results_pane::{render_results_pane, ResultsPaneParams}; pub use results_pane::{ResultsPaneParams, render_results_pane};
pub use searches_pane::render_searches_pane; pub use searches_pane::render_searches_pane;
+3 -3
View File
@@ -1,15 +1,15 @@
use crate::models::FileDisplayData; use crate::models::FileDisplayData;
use crate::ui::{ use crate::ui::{
border_style, border_type, format_bytes, header_style, highlight_style, HIGHLIGHT_SYMBOL, border_style, border_type, format_bytes, header_style,
HIGHLIGHT_SYMBOL, highlight_style,
}; };
use ratatui::{ use ratatui::{
Frame,
layout::Rect, layout::Rect,
widgets::{ widgets::{
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table,
TableState, TableState,
}, },
Frame,
}; };
use std::collections::HashSet; use std::collections::HashSet;
+3 -3
View File
@@ -1,12 +1,12 @@
use crate::models::{SearchEntry, SearchStatus}; use crate::models::{SearchEntry, SearchStatus};
use crate::ui::{ use crate::ui::{
border_style, border_type, header_style, highlight_style, success_style, HIGHLIGHT_SYMBOL, border_style, border_type, header_style, highlight_style,
warning_style, HIGHLIGHT_SYMBOL, success_style, warning_style,
}; };
use ratatui::{ use ratatui::{
Frame,
layout::Rect, layout::Rect,
widgets::{Block, Borders, Cell, HighlightSpacing, Row, Table, TableState}, widgets::{Block, Borders, Cell, HighlightSpacing, Row, Table, TableState},
Frame,
}; };
pub fn render_searches_pane( pub fn render_searches_pane(