Implement the FIREWALLED connection mode: when a direct outbound connection to a peer fails (peer unreachable), fall back to asking the server to broker it. - Add MessageFactory::build_connect_to_peer (server code 18). - A pre-established outbound failure now emits ClientOperation::PeerConnectFailed instead of a normal disconnect; the client registers a correlation token and sends ConnectToPeer so the peer connects back. - The listener accepts the peer's PierceFirewall (peer code 0) connect-back, matches the pending token, registers it as the P control connection, and fires PeerConnected to flush the queued download. Fix a latent aliasing bug this surfaced: a peer actor could emit two terminal notifications (a connect-failure, then a disconnect when later replaced and stopped), and the second removed the *newer* actor registered under the same username, killing the brokered connection mid-transfer. An actor now reports a terminal outcome at most once (disconnect_reported guard). Verified end to end against soulfind: the mock advertises an unlistened port so the direct connect is refused, the server brokers the connection, the mock pierces back, and the file transfers — bytes match. A companion test (soulfind_brokers_connect_to_peer_between_users) pins the server behaviour the mode relies on.
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(())
}