multi-ssh/src/server.rs

267 lines
6.8 KiB
Rust

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<Server> {
match self {
ServerReference::Resolved(server) => Some(server),
ServerReference::Identifier(name) => Self::resolve_server_name(&name, configured_servers),
}
}
#[allow(dead_code)]
pub fn resolve_lazy<S, F>(self, provider: &mut LazyCell<S, F>) -> Option<Server>
where
S: Deref<Target = [Server]>,
F: FnOnce() -> S,
{
match self {
ServerReference::Resolved(server) => Some(server),
ServerReference::Identifier(name) => Self::resolve_server_name(&name, provider),
}
}
pub fn try_resolve_lazy<S, F, E>(
self,
provider: &mut LazyCell<Result<S, E>, F>,
) -> Result<Option<Server>, E>
where
S: Deref<Target = [Server]>,
F: FnOnce() -> Result<S, E>,
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<Server> {
servers
.iter()
.find(|server| server.address.identifier() == identifier)
.cloned()
}
}
impl FromStr for ServerReference {
type Err = ServerReferenceParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<H: Hasher>(&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<Self, ServerParseError> {
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<S>(ssh_address: S) -> Self
where
S: ToString,
{
Self::Ssh {
ssh_address: ssh_address.to_string(),
}
}
pub fn from_str<S>(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()
);
}
}