refactor: input_handler

This commit is contained in:
Jack Wills
2024-11-16 14:50:25 +00:00
parent 8b9fe42468
commit 7f42383495
3 changed files with 128 additions and 152 deletions
+23 -23
View File
@@ -196,7 +196,7 @@ impl DockerData {
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here /// Get all current containers, handle into ContainerItem in the app_data struct rather than here
/// Just make sure that items sent are guaranteed to have an id /// Just make sure that items sent are guaranteed to have an id
/// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set /// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set
pub async fn update_all_containers(&self) -> Vec<(State, ContainerId)> { async fn update_all_containers(&self) -> Vec<(State, ContainerId)> {
let containers = self let containers = self
.docker .docker
.list_containers(Some(ListContainersOptions::<String> { .list_containers(Some(ListContainersOptions::<String> {
@@ -293,6 +293,26 @@ impl DockerData {
} }
} }
/// Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) {
self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4();
GuiState::start_loading_animation(&self.gui_state, loading_uuid);
let all_ids = self.update_all_containers().await;
self.update_all_container_stats(&all_ids);
let init = Arc::new(AtomicUsize::new(0));
self.init_all_logs(&all_ids, &Some(Arc::clone(&init)));
while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids.len() {
self.app_data.lock().sort_containers();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
self.gui_state.lock().stop_loading_animation(loading_uuid);
self.gui_state.lock().status_del(Status::Init);
}
/// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed) /// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed)
async fn update_everything(&mut self) { async fn update_everything(&mut self) {
let all_ids = self.update_all_containers().await; let all_ids = self.update_all_containers().await;
@@ -314,27 +334,6 @@ impl DockerData {
}; };
self.update_all_container_stats(&all_ids); self.update_all_container_stats(&all_ids);
self.app_data.lock().sort_containers(); self.app_data.lock().sort_containers();
self.gui_state.lock().stop_loading_animation(Uuid::nil());
}
/// Initialize docker container data, before any messages are received
async fn initialise_container_data(&mut self) {
self.gui_state.lock().status_push(Status::Init);
let loading_uuid = Uuid::new_v4();
GuiState::start_loading_animation(&self.gui_state, loading_uuid);
let all_ids = self.update_all_containers().await;
self.update_all_container_stats(&all_ids);
let init = Arc::new(AtomicUsize::new(0));
self.init_all_logs(&all_ids, &Some(Arc::clone(&init)));
while init.load(std::sync::atomic::Ordering::SeqCst) != all_ids.len() {
self.app_data.lock().sort_containers();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
self.gui_state.lock().stop_loading_animation(loading_uuid);
self.gui_state.lock().status_del(Status::Init);
} }
/// Set the global error as the docker error, and set gui_state to error /// Set the global error as the docker error, and set gui_state to error
@@ -387,6 +386,7 @@ impl DockerData {
} }
gui_state.lock().stop_loading_animation(uuid); gui_state.lock().stop_loading_animation(uuid);
}); });
self.update_everything().await; self.update_everything().await;
} }
@@ -423,7 +423,7 @@ impl DockerData {
} }
/// Initialise self, and start the message receiving loop /// Initialise self, and start the message receiving loop
pub async fn init( pub async fn start(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
docker: Docker, docker: Docker,
docker_rx: Receiver<DockerMessage>, docker_rx: Receiver<DockerMessage>,
+90 -109
View File
@@ -5,7 +5,7 @@ use std::{
time::SystemTime, time::SystemTime,
}; };
use bollard::{container::LogsOptions, Docker}; use bollard::container::LogsOptions;
use cansi::v3::categorise_text; use cansi::v3::categorise_text;
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
@@ -40,7 +40,7 @@ pub struct InputHandler {
impl InputHandler { impl InputHandler {
/// Initialize self, and running the message handling loop /// Initialize self, and running the message handling loop
pub async fn init( pub async fn start(
app_data: Arc<Mutex<AppData>>, app_data: Arc<Mutex<AppData>>,
rec: Receiver<InputMessages>, rec: Receiver<InputMessages>,
docker_tx: Sender<DockerMessage>, docker_tx: Sender<DockerMessage>,
@@ -55,11 +55,11 @@ impl InputHandler {
rec, rec,
mouse_capture: true, mouse_capture: true,
}; };
inner.start().await; inner.message_handler().await;
} }
/// check for incoming messages /// check for incoming messages
async fn start(&mut self) { async fn message_handler(&mut self) {
while let Some(message) = self.rec.recv().await { while let Some(message) = self.rec.recv().await {
match message { match message {
InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await, InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await,
@@ -124,7 +124,7 @@ impl InputHandler {
if !is_oxker && tty_readable() { if !is_oxker && tty_readable() {
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
GuiState::start_loading_animation(&self.gui_state, uuid); GuiState::start_loading_animation(&self.gui_state, uuid);
let (sx, rx) = tokio::sync::oneshot::channel::<Arc<Docker>>(); let (sx, rx) = tokio::sync::oneshot::channel();
self.docker_tx.send(DockerMessage::Exec(sx)).await.ok(); self.docker_tx.send(DockerMessage::Exec(sx)).await.ok();
if let Ok(docker) = rx.await { if let Ok(docker) = rx.await {
@@ -147,111 +147,101 @@ impl InputHandler {
/// Toggle the mouse capture (via input of the 'm' key) /// Toggle the mouse capture (via input of the 'm' key)
fn m_key(&mut self) { fn m_key(&mut self) {
let err = || {
self.app_data.lock().set_error(
AppError::MouseCapture(!self.mouse_capture),
&self.gui_state,
Status::Error,
);
};
if self.mouse_capture { if self.mouse_capture {
if execute!(std::io::stdout(), DisableMouseCapture).is_ok() { if execute!(std::io::stdout(), DisableMouseCapture).is_ok() {
self.gui_state self.gui_state
.lock() .lock()
.set_info_box("✖ mouse capture disabled"); .set_info_box("✖ mouse capture disabled");
} else { } else {
self.app_data.lock().set_error( err();
AppError::MouseCapture(false),
&self.gui_state,
Status::Error,
);
} }
} else if Ui::enable_mouse_capture().is_ok() { } else if Ui::enable_mouse_capture().is_ok() {
self.gui_state self.gui_state
.lock() .lock()
.set_info_box("✓ mouse capture enabled"); .set_info_box("✓ mouse capture enabled");
} else { } else {
self.app_data.lock().set_error( err();
AppError::MouseCapture(true),
&self.gui_state,
Status::Error,
);
}; };
self.mouse_capture = !self.mouse_capture; self.mouse_capture = !self.mouse_capture;
} }
/// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file /// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file
async fn s_key(&self) { async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
/// This is the inner workings, *inlined* here to return a Result let args = self.app_data.lock().args.clone();
async fn save_logs( let container = self.app_data.lock().get_selected_container_id_state_name();
app_data: &Arc<Mutex<AppData>>, if let Some((id, _, name)) = container {
gui_state: &Arc<Mutex<GuiState>>, if let Some(log_path) = args.save_dir {
docker_tx: &Sender<DockerMessage>, let (sx, rx) = tokio::sync::oneshot::channel();
) -> Result<(), Box<dyn std::error::Error>> { self.docker_tx.send(DockerMessage::Exec(sx)).await?;
let args = app_data.lock().args.clone();
let container = app_data.lock().get_selected_container_id_state_name();
if let Some((id, _, name)) = container {
if let Some(log_path) = args.save_dir {
let (sx, rx) = tokio::sync::oneshot::channel::<Arc<Docker>>();
docker_tx.send(DockerMessage::Exec(sx)).await?;
let now = SystemTime::now() let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |i| i.as_secs()); .map_or(0, |i| i.as_secs());
let path = log_path.join(format!("{name}_{now}.log")); let path = log_path.join(format!("{name}_{now}.log"));
let docker = rx.await?; let options = Some(LogsOptions::<String> {
let options = Some(LogsOptions::<String> { stderr: true,
stderr: true, stdout: true,
stdout: true, timestamps: args.timestamp,
timestamps: args.timestamp, since: 0,
since: 0, ..Default::default()
..Default::default() });
}); let mut logs = rx.await?.logs(id.get(), options);
let mut logs = docker.logs(id.get(), options); let mut output = vec![];
let mut output = vec![];
while let Some(Ok(value)) = logs.next().await { while let Some(Ok(value)) = logs.next().await {
let data = value.to_string(); let data = value.to_string();
if !data.trim().is_empty() { if !data.trim().is_empty() {
output.push( output.push(
categorise_text(&data) categorise_text(&data)
.into_iter() .into_iter()
.map(|i| i.text) .map(|i| i.text)
.collect::<String>(), .collect::<String>(),
);
}
}
if !output.is_empty() {
let mut stream = BufWriter::new(
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)?,
); );
for line in &output {
stream.write_all(line.as_bytes())?;
}
stream.flush()?;
gui_state
.lock()
.set_info_box(&format!("saved to {}", path.display()));
} }
} }
} if !output.is_empty() {
Ok(()) let mut stream = BufWriter::new(
} OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)?,
);
for line in &output {
stream.write_all(line.as_bytes())?;
}
stream.flush()?;
self.gui_state
.lock()
.set_info_box(&format!("saved to {}", path.display()));
}
}
}
Ok(())
}
/// Attempt to save the currently selected container logs to a file
async fn s_key(&self) {
let log_status = Status::Logs; let log_status = Status::Logs;
let status = self.gui_state.lock().status_contains(&[log_status]); let status = self.gui_state.lock().status_contains(&[log_status]);
if !status { if !status {
self.gui_state.lock().status_push(log_status); self.gui_state.lock().status_push(log_status);
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
GuiState::start_loading_animation(&self.gui_state, uuid); GuiState::start_loading_animation(&self.gui_state, uuid);
if save_logs(&self.app_data, &self.gui_state, &self.docker_tx) if self.save_logs().await.is_err() {
.await
.is_err()
{
self.app_data.lock().set_error( self.app_data.lock().set_error(
AppError::DockerLogs, AppError::DockerLogs,
&self.gui_state, &self.gui_state,
@@ -296,50 +286,43 @@ impl InputHandler {
} }
/// Change the the "next" selectable panel /// Change the the "next" selectable panel
/// If no containers, and on Commands panel, skip to next panel, as Commands panel isn't visible in this state
fn tab_key(&self) { fn tab_key(&self) {
let is_containers = self.gui_state.lock().next_panel();
self.gui_state.lock().get_selected_panel() == SelectablePanel::Containers; if self.app_data.lock().get_container_len() == 0
let count = if self.app_data.lock().get_container_len() == 0 && is_containers { && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
2 {
} else {
1
};
for _ in 0..count {
self.gui_state.lock().next_panel(); self.gui_state.lock().next_panel();
} }
} }
/// Change to previously selected panel /// Change to previously selected panel
/// Need to skip the commands planel if there no are current containers running
fn back_tab_key(&self) { fn back_tab_key(&self) {
let is_containers = self.gui_state.lock().get_selected_panel() == SelectablePanel::Logs; self.gui_state.lock().previous_panel();
let count = if self.app_data.lock().get_container_len() == 0 && is_containers { if self.app_data.lock().get_container_len() == 0
2 && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands
} else { {
1
};
for _ in 0..count {
self.gui_state.lock().previous_panel(); self.gui_state.lock().previous_panel();
} }
} }
fn home_key(&self) { fn home_key(&self) {
let mut locked_data = self.app_data.lock();
let selected_panel = self.gui_state.lock().get_selected_panel(); let selected_panel = self.gui_state.lock().get_selected_panel();
match selected_panel { match selected_panel {
SelectablePanel::Containers => locked_data.containers_start(), SelectablePanel::Containers => self.app_data.lock().containers_start(),
SelectablePanel::Logs => locked_data.log_start(), SelectablePanel::Logs => self.app_data.lock().log_start(),
SelectablePanel::Commands => locked_data.docker_controls_start(), SelectablePanel::Commands => self.app_data.lock().docker_controls_start(),
} }
} }
/// Go to end of the list of the currently selected panel /// Go to end of the list of the currently selected panel
fn end_key(&self) { fn end_key(&self) {
let mut locked_data = self.app_data.lock();
let selected_panel = self.gui_state.lock().get_selected_panel(); let selected_panel = self.gui_state.lock().get_selected_panel();
match selected_panel { match selected_panel {
SelectablePanel::Containers => locked_data.containers_end(), SelectablePanel::Containers => self.app_data.lock().containers_end(),
SelectablePanel::Logs => locked_data.log_end(), SelectablePanel::Logs => self.app_data.lock().log_end(),
SelectablePanel::Commands => locked_data.docker_controls_end(), SelectablePanel::Commands => self.app_data.lock().docker_controls_end(),
} }
} }
@@ -525,23 +508,21 @@ impl InputHandler {
/// Change state to next, depending which panel is currently in focus /// Change state to next, depending which panel is currently in focus
fn next(&self) { fn next(&self) {
let mut locked_data = self.app_data.lock();
let selected_panel = self.gui_state.lock().get_selected_panel(); let selected_panel = self.gui_state.lock().get_selected_panel();
match selected_panel { match selected_panel {
SelectablePanel::Containers => locked_data.containers_next(), SelectablePanel::Containers => self.app_data.lock().containers_next(),
SelectablePanel::Logs => locked_data.log_next(), SelectablePanel::Logs => self.app_data.lock().log_next(),
SelectablePanel::Commands => locked_data.docker_controls_next(), SelectablePanel::Commands => self.app_data.lock().docker_controls_next(),
}; };
} }
/// Change state to previous, depending which panel is currently in focus /// Change state to previous, depending which panel is currently in focus
fn previous(&self) { fn previous(&self) {
let mut locked_data = self.app_data.lock();
let selected_panel = self.gui_state.lock().get_selected_panel(); let selected_panel = self.gui_state.lock().get_selected_panel();
match selected_panel { match selected_panel {
SelectablePanel::Containers => locked_data.containers_previous(), SelectablePanel::Containers => self.app_data.lock().containers_previous(),
SelectablePanel::Logs => locked_data.log_previous(), SelectablePanel::Logs => self.app_data.lock().log_previous(),
SelectablePanel::Commands => locked_data.docker_controls_previous(), SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(),
} }
} }
} }
+15 -20
View File
@@ -54,28 +54,26 @@ async fn docker_init(
gui_state: &Arc<Mutex<GuiState>>, gui_state: &Arc<Mutex<GuiState>>,
host: Option<String>, host: Option<String>,
) { ) {
// let err = ||
let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| { let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| {
Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION) Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION)
}); });
if let Ok(docker) = connection { if let Ok(docker) = connection {
if docker.ping().await.is_ok() { if docker.ping().await.is_ok() {
let app_data = Arc::clone(app_data); tokio::spawn(DockerData::start(
let gui_state = Arc::clone(gui_state); Arc::clone(app_data),
docker,
tokio::spawn(DockerData::init( docker_rx,
app_data, docker, docker_rx, docker_tx, gui_state, docker_tx,
Arc::clone(gui_state),
)); ));
} else { return;
app_data
.lock()
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
} }
} else {
app_data
.lock()
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
} }
app_data
.lock()
.set_error(AppError::DockerConnect, gui_state, Status::DockerConnect);
} }
/// Create data for, and then spawn a tokio thread, for the input handler /// Create data for, and then spawn a tokio thread, for the input handler
@@ -86,15 +84,12 @@ fn handler_init(
input_rx: Receiver<InputMessages>, input_rx: Receiver<InputMessages>,
is_running: &Arc<AtomicBool>, is_running: &Arc<AtomicBool>,
) { ) {
let app_data = Arc::clone(app_data); tokio::spawn(input_handler::InputHandler::start(
let gui_state = Arc::clone(gui_state); Arc::clone(app_data),
let is_running = Arc::clone(is_running);
tokio::spawn(input_handler::InputHandler::init(
app_data,
input_rx, input_rx,
docker_sx.clone(), docker_sx.clone(),
gui_state, Arc::clone(gui_state),
is_running, Arc::clone(is_running),
)); ));
} }