Files
SoulStream/crates/soulseek-client/src/responder.rs
T
KeelrLeek a667b4b28e Baseline: full tree snapshot after git repository loss
The previous .git directory was found gutted (only info/ remained), so
history is unrecoverable locally. This commit restores version control
with the tree exactly as it stands: the working app, the preview-tracks
launcher on the artist hero, the scrollable-track-previews design bundle
(docs + approved mockups), and the Phase 0 validation artifacts.
2026-07-22 17:57:00 -05:00

100 lines
3.2 KiB
Rust

//! Responder: answers other peers' requests — searches, browse, user-info,
//! folder contents. Search replies open a fresh P connection to the
//! searcher; the rest reply on the connection the request arrived on.
use std::sync::atomic::Ordering;
use std::sync::Arc;
use soulseek_proto::peer;
use crate::{conn, Event, Inner, PeerHandle};
/// Max files we return in a single search reply.
const MAX_SEARCH_RESULTS: usize = 100;
/// Match a relayed search against our shares and, if we have hits, send a
/// PeerSearchResponse back to `username`. Runs on its own task.
pub(crate) fn respond_to_search(inner: Arc<Inner>, username: String, token: u32, query: String) {
inner.searches_seen.fetch_add(1, Ordering::Relaxed);
// Never answer our own searches; de-dupe repeated relays of a token.
if username == inner.own_username() {
return;
}
{
let mut answered = inner.answered_searches.lock().unwrap();
if answered.contains(&token) {
return;
}
answered.insert(token);
// Bound the set so it can't grow without limit.
if answered.len() > 20_000 {
answered.clear();
}
}
if inner.shares.file_count() == 0 {
return;
}
tokio::spawn(async move {
let excluded = inner.excluded();
let files = inner.shares.search(&query, &excluded, MAX_SEARCH_RESULTS);
if files.is_empty() {
return;
}
let frame = peer::search_response(
&inner.own_username(),
token,
&files,
inner.has_free_upload_slot(),
inner.upload_speed.load(Ordering::Relaxed),
inner.upload_queue.lock().unwrap().len() as u32,
);
// Search replies use a normal P connection to the searcher.
match conn::get_or_connect_peer(&inner, &username).await {
Ok(handle) => {
if handle.write_tx.send(frame).is_ok() {
let _ = inner.event_tx.send(Event::RespondedToSearch {
username: username.clone(),
files: files.len(),
});
}
}
Err(e) => log::debug!("couldn't reply to search from {username}: {e}"),
}
});
}
/// Answer a BrowseRequest on the same connection.
pub(crate) fn respond_browse(inner: &Arc<Inner>, handle: &PeerHandle) {
let dirs = inner.shares.browse();
let _ = handle.write_tx.send(peer::browse_response(&dirs));
}
/// Answer a UserInfoRequest.
pub(crate) fn respond_user_info(inner: &Arc<Inner>, handle: &PeerHandle) {
let frame = peer::user_info_response(
&inner.description,
inner.upload_slots,
inner.upload_queue.lock().unwrap().len() as u32,
inner.has_free_upload_slot(),
);
let _ = handle.write_tx.send(frame);
}
/// Answer a FolderContentsRequest for one folder.
pub(crate) fn respond_folder_contents(
inner: &Arc<Inner>,
handle: &PeerHandle,
token: u32,
folder: &str,
) {
if let Some(dir) = inner.shares.folder(folder) {
let _ = handle
.write_tx
.send(peer::folder_contents_response(token, folder, &dir));
}
}