Stopping or restarting an attempt used to drop only the channel its result would arrive on. The thread kept its client for up to 45 seconds: that client held the configured listener port, so the replacement fell back to an ephemeral one, and a slow server could still accept the old login after the new one and kick the new session. Each attempt now carries its client's CancelHandle. Esc cancels it on the spot. A restart cancels the attempt it replaces on the UI thread, and the new attempt's thread starts by joining the old one, which has dropped its client, and with it the port, by the time it ends. A result the old attempt finished with but nobody collected is dropped before the new one connects. An attempt replaced before its thread got going skips connecting altogether, and a result that lands after a cancel is thrown away instead of handed over. Restarts in a row chain through the joins, so at most one client is connected or holds the port at a time, which is what Soulseek.NET's connection lock and Nicotine+'s single server connection guarantee too. A result is acted on only while the screen shows Connecting. One that lands after Esc, even a successful login, is dropped on the spot, so no stray session stays online until the next submit. The one-second restart floor applies only to restarting an attempt in flight, since a kept attempt no longer means one is running. A restart during the old attempt's share scan waits for that scan to finish; the scan cannot be interrupted.
soulseek-rs
A Soulseek client for the terminal: a full-screen TUI, and a one-shot command surface built for scripts and agents.
Website: https://re-invention.nl/soulseek-rs/
Installation
brew install michel/tap/soulseek-rs # macOS and Linux, prebuilt
cargo install soulseek-rs # anywhere with a Rust toolchain
Usage
Run it with no subcommand for the interactive TUI:
soulseek-rs
Anything else is a one-shot command that runs headless. stdout carries records only, stderr carries progress, and the exit code carries the verdict:
soulseek-rs search <QUERY> # print matching files
soulseek-rs get <QUERY> # search, pick the best, download
soulseek-rs download <USER> <PATH> # fetch one known file
soulseek-rs download --stdin # fetch files listed on stdin
soulseek-rs browse <USER> # list a user's shared files
soulseek-rs serve [--follow] # stay online sharing, stream uploads
soulseek-rs whoami # confirm credentials and connection
soulseek-rs user <NAME> # a peer's status and share counts
soulseek-rs room list|say|users|listen
soulseek-rs message send|read
soulseek-rs shares list|add|remove|status|reindex
soulseek-rs config path|list|get|set
soulseek-rs portmap # test automatic port mapping
soulseek-rs completions install # tab completion for bash/zsh/fish
Add --json for newline-delimited JSON instead of tab-separated text, so
commands compose:
soulseek-rs search "rjd2" --json \
| jq -c 'select(.size > 5000000 and .free_slot)' \
| soulseek-rs download --stdin
Exit codes: 0 success, 2 usage or config, 3 connect or login, 4 nothing
found, 5 timed out, 6 transfer failed, 7 session lost mid-command, 1
unexpected error.
The full README documents every
command, record shape, flag, environment variable and config.toml key.
Building from Source
git clone https://github.com/michel/soulseek-rs.git
cd soulseek-rs
cargo build --release
Library
To build your own Soulseek client, use the soulseek-rs-lib crate: the protocol implementation this client is built on.