The chat-room test gave both joins 500 ms before speaking; a line said before bob is in the room never reaches him. It now waits until each has the member list the server sends once they are in. The password test slept 500 ms and then disconnected. soulfind stores a new password only after hashing it, and drops the change if the session closes first, so a slow hash let the old password keep working. The session now stays up until a login with the new password is accepted.
soulseek-rs-lib
A Rust library for implementing the Soulseek peer-to-peer protocol.
Website: https://re-invention.nl/soulseek-rs/
About
This library provides the core functionality for interacting with the Soulseek network. It can be used to build custom Soulseek clients.
Usage
Add it to your Cargo.toml:
cargo add soulseek-rs-lib
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];
let (_download, status) = client.download(
file.name.clone(),
file.username.clone(),
file.size,
"~/Downloads".to_string(),
)?;
// `download` returns as soon as the request is queued: the transfer is
// finished when the channel says so, not when this call returns.
for update in status {
println!("{}: {update:?}", 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(), 2416),
enable_listen: true,
listen_port: 3000,
..ClientSettings::new("username", "password")
};
let mut client = Client::with_settings(settings);
client.connect()?;
client.login()?;
Ok(())
}
Asking about a peer
The server-backed lookups are request/poll pairs: you ask, then read the
answer once it lands. request_user_info clears any previous answer first, so
the loop below observes this request rather than the last one.
use soulseek_rs::Client;
use std::thread::sleep;
use std::time::{Duration, Instant};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::new("username", "password");
client.connect()?;
client.login()?;
client.request_user_info("someuser")?;
let deadline = Instant::now() + Duration::from_secs(10);
while Instant::now() < deadline {
if let Some(info) = client.user_info("someuser") {
// Presence and stats are separate replies, so each is an Option:
// a reply that never arrived is None, not "offline" or "shares nothing".
println!("{:?} {:?}", info.presence, info.stats);
break;
}
sleep(Duration::from_millis(100));
}
// Room membership is kept current from join and leave events.
println!("{:?}", client.room_members("lobby"));
Ok(())
}
ClientOperation is #[non_exhaustive]: every protocol message the client
learns to handle adds a variant, so match it with a wildcard arm. SharedFile
is too, so build it with the .. syntax rather than listing every field.