267 lines
6.8 KiB
Rust
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()
|
|
);
|
|
}
|
|
}
|