use crate::get_home_directory; use std::cell::LazyCell; use std::error::Error; use std::fmt::{Display, Formatter}; use std::fs; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::path::PathBuf; use std::str::FromStr; #[derive(Debug, Clone)] pub enum ServerReference { Resolved(Server), Identifier(String), } impl ServerReference { pub fn get_identifier(&self) -> &str { match self { ServerReference::Resolved(server) => server.address.identifier(), ServerReference::Identifier(id) => id, } } #[allow(dead_code)] pub fn resolve(self, configured_servers: &[Server]) -> Option { match self { ServerReference::Resolved(server) => Some(server), ServerReference::Identifier(name) => Self::resolve_server_name(&name, configured_servers), } } #[allow(dead_code)] pub fn resolve_lazy(self, provider: &mut LazyCell) -> Option where S: Deref, F: FnOnce() -> S, { match self { ServerReference::Resolved(server) => Some(server), ServerReference::Identifier(name) => Self::resolve_server_name(&name, provider), } } pub fn try_resolve_lazy( self, provider: &mut LazyCell, F>, ) -> Result, E> where S: Deref, F: FnOnce() -> Result, E: Clone, { match self { ServerReference::Resolved(server) => Ok(Some(server)), ServerReference::Identifier(name) => provider .as_ref() .map_err(|e| e.clone()) .map(|servers| Self::resolve_server_name(&name, servers)), } } fn resolve_server_name(identifier: &str, servers: &[Server]) -> Option { servers .iter() .find(|server| server.address.identifier() == identifier) .cloned() } } impl FromStr for ServerReference { type Err = ServerReferenceParseError; fn from_str(s: &str) -> Result { Server::from_str(s, RelativeLocalPathAnker::CurrentDirectory) .map(Self::Resolved) .or_else(|_| Ok(Self::Identifier(s.to_string()))) } } impl PartialEq for ServerReference { fn eq(&self, other: &Self) -> bool { self.get_identifier() == other.get_identifier() } } impl Eq for ServerReference {} impl Hash for ServerReference { fn hash(&self, state: &mut H) { self.get_identifier().hash(state); } } impl Display for ServerReference { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ServerReference::Resolved(server) => write!(f, "{}", server), ServerReference::Identifier(name) => write!(f, "{}", name), } } } #[derive(Debug)] pub enum ServerReferenceParseError {} impl Display for ServerReferenceParseError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) //replace that with an actual implementation if there ever are any variants } } impl Error for ServerReferenceParseError {} #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Server { pub address: ServerAddress, pub server_directory_path: PathBuf, } impl Server { pub fn get_name(&self) -> &str { match &self.address { ServerAddress::Ssh { ssh_address } => ssh_address, ServerAddress::Localhost => "this computer", } } pub fn from_str( s: &str, relative_local_path_anker: RelativeLocalPathAnker, ) -> Result { s.split_once(':') .ok_or(ServerParseError::MissingServerDirectory) .and_then(|(identifier, server_directory)| { let address = ServerAddress::from_str(identifier); let server_directory_path = match &address { ServerAddress::Ssh { .. } => PathBuf::from(server_directory), ServerAddress::Localhost => fs::canonicalize(match relative_local_path_anker { RelativeLocalPathAnker::Home => { let home_directory = get_home_directory() .map_err(|e| ServerParseError::HomeDirectoryRequired { detail_message: e })?; home_directory.join(server_directory) } RelativeLocalPathAnker::CurrentDirectory => PathBuf::from(server_directory), }) .map_err(|_| ServerParseError::ServerDirectoryNonExistent)?, }; Ok(Self { address, server_directory_path, }) }) } } impl Display for Server { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}{}", match &self.address { ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"), ServerAddress::Localhost => "".to_string(), }, self.server_directory_path.to_string_lossy() ) } } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum RelativeLocalPathAnker { Home, CurrentDirectory, } #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum ServerAddress { Ssh { ssh_address: String }, Localhost, } impl ServerAddress { pub fn ssh(ssh_address: S) -> Self where S: ToString, { Self::Ssh { ssh_address: ssh_address.to_string(), } } pub fn from_str(s: S) -> Self where S: ToString, { let s = s.to_string(); if s == "." { Self::Localhost } else { Self::ssh(s) } } pub fn identifier(&self) -> &str { match self { ServerAddress::Ssh { ssh_address } => ssh_address, ServerAddress::Localhost => ".", } } } #[derive(Debug)] pub enum ServerParseError { MissingServerDirectory, ServerDirectoryNonExistent, HomeDirectoryRequired { detail_message: String }, } impl Display for ServerParseError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ServerParseError::MissingServerDirectory => { write!( f, "String is not specifying a server directory. Please use an empty string after a \ double colon to point to the home directory, e.g: 'lobby:'" ) } ServerParseError::HomeDirectoryRequired { detail_message } => write!( f, "localhost requires home directory, but: {detail_message}" ), ServerParseError::ServerDirectoryNonExistent => { write!(f, "The specified server directory doesn't exist") } } } } impl Error for ServerParseError {} #[cfg(test)] mod test_server_reference { use crate::server::{Server, ServerAddress, ServerReference}; use std::path::PathBuf; use std::str::FromStr; #[test] fn test_from_str() { assert_eq!( ServerReference::Identifier("foo".to_string()), ServerReference::from_str("foo").unwrap() ); assert_eq!( ServerReference::Resolved(Server { address: ServerAddress::Ssh { ssh_address: "crea".to_string() }, server_directory_path: PathBuf::from("server/creative2") }), ServerReference::from_str("crea:server/creative2").unwrap() ); } }