Two related lifecycle defects in the direct/firewalled download path: - On a failed direct connect, ClientOperation::PeerConnectFailed left the dead outbound actor registered and running. The actor pinned a thread-pool worker forever (its loop only exits on Stop), so a handful of unreachable-peer downloads could exhaust the fixed pool and wedge the whole client; and a later download() to the same peer saw registry.contains()==true and queued into the dead, streamless actor, hanging silently. The arm now removes and stops the dead actor before brokering, mirroring PeerDisconnected. - The server-brokered fallback had no timeout, so a peer that never connects back (offline / double-firewalled / unroutable address) left the download Queued forever and its Receiver blocked. Arm a bounded timeout: if no PierceFirewall consumes the correlation token within BROKER_CONNECT_TIMEOUT, fail the peer's queued downloads (on the channel and in the store) and reclaim the token. A successful pierce consumes the token first, so the timeout is a no-op on the happy path. Add a unit test for the queued-download failure helper; the direct and firewalled e2e tests still pass (pierce arrives well within the timeout).
soulseek-rs-lib
A Rust library for implementing the Soulseek peer-to-peer protocol.
About
This library provides the core functionality for interacting with the Soulseek network. It can be used to build custom Soulseek clients or bots.
Usage
Add this to your Cargo.toml:
[dependencies]
soulseek-rs-lib = "0.1.0"
Example
Simple Usage
use soulseek_rs::Client;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create and connect to Soulseek server
let mut client = Client::new("username", "password");
client.connect();
client.login()?;
// Search for files
let results = client.search("Alex Kassian lifestream", Duration::from_secs(10))?;
// Download first available file
if let Some(result) = results.iter().find(|r| !r.files.is_empty()) {
let file = &result.files[0];
client.download(
file.name.clone(),
file.username.clone(),
file.size,
"~/Downloads".to_string(),
)?;
println!("Downloaded: {}", file.name);
}
Ok(())
}
Advanced Configuration
use soulseek_rs::{Client, ClientSettings, PeerAddress};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client with custom settings
let settings = ClientSettings {
server_address: PeerAddress::new("server.slsknet.org".to_string(), 2242),
enable_listen: true,
listen_port: 3000,
..ClientSettings::new("username", "password")
};
let mut client = Client::with_settings(settings);
client.connect();
client.login()?;
Ok(())
}