| @ -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), | |||||
| } | |||||
| } | |||||
| } | |||||