Browse Source

WIP: Introduce ServerContext.

main
Titouan Rigoudy 2 years ago
parent
commit
a5ff5beafe
8 changed files with 302 additions and 5 deletions
  1. +29
    -2
      client/src/context.rs
  2. +1
    -0
      client/src/event.rs
  3. +214
    -0
      client/src/server/context.rs
  4. +53
    -0
      client/src/server/login.rs
  5. +1
    -0
      client/src/server/mod.rs
  6. +1
    -0
      client/src/server/room/event.rs
  7. +2
    -2
      client/src/server/room/map.rs
  8. +1
    -1
      client/src/server/user/map.rs

+ 29
- 2
client/src/context.rs View File

@ -9,25 +9,40 @@ use crate::clock::SimulatedSystemClock;
use crate::clock::SystemClock; use crate::clock::SystemClock;
use crate::control::Response as ControlResponse; use crate::control::Response as ControlResponse;
use crate::peer::PeerMap; use crate::peer::PeerMap;
use crate::server::context::{ServerContext, ServerLoginInfo};
use crate::server::room::RoomMap; use crate::server::room::RoomMap;
use crate::server::user::UserMap; use crate::server::user::UserMap;
use crate::Config; use crate::Config;
/// Contains all the different bits of client state. /// Contains all the different bits of client state.
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct State { pub struct State {
pub rooms: RoomMap, pub rooms: RoomMap,
pub users: UserMap, pub users: UserMap,
pub peers: PeerMap, pub peers: PeerMap,
} }
impl Default for State {
fn default() -> Self {
Self {
rooms: RoomMap::default(),
users: UserMap::default(),
peers: PeerMap::default(),
}
}
}
/// Holds process-wide context for message handlers to execute against. /// Holds process-wide context for message handlers to execute against.
#[derive(Debug)] #[derive(Debug)]
pub struct Context { pub struct Context {
/// Mutable state. /// Mutable state.
pub state: State, pub state: State,
/// Server-related context bits.
pub server: ServerContext,
/// The user name with which we logged in to the server. /// The user name with which we logged in to the server.
// TODO: Use the value in `server` instead.
pub user_name: String, pub user_name: String,
/// Sender half of a channel used to send requests to the server. /// Sender half of a channel used to send requests to the server.
@ -101,7 +116,19 @@ impl ContextBundle {
Self { Self {
context: Context { context: Context {
state: options.initial_state,
state: State {
rooms: options.initial_state.rooms.clone(),
users: options.initial_state.users.clone(),
peers: options.initial_state.peers,
},
// TODO: Do not login immediately, wait for a login event.
server: ServerContext::new_logged_in(
options.user_name.clone(),
ServerLoginInfo {
motd: "unimplemented".to_string(),
request_tx: server_request_tx.clone(),
},
),
user_name: options.user_name, user_name: options.user_name,
server_request_tx, server_request_tx,
control_response_tx, control_response_tx,


+ 1
- 0
client/src/event.rs View File

@ -8,6 +8,7 @@ use crate::control;
/// The type of events affecting the client. /// The type of events affecting the client.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Event { pub enum Event {
// TODO: events for login stuff
ControlRequest(control::Request), ControlRequest(control::Request),
ServerResponse(ServerResponse), ServerResponse(ServerResponse),
} }


+ 214
- 0
client/src/server/context.rs View File

@ -0,0 +1,214 @@
use std::mem;
use anyhow::anyhow;
use solstice_proto::ServerRequest;
use thiserror::Error;
use tokio::sync::mpsc::Sender;
use crate::server::room::RoomMap;
use crate::server::user::UserMap;
/// Server-related state stored when we are logged in.
#[derive(Debug)]
pub struct ServerLoggedInContext {
/// Chat rooms hosted by the server.
pub rooms: RoomMap,
/// Users connected to the server.
pub users: UserMap,
/// Sends requests to the server.
pub request_tx: Sender<ServerRequest>,
// Immutable, see accessor methods.
user_name: String,
motd: String,
}
impl ServerLoggedInContext {
/// The user name with which we logged in.
pub fn user_name(&self) -> &str {
&self.user_name
}
/// The message of the day sent by the server when we logged in.
pub fn motd(&self) -> &str {
&self.motd
}
/// Records that we have logged out.
pub fn logout(self) -> ServerLoggedOutContext {
ServerLoggedOutContext {
user_name: self.user_name,
error: None,
}
}
}
/// Server-related state stored when we are logged out.
#[derive(Debug)]
pub struct ServerLoggedOutContext {
// Immutable, see accessor method.
user_name: String,
// Once set to `Some(...)`, never set to `None` again.
error: Option<String>,
}
/// Information provided upon a successful login.
pub struct ServerLoginInfo {
/// The message of the day sent by the server when we logged in.
pub motd: String,
/// Sends requests to the server.
pub request_tx: Sender<ServerRequest>,
}
impl ServerLoggedOutContext {
/// The user name with which we will attempt or have attempted to log in.
pub fn user_name(&self) -> &str {
&self.user_name
}
/// The error received during the last login attempt, if any.
/// `None` if we have never attempted to log in.
pub fn error(&self) -> Option<&str> {
self.error.as_deref()
}
/// Records that we failed to log in, and the server sent us the given error.
pub fn set_error(&mut self, error: String) {
self.error = Some(error)
}
/// Records that we logged in successfully.
pub fn login(self, info: ServerLoginInfo) -> ServerLoggedInContext {
ServerLoggedInContext {
user_name: self.user_name,
motd: info.motd,
request_tx: info.request_tx,
rooms: RoomMap::default(),
users: UserMap::default(),
}
}
}
/// Server-related client state.
#[derive(Debug)]
pub enum ServerContext {
/// We are logged out of the server.
LoggedOut(ServerLoggedOutContext),
/// We are logged in to the server.
LoggedIn(ServerLoggedInContext),
}
fn server_logged_out_error(error: &Option<String>) -> String {
match error {
Some(error) => format!("login error: {}", error),
None => "not logged in".to_string(),
}
}
#[derive(Debug, Error)]
#[error("{}", server_logged_out_error(.error))]
pub struct ServerLoggedOutError {
error: Option<String>,
}
#[derive(Debug, Error)]
#[error("logged in as {user_name}")]
pub struct ServerLoggedInError {
user_name: String,
}
impl ServerContext {
/// Constructs a new logged out context with the given user name.
pub fn new(user_name: String) -> Self {
Self::LoggedOut(ServerLoggedOutContext {
user_name,
error: None,
})
}
/// Constructs a new logged in context with the given information.
pub fn new_logged_in(user_name: String, login: ServerLoginInfo, rooms: RoomMap, users: UserMap) -> Self {
ServerLoggedOutContext {
user_name,
error: None,
}
.login(login),
Self::LoggedIn(
)
}
/// Attempts to record a successful login. Fails if we are already logged in.
pub fn login(
&mut self,
info: ServerLoginInfo,
) -> Result<(), ServerLoggedInError> {
let context = match self {
Self::LoggedIn(context) => {
return Err(ServerLoggedInError {
user_name: context.user_name.clone(),
})
}
// Replace the previous value with a dummy that we will immediately
// overwrite. This allows us to move the previous context out from under
// a mutable reference.
Self::LoggedOut(context) => mem::replace(
context,
ServerLoggedOutContext {
user_name: String::new(),
error: None,
},
),
};
*self = Self::LoggedIn(context.login(info));
Ok(())
}
/// Attempts to record that we logged out. Fails if we are not logged in.
pub fn logout(&mut self) -> Result<(), ServerLoggedOutError> {
if let Self::LoggedOut(context) = self {
return Err(ServerLoggedOutError {
error: context.error.clone(),
});
}
// See `mem::replace()` call in `login()`.
let old_self = mem::replace(self, Self::new(String::new()));
let context = match old_self {
Self::LoggedOut(context) => unreachable!(),
Self::LoggedIn(context) => context,
};
*self = Self::LoggedOut(context.logout());
Ok(())
}
/// Returns a reference to the logged in context.
pub fn logged_in(
&mut self,
) -> Result<&mut ServerLoggedInContext, ServerLoggedOutError> {
match self {
Self::LoggedIn(context) => Ok(context),
Self::LoggedOut(context) => Err(ServerLoggedOutError {
error: context.error.clone(),
}),
}
}
/// Returns a reference to the logged out context.
pub fn logged_out(
&mut self,
) -> Result<&mut ServerLoggedOutContext, ServerLoggedInError> {
match self {
Self::LoggedIn(context) => Err(ServerLoggedInError {
user_name: context.user_name.clone(),
}),
Self::LoggedOut(context) => Ok(context),
}
}
}

+ 53
- 0
client/src/server/login.rs View File

@ -1,3 +1,8 @@
use anyhow::bail;
use crate::context::Context;
use crate::event::EventHandler;
/// Represents the status of the login operation. /// Represents the status of the login operation.
/// ///
/// In order to interact with the server, a client cannot simply open a network /// In order to interact with the server, a client cannot simply open a network
@ -20,3 +25,51 @@ pub enum LoginStatus {
/// Stores the error message as received from the server. /// Stores the error message as received from the server.
Failure(String), Failure(String),
} }
pub enum LoginEvent {
Success(String),
Failure(String),
Disconnected,
StatusRequest,
}
pub struct LoginEventHandler {}
fn handle_login_success(
context: &mut Context,
motd: String,
) -> anyhow::Result<()> {
bail!("unimplemented")
}
fn handle_login_failure(
context: &mut Context,
error: String,
) -> anyhow::Result<()> {
bail!("unimplemented")
}
fn handle_disconnected(context: &mut Context) -> anyhow::Result<()> {
bail!("unimplemented")
}
fn handle_status_request(context: &mut Context) -> anyhow::Result<()> {
bail!("unimplemented")
}
impl EventHandler for LoginEventHandler {
type Event = LoginEvent;
fn handle(
&mut self,
context: &mut Context,
event: LoginEvent,
) -> anyhow::Result<()> {
match event {
LoginEvent::Success(motd) => handle_login_success(context, motd),
LoginEvent::Failure(error) => handle_login_failure(context, error),
LoginEvent::Disconnected => handle_disconnected(context),
LoginEvent::StatusRequest => handle_status_request(context),
}
}
}

+ 1
- 0
client/src/server/mod.rs View File

@ -1,3 +1,4 @@
pub mod context;
mod login; mod login;
pub mod room; pub mod room;
pub mod user; pub mod user;


+ 1
- 0
client/src/server/room/event.rs View File

@ -220,6 +220,7 @@ mod tests {
// Close the channel, so we can observe it was empty without hanging. // Close the channel, so we can observe it was empty without hanging.
drop(bundle.context.server_request_tx); drop(bundle.context.server_request_tx);
bundle.context.server.logout().expect("logging out");
assert_eq!(bundle.server_request_rx.blocking_recv(), None); assert_eq!(bundle.server_request_rx.blocking_recv(), None);
} }


+ 2
- 2
client/src/server/room/map.rs View File

@ -21,7 +21,7 @@ pub struct RoomMembershipChangeError(RoomMembership, RoomMembership);
pub struct RoomNotFoundError(String); pub struct RoomNotFoundError(String);
/// An entry in the chat room map. /// An entry in the chat room map.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct RoomEntry { pub struct RoomEntry {
name: String, name: String,
state: RoomState, state: RoomState,
@ -138,7 +138,7 @@ impl RoomEntry {
/// Contains the mapping from room names to room data and provides a clean /// Contains the mapping from room names to room data and provides a clean
/// interface to interact with it. /// interface to interact with it.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct RoomMap { pub struct RoomMap {
/// The actual map from room names to room data. /// The actual map from room names to room data.
map: HashMap<String, RoomEntry>, map: HashMap<String, RoomEntry>,


+ 1
- 1
client/src/server/user/map.rs View File

@ -11,7 +11,7 @@ pub struct UserNotFoundError(String);
/// Contains the mapping from user names to user data and provides a clean /// Contains the mapping from user names to user data and provides a clean
/// interface to interact with it. /// interface to interact with it.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct UserMap { pub struct UserMap {
/// The actual map from user names to user data and privileged status. /// The actual map from user names to user data and privileged status.
map: collections::HashMap<String, User>, map: collections::HashMap<String, User>,


Loading…
Cancel
Save