feat(cli): expose sharing, peer lookup and settings as one-shot commands
An agent driving the binary could search, fetch and chat, but it could not be a peer: nothing kept a session online to serve files, nothing reported an upload, shares could only be set at startup, and there was no way to ask whether a peer was worth queueing to or to check credentials without doing something else. New commands: serve [--follow] stay online sharing; one record per upload whoami credentials, connection and what we offer user <NAME> a peer's presence and share counts room users <ROOM> the member roster shares list|add|remove|status|reindex config path|list|get|set `config` and `shares list|add|remove` need no account, so they run before the credential check — asking a script to log in for a `config get` would be a poor trade. `serve` is the only place uploads are visible: they live in the process serving them, so a one-shot `uploads list` could print nothing but an empty list. Library additions, all request/poll pairs matching the existing conventions: - Client::room_members, kept current from the join and leave events - Client::request_user_info / user_info, backed by new handlers for GetUserStatus (7) and GetUserStats (36) — codes the dispatcher previously logged as unhandled. The wire format is taken from soulfind's own source and pinned by an e2e test that asserts a peer's real share count. - UserInfo carries presence and stats as Options, so a reply that never arrived cannot be read as "offline" or "shares nothing" - request_user_info clears the previous answer, so the documented poll loop observes this request rather than the last one - ServerMessage and ClientOperation are now #[non_exhaustive]: every protocol message the client learns adds a variant, and that should not break callers - the crate root and prelude re-export every type a Client method returns Also pins the output contract with tests: under --json, stdout carries only newline-delimited JSON across fifteen command shapes while progress still goes to stderr, and a failing command writes nothing to stdout at all.
This commit is contained in:
@@ -24,6 +24,8 @@ Examples:
|
||||
| soulseek-rs download --stdin filter, then fetch
|
||||
soulseek-rs browse someuser --json list a peer's shares
|
||||
soulseek-rs room listen lobby --follow stream room chat
|
||||
soulseek-rs serve --follow --json share files, stream upload events
|
||||
soulseek-rs user someuser is this peer online and worth it
|
||||
|
||||
Exit codes:
|
||||
0 success 2 usage/config 3 connect or login 4 no results
|
||||
@@ -172,10 +174,104 @@ pub enum Commands {
|
||||
#[command(subcommand)]
|
||||
Message(MessageCommand),
|
||||
|
||||
/// Stay online sharing files, reporting each upload as it happens
|
||||
Serve(ServeArgs),
|
||||
|
||||
/// What the server knows about another user
|
||||
User(UserArgs),
|
||||
|
||||
/// Confirm the credentials and connection work
|
||||
Whoami,
|
||||
|
||||
/// The folders this client shares
|
||||
#[command(subcommand)]
|
||||
Shares(SharesCommand),
|
||||
|
||||
/// Read and write the config file
|
||||
#[command(subcommand)]
|
||||
Config(ConfigCommand),
|
||||
|
||||
/// Check whether the router opens the listen port (UPnP/NAT-PMP)
|
||||
Portmap,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct ServeArgs {
|
||||
/// Seconds to stay online before exiting
|
||||
#[arg(
|
||||
long,
|
||||
default_value_t = 3600,
|
||||
value_parser = clap::value_parser!(u64).range(1..=MAX_WAIT_SECS)
|
||||
)]
|
||||
pub duration: u64,
|
||||
|
||||
/// Stay online until interrupted, ignoring --duration
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub follow: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct UserArgs {
|
||||
/// The user to ask the server about
|
||||
pub user: String,
|
||||
|
||||
/// Seconds to wait for the server's answer
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 10,
|
||||
value_parser = clap::value_parser!(u64).range(1..=MAX_WAIT_SECS)
|
||||
)]
|
||||
pub timeout: u64,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum SharesCommand {
|
||||
/// List the configured share folders
|
||||
List,
|
||||
|
||||
/// Add a folder to the shares and save it to the config file
|
||||
Add {
|
||||
/// Folder to start sharing
|
||||
directory: String,
|
||||
},
|
||||
|
||||
/// Stop sharing a folder and save that to the config file
|
||||
Remove {
|
||||
/// Folder to stop sharing
|
||||
directory: String,
|
||||
},
|
||||
|
||||
/// Re-index the shares and report what the network will see
|
||||
Reindex,
|
||||
|
||||
/// Report how many folders and files are indexed
|
||||
Status,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum ConfigCommand {
|
||||
/// Print the path of the config file in use
|
||||
Path,
|
||||
|
||||
/// Print every setting and its effective value
|
||||
List,
|
||||
|
||||
/// Print one setting
|
||||
Get {
|
||||
/// Setting name, as shown by `config list`
|
||||
key: String,
|
||||
},
|
||||
|
||||
/// Change one setting in the config file
|
||||
Set {
|
||||
/// Setting name, as shown by `config list`
|
||||
key: String,
|
||||
/// New value; an empty string clears it
|
||||
value: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// How to order search results.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
|
||||
pub enum SortKey {
|
||||
@@ -319,6 +415,20 @@ pub enum RoomCommand {
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// List who is currently in a room
|
||||
Users {
|
||||
/// Room whose members to list
|
||||
room: String,
|
||||
/// Seconds to wait for the membership list
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = 10,
|
||||
value_parser = clap::value_parser!(u64).range(1..=MAX_WAIT_SECS)
|
||||
)]
|
||||
timeout: u64,
|
||||
},
|
||||
|
||||
/// Stream messages and joins/leaves from a room
|
||||
Listen {
|
||||
/// Room to listen to
|
||||
|
||||
Reference in New Issue
Block a user