feat(interests): keep our own list and send it again each login

The server holds interests only for the session that set them, so a client
that does not re-send its list comes back from a reconnect with none. That
is why Nicotine+ sends its whole list at every login, and why it lowercases
each item before sending — the server matches them case-insensitively, so
two spellings would otherwise be held as two interests.

Ours now keeps the list, in one spelling, and puts it back on the wire when
a session starts; an interest set before there is a connection is kept
rather than dropped. A room's ticker board is also let go when we leave the
room, since the server sends the whole board again on the next join.
This commit is contained in:
Michel de Graaf
2026-09-09 17:06:08 +00:00
parent 0630b548b9
commit 773b5a4100
7 changed files with 212 additions and 4 deletions
+27 -4
View File
@@ -265,10 +265,16 @@ impl Client {
/// Add an interest (code 51). Interests are what the recommendation and
/// similar-user queries below are computed from.
///
/// The server holds interests only for the session that set them, so the
/// list is kept here and sent again after every login. An interest added
/// before there is a connection is kept all the same — the error says
/// only that it could not go out yet, not that it was dropped.
///
/// # Errors
/// [`crate::SoulseekRs::NotConnected`] when there is no server connection.
pub fn add_interest(&self, item: &str) -> Result<()> {
self.send_server_message(MessageFactory::build_add_thing_i_like(item))
let item = self.context.write_safe()?.add_own_interest(item, true);
self.send_server_message(MessageFactory::build_add_thing_i_like(&item))
}
/// Drop an interest (code 52).
@@ -276,8 +282,9 @@ impl Client {
/// # Errors
/// [`crate::SoulseekRs::NotConnected`] when there is no server connection.
pub fn remove_interest(&self, item: &str) -> Result<()> {
let item = self.context.write_safe()?.remove_own_interest(item, true);
self.send_server_message(MessageFactory::build_remove_thing_i_like(
item,
&item,
))
}
@@ -286,7 +293,8 @@ impl Client {
/// # Errors
/// [`crate::SoulseekRs::NotConnected`] when there is no server connection.
pub fn add_dislike(&self, item: &str) -> Result<()> {
self.send_server_message(MessageFactory::build_add_thing_i_hate(item))
let item = self.context.write_safe()?.add_own_interest(item, false);
self.send_server_message(MessageFactory::build_add_thing_i_hate(&item))
}
/// Drop a dislike (code 118).
@@ -294,8 +302,9 @@ impl Client {
/// # Errors
/// [`crate::SoulseekRs::NotConnected`] when there is no server connection.
pub fn remove_dislike(&self, item: &str) -> Result<()> {
let item = self.context.write_safe()?.remove_own_interest(item, false);
self.send_server_message(MessageFactory::build_remove_thing_i_hate(
item,
&item,
))
}
@@ -318,6 +327,20 @@ impl Client {
self.send_server_message(MessageFactory::build_global_recommendations())
}
/// What we ourselves like and hate, as set through this client. The
/// server forgets these when the session ends, so they are re-sent after
/// each login.
#[must_use]
pub fn own_interests(&self) -> UserInterests {
match self.context.read_safe() {
Ok(ctx) => ctx.own_interests(),
Err(e) => {
error!("[client] own_interests: {}", e);
UserInterests::default()
}
}
}
/// The last reply to [`Client::request_recommendations`] as
/// (recommended, recommended-against), or `None` until one arrives.
#[must_use]