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:
@@ -1,4 +1,3 @@
|
||||
use crate::debug;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
@@ -6,17 +5,42 @@ use crate::{
|
||||
message::{Message, MessageHandler},
|
||||
};
|
||||
|
||||
/// `WishlistInterval` (code 104): how often the server will accept a wishlist
|
||||
/// search from us. Usually 720 seconds, or 120 for a privileged account.
|
||||
///
|
||||
/// The value is the server's rate limit, not a suggestion, so it is kept rather
|
||||
/// than logged: the re-search timer runs off it.
|
||||
pub struct WishListIntervalHandler;
|
||||
|
||||
// The server tells us the wishlist search interval.
|
||||
// This interval is almost always 12 minutes, or 2 minutes for privileged users.
|
||||
impl MessageHandler<ServerMessage> for WishListIntervalHandler {
|
||||
fn get_code(&self) -> u8 {
|
||||
104
|
||||
}
|
||||
|
||||
fn handle(&self, message: &mut Message, _sender: Sender<ServerMessage>) {
|
||||
let number = message.read_int32();
|
||||
debug!("Wishlist search interval: {} in seconds", number);
|
||||
fn handle(&self, message: &mut Message, sender: Sender<ServerMessage>) {
|
||||
let seconds = message.read_int32();
|
||||
let _ = sender.send(ServerMessage::WishlistInterval(seconds));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_announced_interval_is_reported_not_discarded() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let mut message = Message::new();
|
||||
message.write_raw_bytes(vec![0u8; 8]);
|
||||
message.write_int32(720);
|
||||
message.set_pointer(8);
|
||||
|
||||
WishListIntervalHandler.handle(&mut message, tx);
|
||||
match rx.try_recv() {
|
||||
Ok(ServerMessage::WishlistInterval(seconds)) => {
|
||||
assert_eq!(seconds, 720);
|
||||
}
|
||||
other => panic!("unexpected: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user