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