feat!: wishlist, upload-queue privilege recognition, and search filters (#20)
Closes the last two gaps in Soulseek's list of features an alternative client must implement to be tolerated, and adds the result filters Nicotine+ has had all along. Wishlist: server code 103 is sent and code 104's announced interval is kept rather than logged and discarded, so the re-search timer runs off the schedule the server set. `wish add|remove|list|run` plus `serve --wishlist`; wishes persist in config.toml as a TOML array so a comma inside a query survives. `wish run` prints search-shaped records, so it pipes into `download --stdin`. Privilege recognition: the code-69 user list is parsed instead of counted and dropped, and code 92 answers `whoami` as `privilege_seconds`. Uploads now go through a queue with a slot cap (`--upload-slots`, default 2) that serves privileged users first — the cap is what makes the ordering mean anything, so they are one change. `PlaceInQueueRequest` (peer code 51) is answered, where it previously had no handler at all, and a departed peer's slot is released so a stalled peer cannot wedge uploads shut. Search filters: `--exclude`, `--extension`, `--min-size`, `--max-size` beside the existing `--min-bitrate` and `--free-slots`, in one flattened arg group shared by `search`, `get` and `wish run`. Also serialises the library e2e suite behind the same gate `cli_e2e` already uses: the new queue tests hold peer sockets open on purpose, which starved concurrently-spawned soulfinds and failed unrelated download tests with `login: Timeout`. BREAKING CHANGE: `UploadStatus` gains a `Queued(u32)` variant, so an exhaustive match on it no longer compiles, and `cli::Commands` gains `Wish`.
This commit is contained in:
+83
-12
@@ -168,6 +168,10 @@ pub enum Commands {
|
||||
/// List the files a user shares
|
||||
Browse(BrowseArgs),
|
||||
|
||||
/// Standing searches the server re-runs on its own schedule
|
||||
#[command(subcommand)]
|
||||
Wish(WishCommand),
|
||||
|
||||
/// Chat rooms
|
||||
#[command(subcommand)]
|
||||
Room(RoomCommand),
|
||||
@@ -265,6 +269,15 @@ pub struct ServeArgs {
|
||||
/// Stay online until interrupted, ignoring --duration
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub follow: bool,
|
||||
|
||||
/// Also re-run the wishlist on the interval the server sets, reporting
|
||||
/// matches as they arrive
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub wishlist: bool,
|
||||
|
||||
/// How many uploads to run at once; the rest queue, privileged users first
|
||||
#[arg(long, value_name = "N", value_parser = clap::value_parser!(u8).range(1..=64))]
|
||||
pub upload_slots: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
@@ -353,11 +366,10 @@ pub enum Pick {
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct SearchArgs {
|
||||
/// What to search for
|
||||
pub query: String,
|
||||
|
||||
/// The result restrictions every command that searches accepts, so `search`,
|
||||
/// `get` and `wish run` all narrow a result set the same way.
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct FilterArgs {
|
||||
/// Drop results below this bitrate in kbps
|
||||
#[arg(long, value_name = "KBPS")]
|
||||
pub min_bitrate: Option<u32>,
|
||||
@@ -366,6 +378,31 @@ pub struct SearchArgs {
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub free_slots: bool,
|
||||
|
||||
/// Drop results whose path contains this term; repeat for several
|
||||
#[arg(long, value_name = "TERM", action = ArgAction::Append)]
|
||||
pub exclude: Vec<String>,
|
||||
|
||||
/// Keep only this file extension, with or without a dot; repeat for several
|
||||
#[arg(long, value_name = "EXT", action = ArgAction::Append)]
|
||||
pub extension: Vec<String>,
|
||||
|
||||
/// Drop results smaller than this many bytes
|
||||
#[arg(long, value_name = "BYTES")]
|
||||
pub min_size: Option<u64>,
|
||||
|
||||
/// Drop results larger than this many bytes
|
||||
#[arg(long, value_name = "BYTES")]
|
||||
pub max_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct SearchArgs {
|
||||
/// What to search for
|
||||
pub query: String,
|
||||
|
||||
#[command(flatten)]
|
||||
pub filter: FilterArgs,
|
||||
|
||||
/// Print at most this many results (0 means all)
|
||||
#[arg(short = 'n', long, default_value_t = 0)]
|
||||
pub limit: usize,
|
||||
@@ -417,13 +454,8 @@ pub struct GetArgs {
|
||||
#[arg(short = 'n', long, default_value_t = 0)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Drop results below this bitrate in kbps
|
||||
#[arg(long, value_name = "KBPS")]
|
||||
pub min_bitrate: Option<u32>,
|
||||
|
||||
/// Keep only peers advertising a free upload slot
|
||||
#[arg(long, action = ArgAction::SetTrue)]
|
||||
pub free_slots: bool,
|
||||
#[command(flatten)]
|
||||
pub filter: FilterArgs,
|
||||
|
||||
/// Seconds to wait for each transfer before giving up
|
||||
#[arg(
|
||||
@@ -450,6 +482,45 @@ pub struct BrowseArgs {
|
||||
pub timeout: u64,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum WishCommand {
|
||||
/// Store a query and search for it from now on
|
||||
Add {
|
||||
/// What to keep looking for
|
||||
query: String,
|
||||
},
|
||||
|
||||
/// Stop looking for a query
|
||||
Remove {
|
||||
/// The wish to drop, as shown by `wish list`
|
||||
query: String,
|
||||
},
|
||||
|
||||
/// Print the stored queries
|
||||
List,
|
||||
|
||||
/// Search every stored query once and print what came back
|
||||
Run(WishRunArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct WishRunArgs {
|
||||
/// Only run this wish instead of all of them
|
||||
#[arg(long, value_name = "QUERY")]
|
||||
pub only: Option<String>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub filter: FilterArgs,
|
||||
|
||||
/// Print at most this many results per wish (0 means all)
|
||||
#[arg(short = 'n', long, default_value_t = 0)]
|
||||
pub limit: usize,
|
||||
|
||||
/// Result order
|
||||
#[arg(long, value_enum, default_value_t = SortKey::Best)]
|
||||
pub sort: SortKey,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum RoomCommand {
|
||||
/// List public rooms and their user counts
|
||||
|
||||
Reference in New Issue
Block a user