feat(lib): chat-room protocol (list, join, leave, say) with events

Implement the Soulseek chat-room server messages against soulfind's
wire format:
- MessageFactory builders: room-list request (64), join (14),
  leave (15), say (13).
- Handlers parsing the server side: RoomList (64) names+counts,
  JoinRoom (14) membership, SayChatroom (13), UserJoinedRoom (16),
  UserLeftRoom (17), LeaveRoom (15).
- Surface everything to the client as a drained RoomEvent stream plus
  a room-list snapshot; new Client API: request_room_list, join_room,
  leave_room, say_in_room, room_list, take_room_events.

Unit tests cover each builder and handler; server-gated e2e tests
exercise message delivery and room listing against soulfind.
This commit is contained in:
michel
2026-07-19 19:47:58 +02:00
parent ae28f4236a
commit 7bc0ee2c3d
12 changed files with 718 additions and 4 deletions
+70
View File
@@ -5,6 +5,8 @@ use crate::message::server::ConnectToPeerHandler;
use crate::message::server::ExcludedSearchPhrasesHandler;
use crate::message::server::FileSearchHandler;
use crate::message::server::GetPeerAddressHandler;
use crate::message::server::JoinRoomHandler;
use crate::message::server::LeaveRoomHandler;
use crate::message::server::LoginHandler;
use crate::message::server::MessageFactory;
use crate::message::server::MessageUser;
@@ -12,11 +14,15 @@ use crate::message::server::ParentMinSpeedHandler;
use crate::message::server::ParentSpeedRatioHandler;
use crate::message::server::PrivilegedUsersHandler;
use crate::message::server::RoomListHandler;
use crate::message::server::SayChatroomHandler;
use crate::message::server::UserJoinedRoomHandler;
use crate::message::server::UserLeftRoomHandler;
use crate::message::server::WishListIntervalHandler;
use crate::message::{Handlers, MessageType};
use crate::message::{Message, MessageReader};
use crate::peer::ConnectionType;
use crate::peer::Peer;
use crate::types::{RoomEvent, RoomInfo};
use crate::utils::lock::RwLockExt;
use std::io::{self, Error, Write};
@@ -168,6 +174,27 @@ pub enum ServerMessage {
obfuscated_port: u16,
},
PrivateMessageReceived(UserMessage),
RoomListReceived(Vec<RoomInfo>),
RoomJoined {
room: String,
users: Vec<String>,
},
RoomLeft {
room: String,
},
RoomMessageReceived {
room: String,
username: String,
message: String,
},
RoomUserJoined {
room: String,
username: String,
},
RoomUserLeft {
room: String,
username: String,
},
}
pub struct ServerActor {
@@ -327,6 +354,11 @@ impl ServerActor {
handlers.register_handler(LoginHandler);
handlers.register_handler(RoomListHandler);
handlers.register_handler(JoinRoomHandler);
handlers.register_handler(LeaveRoomHandler);
handlers.register_handler(SayChatroomHandler);
handlers.register_handler(UserJoinedRoomHandler);
handlers.register_handler(UserLeftRoomHandler);
handlers.register_handler(ExcludedSearchPhrasesHandler);
handlers.register_handler(PrivilegedUsersHandler);
handlers.register_handler(MessageUser);
@@ -468,6 +500,35 @@ impl ServerActor {
);
}
}
ServerMessage::RoomListReceived(rooms) => {
self.forward_room_event(RoomEvent::List(rooms));
}
ServerMessage::RoomJoined { room, users } => {
self.forward_room_event(RoomEvent::Joined { room, users });
}
ServerMessage::RoomLeft { room } => {
self.forward_room_event(RoomEvent::Left { room });
}
ServerMessage::RoomMessageReceived {
room,
username,
message,
} => {
self.forward_room_event(RoomEvent::Message {
room,
username,
message,
});
}
ServerMessage::RoomUserJoined { room, username } => {
self.forward_room_event(RoomEvent::UserJoined {
room,
username,
});
}
ServerMessage::RoomUserLeft { room, username } => {
self.forward_room_event(RoomEvent::UserLeft { room, username });
}
ServerMessage::ProcessRead => {
self.process_read();
}
@@ -602,6 +663,15 @@ impl ServerActor {
self.process_dispatcher_messages();
}
/// Forward a chat-room event to the client operations loop.
fn forward_room_event(&self, event: RoomEvent) {
if let Err(e) =
self.client_channel.send(ClientOperation::RoomEvent(event))
{
error!("[server] Error forwarding room event to client: {}", e);
}
}
fn queue_message(&mut self, message: Message) {
if let Some(sender) = &self.dispatcher_sender {
match sender.send(ServerMessage::SendMessage(message)) {