12 Commits

10 changed files with 456 additions and 306 deletions
Split View
  1. +8
    -11
      client/src/client.rs
  2. +16
    -14
      client/src/control/response.rs
  3. +15
    -26
      client/src/handlers/room_join_request_handler.rs
  4. +11
    -14
      client/src/handlers/room_join_response_handler.rs
  5. +10
    -4
      client/src/handlers/room_list_request_handler.rs
  6. +10
    -4
      client/src/handlers/room_list_response_handler.rs
  7. +13
    -13
      client/src/handlers/room_message_response_handler.rs
  8. +225
    -196
      client/src/room/map.rs
  9. +141
    -23
      client/src/room/state.rs
  10. +7
    -1
      proto/src/core/user.rs

+ 8
- 11
client/src/client.rs View File

@ -32,7 +32,9 @@ enum PeerState {
#[derive(Debug)]
struct Peer {
user_name: String,
#[allow(dead_code)]
ip: net::Ipv4Addr,
#[allow(dead_code)]
port: u16,
connection_type: peer::PeerConnectionType,
token: u32,
@ -547,14 +549,7 @@ impl Client {
mut response: server::RoomJoinResponse,
) {
// Join the room and store the received information.
let result = self.rooms.join(
&response.room_name,
response.owner,
response.operators,
&response.users,
);
let room = match result {
let room = match self.rooms.get_mut_strict(&response.room_name) {
Ok(room) => room,
Err(err) => {
error!("RoomJoinResponse: {}", err);
@ -562,6 +557,8 @@ impl Client {
}
};
room.join(response.owner, response.operators, &response.users);
// Then update the user structs based on the info we just got.
for user in response.users.drain(..) {
self.users.insert(user);
@ -569,7 +566,7 @@ impl Client {
let control_response = control::RoomJoinResponse {
room_name: response.room_name,
room: room.clone(),
room: room.clone_state(),
};
self.send_to_controller(control::Response::RoomJoinResponse(
control_response,
@ -613,13 +610,13 @@ impl Client {
}
};
let message = room::Message {
let message = room::RoomMessage {
received_at: std::time::SystemTime::now(),
user_name: response.user_name.clone(),
message: response.message.clone(),
};
room.messages.insert(message.clone());
room.insert_message(message.clone());
self.send_to_controller(control::Response::RoomMessageResponse(
control::RoomMessageResponse {


+ 16
- 14
client/src/control/response.rs View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use solstice_proto::User;
use crate::room::{Message, Room};
use crate::room::{RoomMessage, RoomState};
/// This enumeration is the list of possible control responses from the client
/// to the controller.
@ -21,7 +21,7 @@ pub enum Response {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoomJoinResponse {
pub room_name: String,
pub room: Room,
pub room: RoomState,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@ -62,7 +62,7 @@ pub enum LoginStatusResponse {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoomListResponse {
/// The list of (room name, room data) pairs.
pub rooms: Vec<(String, Room)>,
pub rooms: Vec<(String, RoomState)>,
}
/// This structure contains a message said in a chat room the user is a member
@ -72,7 +72,7 @@ pub struct RoomMessageResponse {
/// The name of the room in which the message was said.
pub room_name: String,
/// The message itself.
pub message: Message,
pub message: RoomMessage,
}
/// This struct describes the fact that the given user joined the given room.
@ -109,7 +109,9 @@ mod tests {
use solstice_proto::{User, UserStatus};
use crate::room::{Membership, Message, MessageHistory, Room, Visibility};
use crate::room::{
RoomMembership, RoomMessage, RoomMessageHistory, RoomState, RoomVisibility,
};
use super::{
LoginStatusResponse, RoomJoinResponse, RoomLeaveResponse, RoomListResponse,
@ -192,15 +194,15 @@ mod tests {
.unwrap(),
RoomJoinResponse {
room_name: "bleep".to_string(),
room: Room {
membership: Membership::Joining,
visibility: Visibility::PrivateOwned,
room: RoomState {
membership: RoomMembership::Joining,
visibility: RoomVisibility::PrivateOwned,
operated: false,
user_count: 3,
owner: None,
operators: HashSet::new(),
members: HashSet::new(),
messages: MessageHistory::default(),
messages: RoomMessageHistory::default(),
tickers: vec![],
}
}
@ -234,7 +236,7 @@ mod tests {
.unwrap(),
RoomMessageResponse {
room_name: "bleep".to_string(),
message: Message {
message: RoomMessage {
user_name: "karandeep".to_string(),
message: "namaste".to_string(),
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(42),
@ -267,15 +269,15 @@ mod tests {
RoomListResponse {
rooms: vec![(
"bleep".to_string(),
Room {
membership: Membership::Joining,
visibility: Visibility::PrivateOwned,
RoomState {
membership: RoomMembership::Joining,
visibility: RoomVisibility::PrivateOwned,
operated: false,
user_count: 3,
owner: None,
operators: HashSet::new(),
members: HashSet::new(),
messages: MessageHistory::default(),
messages: RoomMessageHistory::default(),
tickers: vec![],
}
)],


+ 15
- 26
client/src/handlers/room_join_request_handler.rs View File

@ -6,30 +6,18 @@ use solstice_proto::ServerRequest;
use crate::context::Context;
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::RoomError;
#[derive(Debug, Default)]
pub struct RoomJoinRequestHandler;
fn start_joining(
context: &mut Context,
room_name: &str,
) -> Result<(), RoomError> {
let result = context.state.rooms.start_joining(room_name);
if let Err(RoomError::MembershipChangeInvalid(_, _)) = result {
// This `expect()` should never fail since the error was not
// `RoomNotFound` but `MembershipChangeInvalid`.
let room = context
.state
.rooms
.get_strict(room_name)
.expect("querying room state");
fn start_joining(context: &mut Context, room_name: &str) -> anyhow::Result<()> {
let room = context.state.rooms.get_mut_strict(room_name)?;
room.start_joining().map_err(|err| {
let response =
control::Response::RoomJoinResponse(control::RoomJoinResponse {
room_name: room_name.to_string(),
room: room.clone(),
room: room.clone_state(),
});
if let Err(err) = context.control_response_tx.blocking_send(response) {
@ -38,9 +26,9 @@ fn start_joining(
room_name, err
);
}
}
result
err.into()
})
}
impl MessageHandler for RoomJoinRequestHandler {
@ -75,7 +63,7 @@ mod tests {
use crate::context::{ContextBundle, ContextOptions};
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::{Membership, Room, Visibility};
use crate::room::{RoomMembership, RoomState, RoomVisibility};
use super::RoomJoinRequestHandler;
@ -99,8 +87,8 @@ mod tests {
#[test]
fn run_already_joined_responds_immediately() -> anyhow::Result<()> {
let mut room = Room::new(Visibility::Public, 3);
room.membership = Membership::Member;
let mut room = RoomState::new(RoomVisibility::Public, 3);
room.membership = RoomMembership::Member;
let mut options = ContextOptions::default();
options
@ -135,10 +123,10 @@ mod tests {
#[test]
fn run_success() -> anyhow::Result<()> {
let mut options = ContextOptions::default();
options
.initial_state
.rooms
.insert("bleep".to_string(), Room::new(Visibility::Public, 3));
options.initial_state.rooms.insert(
"bleep".to_string(),
RoomState::new(RoomVisibility::Public, 3),
);
let mut bundle = ContextBundle::new(options);
RoomJoinRequestHandler::default()
@ -155,8 +143,9 @@ mod tests {
.rooms
.get_strict("bleep")
.context("getting room")?
.clone_state()
.membership,
Membership::Joining
RoomMembership::Joining
);
// The request is forwarded onwards.


+ 11
- 14
client/src/handlers/room_join_response_handler.rs View File

@ -17,22 +17,19 @@ impl MessageHandler for RoomJoinResponseHandler {
response: &RoomJoinResponse,
) -> anyhow::Result<()> {
{
let room = context
.state
.rooms
.join(
&response.room_name,
response.owner.clone(),
response.operators.clone(),
&response.users,
)
.context("joining room")?;
let room = context.state.rooms.get_mut_strict(&response.room_name)?;
room.join(
response.owner.clone(),
response.operators.clone(),
&response.users,
);
// Send under lock to avoid out-of-order sends.
let control_response =
control::Response::RoomJoinResponse(control::RoomJoinResponse {
room_name: response.room_name.clone(),
room: room.clone(),
room: room.clone_state(),
});
context
@ -55,13 +52,13 @@ mod tests {
use crate::context::{ContextBundle, ContextOptions};
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::{Membership, Room, Visibility};
use crate::room::{RoomMembership, RoomState, RoomVisibility};
use super::RoomJoinResponseHandler;
#[test]
fn run_updates_room_state_and_forwards_response() {
let mut room = Room::new(Visibility::Public, 42);
let mut room = RoomState::new(RoomVisibility::Public, 42);
let mut options = ContextOptions::default();
options
@ -81,7 +78,7 @@ mod tests {
.run(&mut bundle.context, &response)
.unwrap();
room.membership = Membership::Member;
room.membership = RoomMembership::Member;
room.user_count = 0;
room.operators = ["shruti"].iter().map(|s| s.to_string()).collect();
room.owner = Some("kim".to_string());


+ 10
- 4
client/src/handlers/room_list_request_handler.rs View File

@ -45,12 +45,12 @@ mod tests {
use crate::context::{ContextBundle, ContextOptions};
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::{Room, Visibility};
use crate::room::{RoomState, RoomVisibility};
use super::RoomListRequestHandler;
// Cannot get the compiler to be satisfied when borrowing the name...
fn room_name(pair: &(String, Room)) -> String {
fn room_name(pair: &(String, RoomState)) -> String {
pair.0.clone()
}
@ -97,8 +97,14 @@ mod tests {
assert_eq!(
rooms,
vec![
("apple".to_string(), Room::new(Visibility::Public, 42)),
("potato".to_string(), Room::new(Visibility::Public, 123)),
(
"apple".to_string(),
RoomState::new(RoomVisibility::Public, 42)
),
(
"potato".to_string(),
RoomState::new(RoomVisibility::Public, 123)
),
]
);
}


+ 10
- 4
client/src/handlers/room_list_response_handler.rs View File

@ -41,12 +41,12 @@ mod tests {
use crate::context::ContextBundle;
use crate::message_handler::MessageHandler;
use crate::room::{Room, Visibility};
use crate::room::{RoomState, RoomVisibility};
use super::RoomListResponseHandler;
// Cannot get the compiler to be satisfied when borrowing the name...
fn room_name(pair: &(String, Room)) -> String {
fn room_name(pair: &(String, RoomState)) -> String {
pair.0.clone()
}
@ -71,8 +71,14 @@ mod tests {
assert_eq!(
rooms,
vec![
("apple".to_string(), Room::new(Visibility::Public, 42)),
("potato".to_string(), Room::new(Visibility::Public, 123)),
(
"apple".to_string(),
RoomState::new(RoomVisibility::Public, 42)
),
(
"potato".to_string(),
RoomState::new(RoomVisibility::Public, 123)
),
]
);
}


+ 13
- 13
client/src/handlers/room_message_response_handler.rs View File

@ -4,7 +4,7 @@ use solstice_proto::server;
use crate::context::Context;
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::Message as RoomMessage;
use crate::room::RoomMessage;
#[derive(Debug, Default)]
pub struct RoomMessageResponseHandler;
@ -29,7 +29,7 @@ impl MessageHandler for RoomMessageResponseHandler {
message: response.message.clone(),
};
room.messages.insert(message.clone());
room.insert_message(message.clone());
context
.control_response_tx
@ -58,7 +58,7 @@ mod tests {
use crate::context::{ContextBundle, ContextOptions};
use crate::control;
use crate::message_handler::MessageHandler;
use crate::room::{Message, Room, Visibility};
use crate::room::{RoomMessage, RoomState, RoomVisibility};
use super::RoomMessageResponseHandler;
@ -69,10 +69,10 @@ mod tests {
#[test]
fn run_forwards_response() {
let mut options = ContextOptions::default();
options
.initial_state
.rooms
.insert("apple".to_string(), Room::new(Visibility::Public, 1));
options.initial_state.rooms.insert(
"apple".to_string(),
RoomState::new(RoomVisibility::Public, 1),
);
options.simulated_clock =
Some(SimulatedSystemClock::new(system_time_from_secs(42)));
@ -98,7 +98,7 @@ mod tests {
response,
control::Response::RoomMessageResponse(control::RoomMessageResponse {
room_name: "apple".to_string(),
message: Message {
message: RoomMessage {
user_name: "shruti".to_string(),
message: "yo!".to_string(),
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(42),
@ -109,8 +109,8 @@ mod tests {
#[test]
fn run_stores_message() {
let mut room = Room::new(Visibility::Public, 42);
room.messages.insert(Message {
let mut room = RoomState::new(RoomVisibility::Public, 42);
room.messages.insert(RoomMessage {
received_at: system_time_from_secs(42),
user_name: "karandeep".to_string(),
message: "namaste!".to_string(),
@ -145,14 +145,14 @@ mod tests {
.expect("looking up room");
assert_eq!(
room.messages.to_vec(),
room.clone_state().messages.to_vec(),
vec![
Message {
RoomMessage {
received_at: system_time_from_secs(42),
user_name: "karandeep".to_string(),
message: "namaste!".to_string(),
},
Message {
RoomMessage {
received_at: system_time_from_secs(43),
user_name: "shruti".to_string(),
message: "yo!".to_string(),


+ 225
- 196
client/src/room/map.rs View File

@ -5,16 +5,113 @@ use log::{error, info, warn};
use solstice_proto::{server, User};
use thiserror::Error;
use crate::room::{Membership, Room, Visibility};
use crate::room::{RoomMembership, RoomMessage, RoomState, RoomVisibility};
/// The error returned by RoomMap functions.
#[derive(Debug, Error)]
pub enum RoomError {
#[error("room {0} not found")]
RoomNotFound(String),
#[error(transparent)]
RoomNotFound(#[from] RoomNotFoundError),
#[error("cannot change membership from {0:?} to {1:?}")]
MembershipChangeInvalid(Membership, Membership),
#[error(transparent)]
MembershipChangeInvalid(#[from] RoomMembershipChangeError),
}
#[derive(Debug, Error)]
#[error("cannot change membership from {0:?} to {1:?}")]
pub struct RoomMembershipChangeError(RoomMembership, RoomMembership);
#[derive(Debug, Error)]
#[error("room {0} not found")]
pub struct RoomNotFoundError(String);
/// An entry in the chat room map.
#[derive(Debug)]
pub struct RoomEntry {
name: String,
state: RoomState,
}
impl RoomEntry {
/// Creates a new entry with the given state.
pub fn new(name: String, state: RoomState) -> Self {
Self { name, state }
}
/// Returns a copy of the room state contained in this entry.
pub fn clone_state(&self) -> RoomState {
self.state.clone()
}
/// Returns the state contained in this entry.
#[cfg(test)]
pub fn into_state(self) -> RoomState {
self.state
}
/// Inserts the given message in this chat room's history.
pub fn insert_message(&mut self, message: RoomMessage) {
self.state.messages.insert(message)
}
/// Records that we are now trying to join this room.
/// Returns an error if its membership is not `NonMember`,
pub fn start_joining(&mut self) -> Result<(), RoomMembershipChangeError> {
match self.state.membership {
RoomMembership::NonMember => {
self.state.membership = RoomMembership::Joining;
Ok(())
}
membership => Err(RoomMembershipChangeError(
membership,
RoomMembership::Joining,
)),
}
}
/// Records that we are now a member of this room and updates its state.
pub fn join(
&mut self,
owner: Option<String>,
mut operators: Vec<String>,
members: &[User],
) {
// Log what's happening.
if let RoomMembership::Joining = self.state.membership {
info!("Joined room {:?}", self.name);
} else {
warn!(
"Joined room {:?} but membership was already {:?}",
self.name, self.state.membership
);
}
// Update the room state.
self.state.membership = RoomMembership::Member;
self.state.user_count = members.len();
self.state.owner = owner;
self.state.operators = operators.drain(..).collect();
self.state.members = members.iter().map(|user| user.name.clone()).collect();
}
/// Records that we are now trying to leave the given room.
/// If the room is not found, or if its membership status is not `Member`,
/// returns an error.
#[cfg(test)]
pub fn start_leaving(&mut self) -> Result<(), RoomMembershipChangeError> {
match self.state.membership {
RoomMembership::Member => {
self.state.membership = RoomMembership::Leaving;
Ok(())
}
membership => Err(RoomMembershipChangeError(
membership,
RoomMembership::Leaving,
)),
}
}
}
/// Contains the mapping from room names to room data and provides a clean
@ -22,7 +119,7 @@ pub enum RoomError {
#[derive(Debug, Default)]
pub struct RoomMap {
/// The actual map from room names to room data.
map: HashMap<String, Room>,
map: HashMap<String, RoomEntry>,
}
impl RoomMap {
@ -33,16 +130,23 @@ impl RoomMap {
/// Inserts the given room in the map under the given name.
/// Same semantics as `std::collections::HashMap::insert()`.
pub fn insert(&mut self, name: String, room: Room) -> Option<Room> {
self.map.insert(name, room)
pub fn insert(&mut self, name: String, room: RoomState) -> Option<RoomState> {
self
.map
.insert(name.clone(), RoomEntry::new(name, room))
.map(|entry| entry.state)
}
/// Looks up the given room name in the map, returning an immutable
/// reference to the associated data if found, or an error if not found.
pub fn get_strict(&self, room_name: &str) -> Result<&Room, RoomError> {
#[cfg(test)]
pub fn get_strict(
&self,
room_name: &str,
) -> Result<&RoomEntry, RoomNotFoundError> {
match self.map.get(room_name) {
Some(room) => Ok(room),
None => Err(RoomError::RoomNotFound(room_name.to_string())),
None => Err(RoomNotFoundError(room_name.to_string())),
}
}
@ -51,10 +155,10 @@ impl RoomMap {
pub fn get_mut_strict(
&mut self,
room_name: &str,
) -> Result<&mut Room, RoomError> {
) -> Result<&mut RoomEntry, RoomNotFoundError> {
match self.map.get_mut(room_name) {
Some(room) => Ok(room),
None => Err(RoomError::RoomNotFound(room_name.to_string())),
None => Err(RoomNotFoundError(room_name.to_string())),
}
}
@ -63,16 +167,16 @@ impl RoomMap {
fn update_one(
&mut self,
name: String,
visibility: Visibility,
visibility: RoomVisibility,
user_count: u32,
old_map: &mut HashMap<String, Room>,
old_map: &mut HashMap<String, RoomEntry>,
) {
let room = match old_map.remove(&name) {
None => Room::new(Visibility::Public, user_count as usize),
Some(mut room) => {
room.visibility = visibility;
room.user_count = user_count as usize;
room
None => RoomState::new(RoomVisibility::Public, user_count as usize),
Some(mut entry) => {
entry.state.visibility = visibility;
entry.state.user_count = user_count as usize;
entry.state
}
};
if let Some(_) = self.insert(name, room) {
@ -88,122 +192,53 @@ impl RoomMap {
// Add all public rooms.
for (name, user_count) in response.rooms.drain(..) {
self.update_one(name, Visibility::Public, user_count, &mut old_map);
self.update_one(name, RoomVisibility::Public, user_count, &mut old_map);
}
// Add all private, owned, rooms.
for (name, user_count) in response.owned_private_rooms.drain(..) {
self.update_one(name, Visibility::PrivateOwned, user_count, &mut old_map);
self.update_one(
name,
RoomVisibility::PrivateOwned,
user_count,
&mut old_map,
);
}
// Add all private, unowned, rooms.
for (name, user_count) in response.other_private_rooms.drain(..) {
self.update_one(name, Visibility::PrivateOther, user_count, &mut old_map);
self.update_one(
name,
RoomVisibility::PrivateOther,
user_count,
&mut old_map,
);
}
// Mark all operated rooms as necessary.
for name in response.operated_private_room_names.iter() {
match self.map.get_mut(name) {
Some(room) => room.operated = true,
Some(room) => room.state.operated = true,
None => error!("Room {} is operated but does not exist", name),
}
}
}
/// Returns the list of (room name, room data) representing all known rooms.
pub fn get_room_list(&self) -> Vec<(String, Room)> {
pub fn get_room_list(&self) -> Vec<(String, RoomState)> {
let mut rooms = Vec::new();
for (room_name, room) in self.map.iter() {
rooms.push((room_name.clone(), room.clone()));
rooms.push((room_name.clone(), room.clone_state()));
}
rooms
}
/// Records that we are now trying to join the given room.
/// If the room is not found, or if its membership is not `NonMember`,
/// returns an error.
pub fn start_joining(&mut self, room_name: &str) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
match room.membership {
Membership::NonMember => {
room.membership = Membership::Joining;
Ok(())
}
membership => Err(RoomError::MembershipChangeInvalid(
membership,
Membership::Joining,
)),
}
}
/// Records that we are now a member of the given room and updates the room
/// information.
pub fn join(
&mut self,
room_name: &str,
owner: Option<String>,
mut operators: Vec<String>,
members: &[User],
) -> Result<&Room, RoomError> {
// First look up the room struct.
let room = self.get_mut_strict(room_name)?;
// Log what's happening.
if let Membership::Joining = room.membership {
info!("Joined room {:?}", room_name);
} else {
warn!(
"Joined room {:?} but membership was already {:?}",
room_name, room.membership
);
}
// Update the room struct.
room.membership = Membership::Member;
room.user_count = members.len();
room.owner = owner;
room.operators.clear();
for user_name in operators.drain(..) {
room.operators.insert(user_name);
}
room.members.clear();
for user in members {
room.members.insert(user.name.clone());
}
Ok(room)
}
#[allow(dead_code)]
/// Records that we are now trying to leave the given room.
/// If the room is not found, or if its membership status is not `Member`,
/// returns an error.
pub fn start_leaving(&mut self, room_name: &str) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
match room.membership {
Membership::Member => {
room.membership = Membership::Leaving;
Ok(())
}
membership => Err(RoomError::MembershipChangeInvalid(
membership,
Membership::Leaving,
)),
}
}
/// Records that we have now left the given room.
pub fn leave(&mut self, room_name: &str) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
match room.membership {
Membership::Leaving => info!("Left room {:?}", room_name),
match room.state.membership {
RoomMembership::Leaving => info!("Left room {:?}", room_name),
membership => warn!(
"Left room {:?} with wrong membership: {:?}",
@ -211,7 +246,7 @@ impl RoomMap {
),
}
room.membership = Membership::NonMember;
room.state.membership = RoomMembership::NonMember;
Ok(())
}
@ -223,7 +258,7 @@ impl RoomMap {
user_name: String,
) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
room.members.insert(user_name);
room.state.members.insert(user_name);
Ok(())
}
@ -235,7 +270,7 @@ impl RoomMap {
user_name: &str,
) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
room.members.remove(user_name);
room.state.members.remove(user_name);
Ok(())
}
@ -249,127 +284,121 @@ impl RoomMap {
tickers: Vec<(String, String)>,
) -> Result<(), RoomError> {
let room = self.get_mut_strict(room_name)?;
room.tickers = tickers;
room.state.tickers = tickers;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, SystemTime};
use std::collections::HashSet;
use solstice_proto::server::RoomListResponse;
use crate::room::{Membership, Message, MessageHistory, Room, Visibility};
use crate::room::{RoomMembership, RoomState, RoomVisibility};
use super::RoomMap;
use super::*;
#[test]
fn deserialize_membership() {
assert_eq!(
serde_json::from_str::<Membership>(r#""Member""#).unwrap(),
Membership::Member
);
fn entry_start_joining_error() {
let initial_state = RoomState {
membership: RoomMembership::Member,
..RoomState::default()
};
let mut room = RoomEntry::new("bleep".to_string(), initial_state.clone());
room.start_joining().unwrap_err();
assert_eq!(room.into_state(), initial_state);
}
#[test]
fn deserialize_visibility() {
fn entry_start_joining_success() {
let mut room = RoomEntry::new("bleep".to_string(), RoomState::default());
room.start_joining().unwrap();
assert_eq!(
serde_json::from_str::<Visibility>(r#""Public""#).unwrap(),
Visibility::Public
room.into_state(),
RoomState {
membership: RoomMembership::Joining,
..RoomState::default()
}
);
}
#[test]
fn deserialize_message() {
fn entry_join() {
let mut room = RoomEntry::new("bleep".to_string(), RoomState::default());
let owner = Some("owner".to_string());
let mut operators = vec!["operator1".to_string(), "operator2".to_string()];
let users = [
User {
name: "shruti".to_string(),
..User::default()
},
User {
name: "kim".to_string(),
..User::default()
},
];
room.join(owner.clone(), operators.clone(), &users);
assert_eq!(
serde_json::from_str::<Message>(
r#"{
"received_at": { "secs_since_epoch": 42, "nanos_since_epoch": 1337 },
"user_name":"karandeep",
"message":"namaste"
}"#
)
.unwrap(),
Message {
received_at: SystemTime::UNIX_EPOCH
+ Duration::from_secs(42)
+ Duration::from_nanos(1337),
user_name: "karandeep".to_string(),
message: "namaste".to_string()
room.into_state(),
RoomState {
membership: RoomMembership::Member,
owner: owner,
operators: operators.drain(..).collect::<HashSet<String>>(),
user_count: 2,
members: users
.iter()
.map(|user| user.name.clone())
.collect::<HashSet<String>>(),
..RoomState::default()
}
);
}
#[test]
fn deserialize_room() {
fn entry_start_leaving_error() {
let mut room = RoomEntry::new("bleep".to_string(), RoomState::default());
room.start_leaving().unwrap_err();
assert_eq!(room.into_state(), RoomState::default());
}
#[test]
fn entry_start_leaving_success() {
let initial_state = RoomState {
membership: RoomMembership::Member,
..RoomState::default()
};
let mut room = RoomEntry::new("bleep".to_string(), initial_state.clone());
room.start_leaving().unwrap();
assert_eq!(
serde_json::from_str::<Room>(
r#"{
"membership": "Joining",
"visibility": "PrivateOwned",
"operated": false,
"user_count": 3,
"owner": null,
"operators": ["op1", "op2"],
"members": ["m1", "m2"],
"messages": [
{
"received_at": { "secs_since_epoch": 43, "nanos_since_epoch": 0 },
"user_name": "u2",
"message": "msg2"
},
{
"received_at": { "secs_since_epoch": 42, "nanos_since_epoch": 0 },
"user_name": "u1",
"message": "msg1"
}
],
"tickers": [["t11", "t12"], ["t21", "t22"]]
}"#
)
.unwrap(),
Room {
membership: Membership::Joining,
visibility: Visibility::PrivateOwned,
operated: false,
user_count: 3,
owner: None,
operators: ["op1".to_string(), "op2".to_string()]
.iter()
.cloned()
.collect(),
members: ["m1".to_string(), "m2".to_string()]
.iter()
.cloned()
.collect(),
messages: MessageHistory::new(vec![
Message {
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(42),
user_name: "u1".to_string(),
message: "msg1".to_string(),
},
Message {
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(43),
user_name: "u2".to_string(),
message: "msg2".to_string(),
}
]),
tickers: vec![
("t11".to_string(), "t12".to_string()),
("t21".to_string(), "t22".to_string()),
],
room.into_state(),
RoomState {
membership: RoomMembership::Leaving,
..initial_state
}
);
}
#[test]
fn room_map_new_is_empty() {
fn map_new_is_empty() {
assert_eq!(RoomMap::new().get_room_list(), vec![]);
}
#[test]
fn room_map_get_strict() {
fn map_get_strict() {
let mut rooms = RoomMap::new();
rooms.set_room_list(RoomListResponse {
rooms: vec![("room a".to_string(), 42), ("room b".to_string(), 1337)],
@ -379,8 +408,8 @@ mod tests {
});
assert_eq!(
rooms.get_strict("room a").unwrap(),
&Room::new(Visibility::Public, 42)
rooms.get_strict("room a").unwrap().clone_state(),
RoomState::new(RoomVisibility::Public, 42)
);
}
}

+ 141
- 23
client/src/room/state.rs View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// This enumeration is the list of possible membership states for a chat room.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Membership {
pub enum RoomMembership {
/// The user is not a member of this room.
NonMember,
/// The user has requested to join the room, but hasn't heard back from the
@ -18,10 +18,16 @@ pub enum Membership {
Leaving,
}
impl Default for RoomMembership {
fn default() -> Self {
Self::NonMember
}
}
/// This enumeration is the list of visibility types for rooms that the user is
/// a member of.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Visibility {
pub enum RoomVisibility {
/// This room is visible to any user.
Public,
/// This room is visible only to members, and the user owns it.
@ -30,11 +36,17 @@ pub enum Visibility {
PrivateOther,
}
impl Default for RoomVisibility {
fn default() -> Self {
Self::Public
}
}
/// A message sent to a chat room.
#[derive(
Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize,
)]
pub struct Message {
pub struct RoomMessage {
/// Time at which the message was received by this client.
///
/// We use `SystemTime` instead of `Instant` because this is serialized and
@ -53,24 +65,24 @@ pub struct Message {
/// The history of messages sent for a single chat room.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MessageHistory {
pub struct RoomMessageHistory {
/// Messages, sorted in increasing order.
messages: Vec<Message>,
messages: Vec<RoomMessage>,
}
// MessageHistory should be transparent for serialization purposes.
impl<'de> Deserialize<'de> for MessageHistory {
impl<'de> Deserialize<'de> for RoomMessageHistory {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let messages = Vec::<Message>::deserialize(deserializer)?;
let messages = Vec::<RoomMessage>::deserialize(deserializer)?;
Ok(Self::new(messages))
}
}
// MessageHistory should be transparent for serialization purposes.
impl Serialize for MessageHistory {
// RoomMessageHistory should be transparent for serialization purposes.
impl Serialize for RoomMessageHistory {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
@ -79,14 +91,14 @@ impl Serialize for MessageHistory {
}
}
impl MessageHistory {
pub fn new(mut messages: Vec<Message>) -> Self {
impl RoomMessageHistory {
pub fn new(mut messages: Vec<RoomMessage>) -> Self {
messages.sort();
Self { messages }
}
/// Inserts a `message` into this history.
pub fn insert(&mut self, message: Message) {
pub fn insert(&mut self, message: RoomMessage) {
self.messages.push(message);
// This could be terrible for performance in the general case, but we know
@ -97,7 +109,7 @@ impl MessageHistory {
#[cfg(test)]
/// Returns the list of messages sorted in increasing chronological order.
pub fn to_vec(&self) -> Vec<Message> {
pub fn to_vec(&self) -> Vec<RoomMessage> {
return self.messages.clone();
}
}
@ -105,12 +117,12 @@ impl MessageHistory {
/// This structure contains the last known information about a chat room.
/// It does not store the name, as that is stored implicitly as the key in the
/// room hash table.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Room {
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct RoomState {
/// The membership state of the user for the room.
pub membership: Membership,
pub membership: RoomMembership,
/// The visibility of the room.
pub visibility: Visibility,
pub visibility: RoomVisibility,
/// True if the user is one of the room's operators, False if the user is a
/// regular member.
pub operated: bool,
@ -123,24 +135,130 @@ pub struct Room {
/// The names of the room's members.
pub members: HashSet<String>,
/// The messages sent to this chat room.
pub messages: MessageHistory,
pub messages: RoomMessageHistory,
/// The tickers displayed in this room.
pub tickers: Vec<(String, String)>,
}
impl Room {
impl RoomState {
/// Creates a new room with the given visibility and user count.
pub fn new(visibility: Visibility, user_count: usize) -> Self {
Room {
membership: Membership::NonMember,
pub fn new(visibility: RoomVisibility, user_count: usize) -> Self {
Self {
membership: RoomMembership::NonMember,
visibility: visibility,
operated: false,
user_count: user_count,
owner: None,
operators: HashSet::new(),
members: HashSet::new(),
messages: MessageHistory::default(),
messages: RoomMessageHistory::default(),
tickers: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, SystemTime};
use super::*;
#[test]
fn deserialize_membership() {
assert_eq!(
serde_json::from_str::<RoomMembership>(r#""Member""#).unwrap(),
RoomMembership::Member
);
}
#[test]
fn deserialize_visibility() {
assert_eq!(
serde_json::from_str::<RoomVisibility>(r#""Public""#).unwrap(),
RoomVisibility::Public
);
}
#[test]
fn deserialize_message() {
assert_eq!(
serde_json::from_str::<RoomMessage>(
r#"{
"received_at": { "secs_since_epoch": 42, "nanos_since_epoch": 1337 },
"user_name":"karandeep",
"message":"namaste"
}"#
)
.unwrap(),
RoomMessage {
received_at: SystemTime::UNIX_EPOCH
+ Duration::from_secs(42)
+ Duration::from_nanos(1337),
user_name: "karandeep".to_string(),
message: "namaste".to_string()
}
);
}
#[test]
fn deserialize_room() {
assert_eq!(
serde_json::from_str::<RoomState>(
r#"{
"membership": "Joining",
"visibility": "PrivateOwned",
"operated": false,
"user_count": 3,
"owner": null,
"operators": ["op1", "op2"],
"members": ["m1", "m2"],
"messages": [
{
"received_at": { "secs_since_epoch": 43, "nanos_since_epoch": 0 },
"user_name": "u2",
"message": "msg2"
},
{
"received_at": { "secs_since_epoch": 42, "nanos_since_epoch": 0 },
"user_name": "u1",
"message": "msg1"
}
],
"tickers": [["t11", "t12"], ["t21", "t22"]]
}"#
)
.unwrap(),
RoomState {
membership: RoomMembership::Joining,
visibility: RoomVisibility::PrivateOwned,
operated: false,
user_count: 3,
owner: None,
operators: ["op1".to_string(), "op2".to_string()]
.iter()
.cloned()
.collect(),
members: ["m1".to_string(), "m2".to_string()]
.iter()
.cloned()
.collect(),
messages: RoomMessageHistory::new(vec![
RoomMessage {
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(42),
user_name: "u1".to_string(),
message: "msg1".to_string(),
},
RoomMessage {
received_at: SystemTime::UNIX_EPOCH + Duration::from_secs(43),
user_name: "u2".to_string(),
message: "msg2".to_string(),
}
]),
tickers: vec![
("t11".to_string(), "t12".to_string()),
("t21".to_string(), "t22".to_string()),
],
}
);
}
}

+ 7
- 1
proto/src/core/user.rs View File

@ -22,6 +22,12 @@ pub enum UserStatus {
Online,
}
impl Default for UserStatus {
fn default() -> Self {
Self::Offline
}
}
impl ValueEncode for UserStatus {
fn encode_to(
&self,
@ -55,7 +61,7 @@ impl ValueDecode for UserStatus {
/// This structure contains the last known information about a fellow user.
#[derive(
Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize,
Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize,
)]
pub struct User {
/// The name of the user.


Loading…
Cancel
Save