diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index ce8bee5..d3dac77 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -196,7 +196,7 @@ impl DockerData { /// 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 /// 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 .docker .list_containers(Some(ListContainersOptions:: { @@ -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) async fn update_everything(&mut self) { let all_ids = self.update_all_containers().await; @@ -314,27 +334,6 @@ impl DockerData { }; self.update_all_container_stats(&all_ids); 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 @@ -387,6 +386,7 @@ impl DockerData { } gui_state.lock().stop_loading_animation(uuid); }); + self.update_everything().await; } @@ -423,7 +423,7 @@ impl DockerData { } /// Initialise self, and start the message receiving loop - pub async fn init( + pub async fn start( app_data: Arc>, docker: Docker, docker_rx: Receiver, diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index a736182..6799aaa 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -5,7 +5,7 @@ use std::{ time::SystemTime, }; -use bollard::{container::LogsOptions, Docker}; +use bollard::container::LogsOptions; use cansi::v3::categorise_text; use crossterm::{ event::{DisableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, @@ -40,7 +40,7 @@ pub struct InputHandler { impl InputHandler { /// Initialize self, and running the message handling loop - pub async fn init( + pub async fn start( app_data: Arc>, rec: Receiver, docker_tx: Sender, @@ -55,11 +55,11 @@ impl InputHandler { rec, mouse_capture: true, }; - inner.start().await; + inner.message_handler().await; } /// check for incoming messages - async fn start(&mut self) { + async fn message_handler(&mut self) { while let Some(message) = self.rec.recv().await { match message { InputMessages::ButtonPress(key) => self.button_press(key.0, key.1).await, @@ -124,7 +124,7 @@ impl InputHandler { if !is_oxker && tty_readable() { let uuid = Uuid::new_v4(); GuiState::start_loading_animation(&self.gui_state, uuid); - let (sx, rx) = tokio::sync::oneshot::channel::>(); + let (sx, rx) = tokio::sync::oneshot::channel(); self.docker_tx.send(DockerMessage::Exec(sx)).await.ok(); if let Ok(docker) = rx.await { @@ -147,111 +147,101 @@ impl InputHandler { /// Toggle the mouse capture (via input of the 'm' key) 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 execute!(std::io::stdout(), DisableMouseCapture).is_ok() { self.gui_state .lock() .set_info_box("✖ mouse capture disabled"); } else { - self.app_data.lock().set_error( - AppError::MouseCapture(false), - &self.gui_state, - Status::Error, - ); + err(); } } else if Ui::enable_mouse_capture().is_ok() { self.gui_state .lock() .set_info_box("✓ mouse capture enabled"); } else { - self.app_data.lock().set_error( - AppError::MouseCapture(true), - &self.gui_state, - Status::Error, - ); + err(); }; self.mouse_capture = !self.mouse_capture; } /// Save the currently selected containers logs into a `[container_name]_[timestamp].log` file - async fn s_key(&self) { - /// This is the inner workings, *inlined* here to return a Result - async fn save_logs( - app_data: &Arc>, - gui_state: &Arc>, - docker_tx: &Sender, - ) -> Result<(), Box> { - 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::>(); - docker_tx.send(DockerMessage::Exec(sx)).await?; + async fn save_logs(&self) -> Result<(), Box> { + let args = self.app_data.lock().args.clone(); + let container = self.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(); + self.docker_tx.send(DockerMessage::Exec(sx)).await?; - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map_or(0, |i| i.as_secs()); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .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:: { - stderr: true, - stdout: true, - timestamps: args.timestamp, - since: 0, - ..Default::default() - }); - let mut logs = docker.logs(id.get(), options); - let mut output = vec![]; + let options = Some(LogsOptions:: { + stderr: true, + stdout: true, + timestamps: args.timestamp, + since: 0, + ..Default::default() + }); + let mut logs = rx.await?.logs(id.get(), options); + let mut output = vec![]; - while let Some(Ok(value)) = logs.next().await { - let data = value.to_string(); - if !data.trim().is_empty() { - output.push( - categorise_text(&data) - .into_iter() - .map(|i| i.text) - .collect::(), - ); - } - } - if !output.is_empty() { - let mut stream = BufWriter::new( - OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(&path)?, + while let Some(Ok(value)) = logs.next().await { + let data = value.to_string(); + if !data.trim().is_empty() { + output.push( + categorise_text(&data) + .into_iter() + .map(|i| i.text) + .collect::(), ); - - for line in &output { - stream.write_all(line.as_bytes())?; - } - stream.flush()?; - - gui_state - .lock() - .set_info_box(&format!("saved to {}", path.display())); } } - } - Ok(()) - } + 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()?; + + 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 status = self.gui_state.lock().status_contains(&[log_status]); if !status { self.gui_state.lock().status_push(log_status); - let uuid = Uuid::new_v4(); GuiState::start_loading_animation(&self.gui_state, uuid); - if save_logs(&self.app_data, &self.gui_state, &self.docker_tx) - .await - .is_err() - { + if self.save_logs().await.is_err() { self.app_data.lock().set_error( AppError::DockerLogs, &self.gui_state, @@ -296,50 +286,43 @@ impl InputHandler { } /// 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) { - let is_containers = - self.gui_state.lock().get_selected_panel() == SelectablePanel::Containers; - let count = if self.app_data.lock().get_container_len() == 0 && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { + self.gui_state.lock().next_panel(); + if self.app_data.lock().get_container_len() == 0 + && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands + { self.gui_state.lock().next_panel(); } } /// Change to previously selected panel + /// Need to skip the commands planel if there no are current containers running fn back_tab_key(&self) { - let is_containers = self.gui_state.lock().get_selected_panel() == SelectablePanel::Logs; - let count = if self.app_data.lock().get_container_len() == 0 && is_containers { - 2 - } else { - 1 - }; - for _ in 0..count { + self.gui_state.lock().previous_panel(); + if self.app_data.lock().get_container_len() == 0 + && self.gui_state.lock().get_selected_panel() == SelectablePanel::Commands + { self.gui_state.lock().previous_panel(); } } fn home_key(&self) { - let mut locked_data = self.app_data.lock(); let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => locked_data.containers_start(), - SelectablePanel::Logs => locked_data.log_start(), - SelectablePanel::Commands => locked_data.docker_controls_start(), + SelectablePanel::Containers => self.app_data.lock().containers_start(), + SelectablePanel::Logs => self.app_data.lock().log_start(), + SelectablePanel::Commands => self.app_data.lock().docker_controls_start(), } } /// Go to end of the list of the currently selected panel fn end_key(&self) { - let mut locked_data = self.app_data.lock(); let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => locked_data.containers_end(), - SelectablePanel::Logs => locked_data.log_end(), - SelectablePanel::Commands => locked_data.docker_controls_end(), + SelectablePanel::Containers => self.app_data.lock().containers_end(), + SelectablePanel::Logs => self.app_data.lock().log_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 fn next(&self) { - let mut locked_data = self.app_data.lock(); let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => locked_data.containers_next(), - SelectablePanel::Logs => locked_data.log_next(), - SelectablePanel::Commands => locked_data.docker_controls_next(), + SelectablePanel::Containers => self.app_data.lock().containers_next(), + SelectablePanel::Logs => self.app_data.lock().log_next(), + SelectablePanel::Commands => self.app_data.lock().docker_controls_next(), }; } /// Change state to previous, depending which panel is currently in focus fn previous(&self) { - let mut locked_data = self.app_data.lock(); let selected_panel = self.gui_state.lock().get_selected_panel(); match selected_panel { - SelectablePanel::Containers => locked_data.containers_previous(), - SelectablePanel::Logs => locked_data.log_previous(), - SelectablePanel::Commands => locked_data.docker_controls_previous(), + SelectablePanel::Containers => self.app_data.lock().containers_previous(), + SelectablePanel::Logs => self.app_data.lock().log_previous(), + SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(), } } } diff --git a/src/main.rs b/src/main.rs index e11b334..ca23fb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,28 +54,26 @@ async fn docker_init( gui_state: &Arc>, host: Option, ) { + // let err = || let connection = host.map_or_else(Docker::connect_with_socket_defaults, |host| { Docker::connect_with_socket(&host, 120, API_DEFAULT_VERSION) }); if let Ok(docker) = connection { if docker.ping().await.is_ok() { - let app_data = Arc::clone(app_data); - let gui_state = Arc::clone(gui_state); - - tokio::spawn(DockerData::init( - app_data, docker, docker_rx, docker_tx, gui_state, + tokio::spawn(DockerData::start( + Arc::clone(app_data), + docker, + docker_rx, + docker_tx, + Arc::clone(gui_state), )); - } else { - app_data - .lock() - .set_error(AppError::DockerConnect, gui_state, Status::DockerConnect); + return; } - } 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 @@ -86,15 +84,12 @@ fn handler_init( input_rx: Receiver, is_running: &Arc, ) { - let app_data = Arc::clone(app_data); - let gui_state = Arc::clone(gui_state); - let is_running = Arc::clone(is_running); - tokio::spawn(input_handler::InputHandler::init( - app_data, + tokio::spawn(input_handler::InputHandler::start( + Arc::clone(app_data), input_rx, docker_sx.clone(), - gui_state, - is_running, + Arc::clone(gui_state), + Arc::clone(is_running), )); }