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:
@@ -87,6 +87,38 @@ impl MessageFactory {
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Ask the server (code 92) how much of our own privilege time is left.
|
||||
#[must_use]
|
||||
pub fn build_check_privileges() -> Message {
|
||||
Message::new().write_int32(92).clone()
|
||||
}
|
||||
|
||||
/// Tell a peer where their queued file sits (peer code 44). Place counts
|
||||
/// from 1.
|
||||
#[must_use]
|
||||
pub fn build_place_in_queue_response(
|
||||
filename: &str,
|
||||
place: u32,
|
||||
) -> Message {
|
||||
Message::new()
|
||||
.write_int32(44)
|
||||
.write_string(filename)
|
||||
.write_int32(place)
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// A wishlist search (server code 103). Same shape as a plain `FileSearch`,
|
||||
/// but the server rate-limits it to the interval it announced in code 104
|
||||
/// and does not count it against the normal search allowance.
|
||||
#[must_use]
|
||||
pub fn build_wishlist_search(token: u32, query: &str) -> Message {
|
||||
Message::new()
|
||||
.write_int32(103)
|
||||
.write_int32(token)
|
||||
.write_string(query)
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn build_set_status_message(status_code: u32) -> Message {
|
||||
Message::new()
|
||||
@@ -368,6 +400,37 @@ fn test_build_room_list_request() {
|
||||
assert_eq!(vec![64, 0, 0, 0], message.get_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_check_privileges() {
|
||||
assert_eq!(
|
||||
vec![92, 0, 0, 0],
|
||||
MessageFactory::build_check_privileges().get_data()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_place_in_queue_response() {
|
||||
let message = MessageFactory::build_place_in_queue_response("song.mp3", 3);
|
||||
let expect: Vec<u8> = [
|
||||
44, 0, 0, 0, // code
|
||||
8, 0, 0, 0, 115, 111, 110, 103, 46, 109, 112, 51, // "song.mp3"
|
||||
3, 0, 0, 0, // place
|
||||
]
|
||||
.to_vec();
|
||||
assert_eq!(expect, message.get_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_wishlist_search() {
|
||||
let message = MessageFactory::build_wishlist_search(12, "trance wax");
|
||||
let expect: Vec<u8> = [
|
||||
103, 0, 0, 0, 12, 0, 0, 0, 10, 0, 0, 0, 116, 114, 97, 110, 99, 101, 32,
|
||||
119, 97, 120,
|
||||
]
|
||||
.to_vec();
|
||||
assert_eq!(expect, message.get_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_file_search_message() {
|
||||
let message = MessageFactory::build_file_search_message(12, "trance wax");
|
||||
|
||||
Reference in New Issue
Block a user