feat(cli)!: replace the one-shot commands with a scriptable surface
The one-shot half of the binary could not finish its headline task without a human at the keyboard: `search` opened a full-screen picker, panicked with exit 101 when there was no terminal, and no command could fetch a known file at all. Everything exited 0 whatever happened, wrote emoji prose to stdout, and ignored --download-dir, SOULSEEK_DOWNLOAD_DIR and config.toml. The commands are now built for scripts and agents: search download get browse room list|say|listen message send|read stdout carries records only (tab-separated, or NDJSON under --json), stderr carries progress, and the exit code carries the verdict: 2 usage, 3 connect, 4 no results, 5 timed out, 6 transfer failed. `get` does search, rank and fetch in one step, and `search --json | jq ... | download --stdin` composes. Global flags parse in any position and every one has a SOULSEEK_ variable and a config.toml key resolved through a single precedence chain. Defects found and fixed along the way, each covered by a test: - a download with an unknown size was finalized against zero bytes and saved as an empty file; the size now comes from the peer's share listing - transfers are keyed by a hash of the remote path, so the same path from two peers shadowed itself and two files sharing a basename overwrote each other; both collisions are now dropped with a warning - `message send` and `room say` slept 500ms and claimed success; they now wait for a server reply queued behind the message before reporting - Client::login waited on an unbounded channel and never returned when the server went quiet Deleted the TUI picker the old `search` drove (download_selector, MultiDownloadProgress, SearchConfig): 1,573 lines out, 926 in. The interactive TUI is otherwise untouched. BREAKING CHANGE: `rooms` and `chat` become `room list|say|listen` and `message send|read`; `--disable-listener` becomes `--no-listener`; the unprefixed DISABLE_LISTENER, LISTENER_PORT, LOG_FILE and MAX_CONCURRENT_DOWNLOADS variables are now SOULSEEK_-prefixed; `search` no longer opens a picker or downloads anything.
This commit is contained in:
@@ -5,6 +5,11 @@ use super::{
|
||||
TcpStream, debug, error, info, mpsc, thread, trace,
|
||||
};
|
||||
|
||||
/// Ceiling on the wait for a login verdict. Generous enough for a slow server
|
||||
/// and a retrying connect, short enough that an unattended caller ends.
|
||||
const LOGIN_RESPONSE_TIMEOUT: std::time::Duration =
|
||||
std::time::Duration::from_secs(45);
|
||||
|
||||
impl Client {
|
||||
pub fn connect(&mut self) -> Result<()> {
|
||||
let (sender, message_reader): (
|
||||
@@ -89,6 +94,13 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Log in and wait for the server's verdict.
|
||||
///
|
||||
/// The wait is bounded: the actor only answers once it has a working
|
||||
/// connection, so a server that never completes the TCP handshake would
|
||||
/// otherwise block the caller forever. A caller that hits
|
||||
/// [`SoulseekRs::Timeout`] can retry or give up, but it always regains
|
||||
/// control.
|
||||
pub fn login(&self) -> Result<bool> {
|
||||
info!("Logging in as {}", self.username);
|
||||
if let Some(handle) = &self.server_handle {
|
||||
@@ -99,7 +111,7 @@ impl Client {
|
||||
response: tx,
|
||||
});
|
||||
|
||||
match rx.recv() {
|
||||
match rx.recv_timeout(LOGIN_RESPONSE_TIMEOUT) {
|
||||
Ok(result) => result,
|
||||
Err(_) => Err(SoulseekRs::Timeout),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user