use std::io::Error;
use crossterm::event::
{
KeyCode,
KeyEvent,
KeyModifiers,
};
use tokio::
{
sync::mpsc::Sender,
net::tcp::{ OwnedReadHalf, OwnedWriteHalf },
};
use crate::
{
config,
options,
network::client,
};
use super::
{
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, }
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;
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();
login.input = InputBuffer::new();
login.error = None;
login.hint = None;
login.busy = true;
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;
});
}