use std::
{
io::Error,
time::Instant,
};
use crossterm::event::
{
KeyCode,
KeyEvent,
KeyModifiers,
};
use zeroize::Zeroizing;
use tokio::
{
sync::mpsc::Sender,
net::tcp::{ OwnedReadHalf, OwnedWriteHalf },
};
use crate::
{
config,
options,
network::client,
};
use super::
{
consts,
input::InputBuffer,
state::App,
};
pub type ConnectResult = (u64, Result<(OwnedReadHalf, OwnedWriteHalf), Error>);
pub enum Action {
None,
Connect,
Submit, Quit,
}
#[derive(Clone, Copy, PartialEq)] pub enum Stage
{
Address,
Username,
Password { register: bool },
}
pub struct Login {
pub input: InputBuffer,
pub stage: Stage,
pub busy: bool, pub connected: bool, pub error: Option<String>, pub hint: Option<String>, attempt: u64, }
#[derive(Default)]
pub struct Reconnect
{
credentials: Option<(String, Zeroizing<String>)>, typed: (String, Zeroizing<String>), due: Option<Instant>, left: u32, answers: u32, retrying: bool, pub submit: bool, }
impl Reconnect
{
pub fn remember(&mut self, stage: Stage, text: &str)
{
match stage
{
Stage::Username => self.typed.0 = text.to_owned(),
Stage::Password { .. } => self.typed.1 = Zeroizing::new(text.to_owned()),
Stage::Address => {},
}
}
pub fn accepted(&mut self)
{
self.credentials = Some(self.typed.clone());
self.left = consts::RECONNECT_ATTEMPTS;
self.retrying = false;
self.due = None;
}
pub fn forget(&mut self) { *self = Self::default(); }
pub fn arm(&mut self) -> bool
{
if self.credentials.is_none() || self.left == 0
{
self.retrying = false;
return false;
}
self.left -= 1;
self.retrying = true;
self.answers = 2; self.due = Some(Instant::now() + consts::RECONNECT_DELAY);
true
}
pub fn status(&self) -> Option<String>
{
self.retrying.then(|| format!("Connection lost, reconnecting… ({}/{})",
consts::RECONNECT_ATTEMPTS - self.left, consts::RECONNECT_ATTEMPTS))
}
pub fn take_due(&mut self) -> bool
{
if self.due.is_none_or(|due| Instant::now() < due) { return false; }
self.due = None;
true
}
pub fn answer(&mut self, stage: Stage) -> Option<String>
{
if !self.retrying || self.answers == 0 { return None; }
let (username, password) = self.credentials.as_ref()?;
let answer = match stage
{
Stage::Username => username.clone(),
Stage::Password { register: false } => password.to_string(),
_ => return None,
};
self.answers -= 1;
Some(answer)
}
}
impl Default for Login
{
fn default() -> Self { Self::new() }
}
impl Login
{
pub fn new() -> Self
{
let mut input = InputBuffer::new();
let auto = config::read_config::<bool>("auto_connect");
if auto { input.insert_str(config::read_config::<String>("auto_connect_addr").trim()); }
Self { input, stage: Stage::Address, busy: auto, connected: false, error: None, hint: None, attempt: 0 }
}
pub fn again(address: &str, attempt: u64, error: String) -> Self
{
let mut input = InputBuffer::new();
input.insert_str(address);
Self { input, stage: Stage::Address, busy: false, connected: false, error: Some(error), hint: None, attempt }
}
pub fn address(&self) -> String { self.input.text().trim().to_owned() }
pub fn attempt(&self) -> u64 { self.attempt }
pub fn accepts(&self, attempt: u64) -> bool { self.busy && attempt == self.attempt }
pub fn failed(&mut self, error: &Error)
{
self.busy = false;
self.error = Some(error.to_string());
}
pub fn ask(&mut self, stage: Stage, hint: Option<String>)
{
self.stage = stage;
self.hint = hint;
self.busy = false;
self.input = InputBuffer::new();
}
pub fn masked(&self) -> bool { matches!(self.stage, Stage::Password { .. }) }
pub fn title(&self) -> &'static str
{
match self.stage
{
Stage::Address => " Connect ",
Stage::Username => " Identify ",
Stage::Password { register: true } => " Register ",
Stage::Password { register: false } => " Log in ",
}
}
pub fn label(&self) -> &'static str
{
match self.stage
{
Stage::Address => "Server address",
Stage::Username => "Username",
Stage::Password { .. } => "Password",
}
}
pub fn waiting(&self) -> &'static str
{
match (self.stage, self.connected)
{
(Stage::Address, false) => "Connecting…",
(Stage::Address, true) => "Exchanging keys…", _ => "Waiting for the server…",
}
}
pub fn cancellable(&self) -> bool { self.busy && !self.connected && self.stage == Stage::Address }
}
pub fn handle_key(app: &mut App, key: KeyEvent) -> Action
{
let Some(login) = app.login.as_mut() else { return Action::None };
if key.code == KeyCode::Esc
{
if login.connected || login.stage != Stage::Address || !login.busy { return Action::Quit; }
login.busy = false;
login.error = None;
app.reconnect.forget();
return Action::None;
}
if login.busy { return Action::None; }
if key.modifiers.contains(KeyModifiers::CONTROL)
{
match key.code
{
KeyCode::Char('a') => login.input.home(),
KeyCode::Char('e') => login.input.end(),
KeyCode::Char('u') => login.input.kill_to_start(),
KeyCode::Char('k') => login.input.kill_to_end(),
KeyCode::Char('w') => login.input.delete_word(),
_ => {},
}
return Action::None;
}
match key.code
{
KeyCode::Char(character) => login.input.insert(character),
KeyCode::Backspace => login.input.backspace(),
KeyCode::Delete => login.input.delete(),
KeyCode::Left => login.input.left(),
KeyCode::Right => login.input.right(),
KeyCode::Home => login.input.home(),
KeyCode::End => login.input.end(),
KeyCode::Enter => match login.stage
{
Stage::Address =>
{
if login.address().is_empty()
{
login.error = Some(String::from("Enter the address of a server."));
} else { return Action::Connect; }
},
_ =>
{
if login.input.text().is_empty()
{
login.error = Some(format!("Enter a {}.", login.label().to_lowercase()));
} else { return Action::Submit; }
},
},
_ => {},
}
Action::None
}
pub fn insert_str(app: &mut App, text: &str) {
if let Some(login) = app.login.as_mut() && !login.busy
{
login.input.insert_str(&text.replace(['\r', '\n'], ""));
}
}
pub fn take_input(app: &mut App) -> String
{
let Some(login) = app.login.as_mut() else { return String::new() };
let text = login.input.text();
let stage = login.stage;
login.input = InputBuffer::new();
login.error = None;
login.hint = None;
login.busy = true;
app.reconnect.remember(stage, &text);
text
}
pub fn connect(app: &mut App, results: &Sender<ConnectResult>)
{
let Some(login) = app.login.as_mut() else { return };
let display = login.address();
if display.is_empty() { return; }
login.busy = true;
login.error = None;
login.attempt += 1;
let attempt = login.attempt;
let mut address = display.clone();
if !address.contains(':') { address.push_str(&format!(":{}", config::read_config::<u16>("default_port"))); }
app.address = display;
options::set_server_address(&address);
options::set_seq(0);
options::set_server_seq(0);
let results = results.clone();
tokio::spawn(async move
{
let _ = results.send((attempt, client::connect(address).await)).await;
});
}