Solstice client.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

226 lines
5.7 KiB

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,
}
/// Initial values from which a `ServerLoggedInContext` can be built.
pub struct ServerLoggedInContextOptions {
/// See similarly-named fields in `ServerLoggedInContext`.
pub user_name: String,
pub motd: String,
pub rooms: RoomMap,
pub users: UserMap,
}
impl ServerLoggedInContext {
/// Mainly for use in tests.
// TODO: Mark as #[cfg(test)]?
pub fn new(
request_tx: Sender<ServerRequest>,
options: ServerLoggedInContextOptions,
) -> Self {
Self {
rooms: options.rooms,
users: options.users,
user_name: options.user_name,
motd: options.motd,
request_tx,
}
}
/// 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,
})
}
/// 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),
}
}
}