You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
distant/src/cli/client.rs

178 lines
6.6 KiB
Rust

use crate::config::NetworkConfig;
use anyhow::Context;
use distant_core::{
net::{AuthRequest, AuthResponse, FramedTransport, PlainCodec},
DistantManagerClient, DistantManagerClientConfig,
};
use log::*;
mod msg;
pub use msg::*;
pub struct Client {
config: DistantManagerClientConfig,
network: NetworkConfig,
}
impl Client {
pub fn new(network: NetworkConfig) -> Self {
let config = DistantManagerClientConfig::with_prompts(
|prompt| rpassword::prompt_password(prompt),
|prompt| {
use std::io::Write;
eprint!("{}", prompt);
std::io::stderr().lock().flush()?;
let mut answer = String::new();
std::io::stdin().read_line(&mut answer)?;
Ok(answer)
},
);
Self { config, network }
}
/// Configure client to talk over stdin and stdout using messages
pub fn using_msg_stdin_stdout(self) -> Self {
self.using_msg(MsgSender::from_stdout(), MsgReceiver::from_stdin())
}
/// Configure client to use a pair of msg sender and receiver
pub fn using_msg(mut self, tx: MsgSender, rx: MsgReceiver) -> Self {
self.config = DistantManagerClientConfig {
on_challenge: {
let tx = tx.clone();
let rx = rx.clone();
Box::new(move |questions, options| {
let question_cnt = questions.len();
if let Err(x) = tx.send_blocking(&AuthRequest::Challenge { questions, options })
{
error!("{}", x);
return (0..question_cnt)
.into_iter()
.map(|_| "".to_string())
.collect();
}
match rx.recv_blocking() {
Ok(AuthResponse::Challenge { answers }) => answers,
Ok(x) => {
error!("Invalid response received: {:?}", x);
(0..question_cnt)
.into_iter()
.map(|_| "".to_string())
.collect()
}
Err(x) => {
error!("{}", x);
(0..question_cnt)
.into_iter()
.map(|_| "".to_string())
.collect()
}
}
})
},
on_info: {
let tx = tx.clone();
Box::new(move |text| {
let _ = tx.send_blocking(&AuthRequest::Info { text });
})
},
on_verify: {
let tx = tx.clone();
Box::new(move |kind, text| {
if let Err(x) = tx.send_blocking(&AuthRequest::Verify { kind, text }) {
error!("{}", x);
return false;
}
match rx.recv_blocking() {
Ok(AuthResponse::Verify { valid }) => valid,
Ok(x) => {
error!("Invalid response received: {:?}", x);
false
}
Err(x) => {
error!("{}", x);
false
}
}
})
},
on_error: {
Box::new(move |kind, text| {
let _ = tx.send_blocking(&AuthRequest::Error { kind, text });
})
},
};
self
}
/// Connect to the manager listening on the socket or windows pipe based on
/// the [`NetworkConfig`] provided to the client earlier. Will return a new instance
/// of the [`DistantManagerClient`] upon successful connection
pub async fn connect(self) -> anyhow::Result<DistantManagerClient> {
#[cfg(unix)]
let transport = {
use distant_core::net::UnixSocketTransport;
let mut maybe_transport = None;
let mut error: Option<anyhow::Error> = None;
for path in self.network.to_unix_socket_path_candidates() {
match UnixSocketTransport::connect(path).await {
Ok(transport) => {
info!("Connected to unix socket @ {:?}", path);
maybe_transport = Some(FramedTransport::new(transport, PlainCodec));
break;
}
Err(x) => {
let err = anyhow::Error::new(x)
.context(format!("Failed to connect to unix socket {:?}", path));
if let Some(x) = error {
error = Some(x.context(err));
} else {
error = Some(err);
}
}
}
}
maybe_transport.ok_or_else(|| {
error.unwrap_or_else(|| anyhow::anyhow!("No unix socket candidate available"))
})?
};
#[cfg(windows)]
let transport = {
use distant_core::net::WindowsPipeTransport;
let mut maybe_transport = None;
let mut error: Option<anyhow::Error> = None;
for name in self.network.to_windows_pipe_name_candidates() {
match WindowsPipeTransport::connect_local(name).await {
Ok(transport) => {
info!("Connected to named windows socket @ {:?}", name);
maybe_transport = Some(FramedTransport::new(transport, PlainCodec));
break;
}
Err(x) => {
let err = anyhow::Error::new(x)
.context(format!("Failed to connect to windows pipe {:?}", name));
if let Some(x) = error {
error = Some(x.context(err));
} else {
error = Some(err);
}
}
}
}
maybe_transport.ok_or_else(|| {
error.unwrap_or_else(|| anyhow::anyhow!("No windows pipe candidate available"))
})?
};
DistantManagerClient::new(self.config, transport)
.context("Failed to create client for manager")
}
}