//! 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, 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, 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, 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, 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)); } }