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