feat(lib): per-member room statistics from the JoinRoom reply
The JoinRoom (code 14) reply carries, after the membership list, four parallel vectors describing each member: status, share statistics, free upload slots and country. The handler stopped after the usernames, so all of it was discarded. It is now parsed and exposed as Client::room_member_stats. Issue #48 asked for RoomStats (57) and RoomListStats (55). Those codes are UserInterests and MyRecommendations in the Soulseek protocol; there is no RoomStats message on the wire. Per-room user counts already arrive with RoomList (64), which the client handles, and the per-member statistics are part of the JoinRoom reply -- so that is where this adds them. - join_room.rs: parse the stat vectors, each bounded by the payload so a hostile count cannot hang or overallocate - types::RoomUserStats; every field looked up positionally so a server sending short vectors (or omitting countries) does not shift the roster - ServerMessage/ClientOperation::RoomMemberStats and the context snapshot behind Client::room_member_stats, replaced on each rejoin
This commit is contained in:
@@ -5,7 +5,8 @@ use crate::actor::server_actor::{
|
||||
use crate::download_store::{DownloadStore, collect_failed_tokens};
|
||||
use crate::types::{
|
||||
ClientVersion, DownloadMetadata, DownloadStatus, RoomEvent, RoomInfo,
|
||||
SessionLoss, SessionWatch, UserInfo, UserPresence, UserStats, UserStatus,
|
||||
RoomUserStats, SessionLoss, SessionWatch, UserInfo, UserPresence,
|
||||
UserStats, UserStatus,
|
||||
};
|
||||
use crate::utils::logger;
|
||||
use crate::{
|
||||
@@ -278,6 +279,11 @@ pub enum ClientOperation {
|
||||
/// Something happened in the chat-room subsystem (list refreshed, a room
|
||||
/// joined/left, a message said, a member joined/left).
|
||||
RoomEvent(RoomEvent),
|
||||
/// Per-member statistics from a `JoinRoom` reply.
|
||||
RoomMemberStats {
|
||||
room: String,
|
||||
stats: Vec<RoomUserStats>,
|
||||
},
|
||||
/// The server announced how many seconds must pass between wishlist
|
||||
/// searches.
|
||||
WishlistInterval(u32),
|
||||
@@ -325,6 +331,9 @@ pub struct ClientContext {
|
||||
/// Who is in each room we have joined, kept current from the membership
|
||||
/// the server sends on join plus the later join/leave events.
|
||||
room_members: HashMap<String, Vec<String>>,
|
||||
/// Per-member statistics for each joined room, from the stat vectors the
|
||||
/// server sends alongside the membership list.
|
||||
room_member_stats: HashMap<String, Vec<RoomUserStats>>,
|
||||
/// What the server has told us about other users, merged across the
|
||||
/// separate status and statistics replies.
|
||||
user_info: HashMap<String, UserInfo>,
|
||||
@@ -415,6 +424,7 @@ impl ClientContext {
|
||||
room_list: Vec::new(),
|
||||
room_events: Vec::new(),
|
||||
room_members: HashMap::new(),
|
||||
room_member_stats: HashMap::new(),
|
||||
user_info: HashMap::new(),
|
||||
wishlist_interval: None,
|
||||
privileged_users: HashSet::new(),
|
||||
@@ -515,6 +525,29 @@ impl ClientContext {
|
||||
self.room_members.get(room).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Record the per-member statistics of a room we just joined, replacing
|
||||
/// any previous snapshot for it.
|
||||
pub fn apply_room_member_stats(
|
||||
&mut self,
|
||||
room: String,
|
||||
stats: Vec<RoomUserStats>,
|
||||
) {
|
||||
self.room_member_stats.insert(room, stats);
|
||||
}
|
||||
|
||||
/// What the server reported about the members of `room`, sorted by name,
|
||||
/// or empty for a room we have not joined.
|
||||
#[must_use]
|
||||
pub fn room_member_stats(&self, room: &str) -> Vec<RoomUserStats> {
|
||||
let mut stats = self
|
||||
.room_member_stats
|
||||
.get(room)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
stats.sort_by(|a, b| a.username.cmp(&b.username));
|
||||
stats
|
||||
}
|
||||
|
||||
/// The latest snapshot of the public chat-room list.
|
||||
#[must_use]
|
||||
pub fn room_list(&self) -> Vec<RoomInfo> {
|
||||
|
||||
Reference in New Issue
Block a user