feat: browse another user's shared files

Add Client::browse_user (sends GetShareFileList, code 4, connecting first if
needed via the same queue-and-flush path as search replies) and
take_browse_result. A new SharedFileListResponseHandler parses the peer's
code-5 reply into SharedDirectory entries, forwarded as
ClientOperation::BrowseResult and stored per user. Verified end to end: a
second real client browses a sharer and receives its file listing.
This commit is contained in:
michel
2026-07-19 18:56:19 +02:00
parent 9e050b59d9
commit f3218475dc
7 changed files with 175 additions and 4 deletions
+85 -1
View File
@@ -9,7 +9,7 @@ use crate::{
Transfer,
actor::{ActorSystem, peer_registry::PeerRegistry},
error::{Result, SoulseekRs},
message::peer::{FileEntry, build_file_search_response},
message::peer::{FileEntry, SharedDirectory, build_file_search_response},
peer::{
ConnectionType, DownloadPeer, NewPeer, Peer, PeerMessage,
listen::Listen,
@@ -177,6 +177,11 @@ pub enum ClientOperation {
ShareListRequested {
requester_key: String,
},
/// A peer we are browsing returned their shared-file listing.
BrowseResult {
username: String,
directories: Vec<SharedDirectory>,
},
/// A direct outbound connection to this peer failed before it was
/// established — the peer is likely firewalled, so fall back to asking the
/// server to broker the connection. Carries the reporting actor's id.
@@ -202,6 +207,8 @@ pub struct ClientContext {
uploads: HashMap<u32, UploadJob>,
/// Upload tokens waiting for the downloader's address to be resolved.
pending_serves: HashMap<String, Vec<u32>>,
/// Shared-file listings received from peers we browsed.
browse_results: HashMap<String, Vec<SharedDirectory>>,
actor_system: Arc<ActorSystem>,
}
impl Default for ClientContext {
@@ -491,6 +498,7 @@ impl ClientContext {
pending_peer_messages: HashMap::new(),
uploads: HashMap::new(),
pending_serves: HashMap::new(),
browse_results: HashMap::new(),
downloads: DownloadStore::new(),
actor_system,
}
@@ -535,6 +543,23 @@ impl ClientContext {
.unwrap_or_default()
}
/// Store a shared-file listing received from browsing `username`.
pub fn store_browse_result(
&mut self,
username: String,
directories: Vec<SharedDirectory>,
) {
self.browse_results.insert(username, directories);
}
/// Remove and return the shared-file listing browsed from `username`.
pub fn take_browse_result(
&mut self,
username: &str,
) -> Option<Vec<SharedDirectory>> {
self.browse_results.remove(username)
}
/// Remember that a server-brokered connection to `username` is pending under
/// `token`; the peer will quote it back in a PierceFirewall.
pub fn add_pending_connect(&mut self, token: u32, username: String) {
@@ -721,6 +746,53 @@ impl Client {
Ok(())
}
/// Request a peer's shared-file listing. When it arrives it can be
/// retrieved with [`Client::take_browse_result`].
///
/// # Errors
/// Returns an error if the client's context lock is poisoned.
pub fn browse_user(&self, username: &str) -> Result<()> {
let request =
crate::message::server::MessageFactory::build_get_share_file_list();
let (connected, registry) = {
let ctx = self.context.read_safe()?;
(
ctx.peer_registry
.as_ref()
.is_some_and(|r| r.contains(username)),
ctx.peer_registry.clone(),
)
};
if connected {
if let Some(registry) = registry {
let _ = registry
.send_to_peer(username, PeerMessage::SendMessage(request));
}
} else {
self.context
.write_safe()?
.queue_peer_message(username, request);
if let Some(handle) = &self.server_handle {
let _ = handle
.send(ServerMessage::GetPeerAddress(username.to_string()));
}
}
Ok(())
}
/// Remove and return a peer's shared-file listing requested via
/// [`Client::browse_user`], if it has arrived.
#[must_use]
pub fn take_browse_result(
&self,
username: &str,
) -> Option<Vec<SharedDirectory>> {
self.context
.write_safe()
.ok()
.and_then(|mut ctx| ctx.take_browse_result(username))
}
/// Ask the server for a peer's address and open a direct control
/// connection to it. Downloads queued for that peer are sent automatically
/// once the connection is established.
@@ -1821,6 +1893,18 @@ impl Client {
);
}
}
ClientOperation::BrowseResult {
username,
directories,
} => {
if let Ok(mut ctx) = client_context.write_safe()
{
ctx.store_browse_result(
username,
directories,
);
}
}
ClientOperation::PeerConnectFailed(
id,
username,