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,6 +1,7 @@
|
||||
use crate::actor::{Actor, ActorHandle, ConnectionState};
|
||||
use crate::client::ClientOperation;
|
||||
use crate::dispatcher::MessageDispatcher;
|
||||
use crate::message::server::CheckPrivilegesHandler;
|
||||
use crate::message::server::ConnectToPeerHandler;
|
||||
use crate::message::server::ExcludedSearchPhrasesHandler;
|
||||
use crate::message::server::FileSearchHandler;
|
||||
@@ -166,6 +167,20 @@ pub enum ServerMessage {
|
||||
token: u32,
|
||||
query: String,
|
||||
},
|
||||
/// A wishlist search (code 103): a stored query the server lets us repeat
|
||||
/// once per announced interval.
|
||||
WishlistSearch {
|
||||
token: u32,
|
||||
query: String,
|
||||
},
|
||||
/// The server announced how often it will accept a wishlist search.
|
||||
WishlistInterval(u32),
|
||||
/// Everyone the server counts as privileged (code 69).
|
||||
PrivilegedUsers(Vec<String>),
|
||||
/// Seconds of our own privileges left (code 92).
|
||||
OwnPrivileges(u32),
|
||||
/// Ask the server for the answer to the above.
|
||||
CheckPrivileges,
|
||||
/// A search the server distributed to us from another user; if it matches
|
||||
/// our shares we reply with a FileSearchResponse.
|
||||
FileSearchRequest {
|
||||
@@ -391,7 +406,7 @@ impl ServerActor {
|
||||
handlers.register_handler(WishListIntervalHandler);
|
||||
handlers.register_handler(ParentMinSpeedHandler);
|
||||
handlers.register_handler(ParentSpeedRatioHandler);
|
||||
handlers.register_handler(PrivilegedUsersHandler);
|
||||
handlers.register_handler(CheckPrivilegesHandler);
|
||||
handlers.register_handler(FileSearchHandler);
|
||||
handlers.register_handler(GetPeerAddressHandler);
|
||||
handlers.register_handler(ConnectToPeerHandler);
|
||||
@@ -549,6 +564,39 @@ impl ServerActor {
|
||||
} => {
|
||||
self.handle_file_search_request(username, token, query);
|
||||
}
|
||||
other => self.handle_standing_message(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// The wishlist and privilege traffic: standing searches (codes 103/104) and
|
||||
/// who is privileged (codes 69/92).
|
||||
///
|
||||
/// Split out only because it keeps `handle_message`'s match to a readable
|
||||
/// length; there is no behaviour here beyond dispatch.
|
||||
fn handle_standing_message(&mut self, message: ServerMessage) {
|
||||
match message {
|
||||
ServerMessage::WishlistSearch { token, query } => {
|
||||
self.queue_message(MessageFactory::build_wishlist_search(
|
||||
token, &query,
|
||||
));
|
||||
}
|
||||
ServerMessage::WishlistInterval(seconds) => {
|
||||
self.forward_to_client(ClientOperation::WishlistInterval(
|
||||
seconds,
|
||||
));
|
||||
}
|
||||
ServerMessage::PrivilegedUsers(users) => {
|
||||
self.forward_to_client(ClientOperation::PrivilegedUsers(users));
|
||||
}
|
||||
ServerMessage::OwnPrivileges(seconds) => {
|
||||
self.forward_to_client(ClientOperation::OwnPrivileges(seconds));
|
||||
}
|
||||
ServerMessage::CheckPrivileges => {
|
||||
self.queue_message(MessageFactory::build_check_privileges());
|
||||
}
|
||||
other => {
|
||||
error!("[server] unroutable message: {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user