From 2a834d6c2fa4a15124d24ddbd12f667829e148ad Mon Sep 17 00:00:00 2001 From: Jack Wills <32690432+mrjackwills@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:39:06 +0000 Subject: [PATCH] refactor: execute_command() Include Id and DockerCommand in a DockerMessage, used by the execute_command function to reduce duplicated code --- src/app_data/container_state.rs | 10 +-- src/app_data/mod.rs | 57 +++++++------- src/app_error.rs | 4 +- src/docker_data/message.rs | 16 ++-- src/docker_data/mod.rs | 135 ++++++++++++-------------------- src/input_handler/mod.rs | 30 +++---- 6 files changed, 104 insertions(+), 148 deletions(-) diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 1dd717a..39fbdc5 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -333,7 +333,7 @@ impl fmt::Display for State { /// Items for the container control list #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DockerControls { +pub enum DockerCommand { Pause, Restart, Start, @@ -342,7 +342,7 @@ pub enum DockerControls { Delete, } -impl DockerControls { +impl DockerCommand { pub const fn get_color(self) -> Color { match self { Self::Pause => Color::Yellow, @@ -366,7 +366,7 @@ impl DockerControls { } } -impl fmt::Display for DockerControls { +impl fmt::Display for DockerCommand { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let disp = match self { Self::Pause => "pause", @@ -577,7 +577,7 @@ impl Logs { pub struct ContainerItem { pub cpu_stats: VecDeque, pub created: u64, - pub docker_controls: StatefulList, + pub docker_controls: StatefulList, pub id: ContainerId, pub image: ContainerImage, pub is_oxker: bool, @@ -620,7 +620,7 @@ impl ContainerItem { state: State, status: ContainerStatus, ) -> Self { - let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state)); + let mut docker_controls = StatefulList::new(DockerCommand::gen_vec(state)); docker_controls.start(); Self { diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index d21c232..9bbda29 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -535,7 +535,7 @@ impl AppData { /// Get the current selected docker command /// So know which command to execute - pub fn selected_docker_controls(&self) -> Option { + pub fn selected_docker_controls(&self) -> Option { self.get_selected_container().and_then(|i| { i.docker_controls.state.selected().and_then(|x| { i.docker_controls @@ -574,15 +574,14 @@ impl AppData { } } - /// Get mutable Option of the currently selected container DockerControls state + /// Get mutable Option of the currently selected container DockerCommand state pub fn get_control_state(&mut self) -> Option<&mut ListState> { self.get_mut_selected_container() .map(|i| &mut i.docker_controls.state) } - /// Get mutable Option of the currently selected container DockerControls items - /// TODO command or control, need a uniform name across the application - pub fn get_control_items(&mut self) -> Option<&mut Vec> { + /// Get mutable Option of the currently selected container DockerConmand items + pub fn get_control_items(&mut self) -> Option<&mut Vec> { self.get_mut_selected_container() .map(|i| &mut i.docker_controls.items) } @@ -855,7 +854,7 @@ impl AppData { item.status = status; }; if item.state != state { - item.docker_controls.items = DockerControls::gen_vec(state); + item.docker_controls.items = DockerCommand::gen_vec(state); // Update the list state, needs to be None if the gen_vec returns an empty vec match state { State::Removing | State::Restarting | State::Unknown => { @@ -1526,7 +1525,7 @@ mod tests { app_data.docker_controls_start(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Pause)); + assert_eq!(result, Some(DockerCommand::Pause)); } #[test] @@ -1539,7 +1538,7 @@ mod tests { app_data.docker_controls_next(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Restart)); + assert_eq!(result, Some(DockerCommand::Restart)); } #[test] @@ -1551,12 +1550,12 @@ mod tests { app_data.docker_controls_end(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Delete)); + assert_eq!(result, Some(DockerCommand::Delete)); // Next has no effect when at end app_data.docker_controls_next(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Delete)); + assert_eq!(result, Some(DockerCommand::Delete)); } #[test] @@ -1569,19 +1568,19 @@ mod tests { app_data.docker_controls_previous(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Stop)); + assert_eq!(result, Some(DockerCommand::Stop)); // previous has no effect when at start app_data.docker_controls_start(); app_data.docker_controls_previous(); let result = app_data.selected_docker_controls(); - assert_eq!(result, Some(DockerControls::Pause)); + assert_eq!(result, Some(DockerCommand::Pause)); } #[test] /// DockerCommands get correct controls dependant on container state fn test_app_data_get_control_items() { - let test_state = |state: State, expected: &mut Vec| { + let test_state = |state: State, expected: &mut Vec| { let gen_item_state = |state: State| { ContainerItem::new( 1, @@ -1605,42 +1604,42 @@ mod tests { test_state( State::Dead, &mut vec![ - DockerControls::Start, - DockerControls::Restart, - DockerControls::Delete, + DockerCommand::Start, + DockerCommand::Restart, + DockerCommand::Delete, ], ); test_state( State::Exited, &mut vec![ - DockerControls::Start, - DockerControls::Restart, - DockerControls::Delete, + DockerCommand::Start, + DockerCommand::Restart, + DockerCommand::Delete, ], ); test_state( State::Paused, &mut vec![ - DockerControls::Resume, - DockerControls::Stop, - DockerControls::Delete, + DockerCommand::Resume, + DockerCommand::Stop, + DockerCommand::Delete, ], ); - test_state(State::Removing, &mut vec![DockerControls::Delete]); + test_state(State::Removing, &mut vec![DockerCommand::Delete]); test_state( State::Restarting, - &mut vec![DockerControls::Stop, DockerControls::Delete], + &mut vec![DockerCommand::Stop, DockerCommand::Delete], ); test_state( State::Running(RunningState::Healthy), &mut vec![ - DockerControls::Pause, - DockerControls::Restart, - DockerControls::Stop, - DockerControls::Delete, + DockerCommand::Pause, + DockerCommand::Restart, + DockerCommand::Stop, + DockerCommand::Delete, ], ); - test_state(State::Unknown, &mut vec![DockerControls::Delete]); + test_state(State::Unknown, &mut vec![DockerCommand::Delete]); } // ****** // diff --git a/src/app_error.rs b/src/app_error.rs index ba0d66f..a4a6315 100644 --- a/src/app_error.rs +++ b/src/app_error.rs @@ -1,11 +1,11 @@ -use crate::app_data::DockerControls; +use crate::app_data::DockerCommand; use std::fmt; /// app errors to set in global state #[allow(unused)] #[derive(Debug, Clone, Copy)] pub enum AppError { - DockerCommand(DockerControls), + DockerCommand(DockerCommand), DockerExec, DockerLogs, DockerConnect, diff --git a/src/docker_data/message.rs b/src/docker_data/message.rs index 866aada..1f60261 100644 --- a/src/docker_data/message.rs +++ b/src/docker_data/message.rs @@ -1,19 +1,21 @@ use std::sync::Arc; -use crate::app_data::ContainerId; +use crate::app_data::{ContainerId, DockerCommand}; use bollard::Docker; use tokio::sync::oneshot::Sender; #[derive(Debug)] pub enum DockerMessage { ConfirmDelete(ContainerId), - Delete(ContainerId), + Control((DockerCommand, ContainerId)), + + // Delete(ContainerId), Exec(Sender>), - Pause(ContainerId), + // Pause(ContainerId), Quit, - Restart(ContainerId), - Start(ContainerId), - Stop(ContainerId), - Resume(ContainerId), + // Restart(ContainerId), + // Start(ContainerId), + // Stop(ContainerId), + // Resume(ContainerId), Update, } diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index b97102c..b173cae 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -22,7 +22,7 @@ use tokio::{ use uuid::Uuid; use crate::{ - app_data::{AppData, ContainerId, ContainerStatus, DockerControls, State}, + app_data::{AppData, ContainerId, ContainerStatus, DockerCommand, State}, app_error::AppError, parse_args::CliArgs, ui::{GuiState, Status}, @@ -340,7 +340,7 @@ impl DockerData { /// Set the global error as the docker error, and set gui_state to error fn set_error( app_data: &Arc>, - error: DockerControls, + error: DockerCommand, gui_state: &Arc>, ) { app_data @@ -348,99 +348,60 @@ impl DockerData { .set_error(AppError::DockerCommand(error), gui_state, Status::Error); } + /// Execute docker comamnds (start, stop etc) on it's own tokio thread + async fn execute_command(&mut self, control: DockerCommand, id: ContainerId) { + let (app_data, docker, gui_state) = ( + Arc::clone(&self.app_data), + Arc::clone(&self.docker), + Arc::clone(&self.gui_state), + ); + tokio::spawn(async move { + let uuid = Uuid::new_v4(); + GuiState::start_loading_animation(&gui_state, uuid); + if match control { + DockerCommand::Delete => { + docker + .remove_container( + id.get(), + Some(RemoveContainerOptions { + v: false, + force: true, + link: false, + }), + ) + .await + } + DockerCommand::Pause => docker.pause_container(id.get()).await, + DockerCommand::Restart => docker.restart_container(id.get(), None).await, + DockerCommand::Resume => docker.unpause_container(id.get()).await, + DockerCommand::Start => { + docker + .start_container(id.get(), None::>) + .await + } + DockerCommand::Stop => docker.stop_container(id.get(), None).await, + } + .is_err() + { + Self::set_error(&app_data, control, &gui_state); + } + gui_state.lock().stop_loading_animation(uuid); + }); + self.update_everything().await; + } + /// Handle incoming messages, container controls & all container information update /// Spawn Docker commands off into own thread - #[allow(clippy::too_many_lines)] async fn message_handler(&mut self) { while let Some(message) = self.receiver.recv().await { - let docker = Arc::clone(&self.docker); - let gui_state = Arc::clone(&self.gui_state); - let app_data = Arc::clone(&self.app_data); - let uuid = Uuid::new_v4(); - // TODO need to refactor these match message { - DockerMessage::Exec(docker_tx) => { - docker_tx.send(Arc::clone(&self.docker)).ok(); - } - DockerMessage::Pause(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker.pause_container(id.get()).await.is_err() { - Self::set_error(&app_data, DockerControls::Pause, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - } - DockerMessage::Restart(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker.restart_container(id.get(), None).await.is_err() { - Self::set_error(&app_data, DockerControls::Restart, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - } - DockerMessage::Start(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker - .start_container(id.get(), None::>) - .await - .is_err() - { - Self::set_error(&app_data, DockerControls::Start, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - } - DockerMessage::Stop(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker.stop_container(id.get(), None).await.is_err() { - Self::set_error(&app_data, DockerControls::Stop, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - } - DockerMessage::Resume(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker.unpause_container(id.get()).await.is_err() { - Self::set_error(&app_data, DockerControls::Resume, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - } - DockerMessage::Delete(id) => { - tokio::spawn(async move { - GuiState::start_loading_animation(&gui_state, uuid); - if docker - .remove_container( - id.get(), - Some(RemoveContainerOptions { - v: false, - force: true, - link: false, - }), - ) - .await - .is_err() - { - Self::set_error(&app_data, DockerControls::Stop, &gui_state); - } - gui_state.lock().stop_loading_animation(uuid); - }); - self.update_everything().await; - self.gui_state.lock().set_delete_container(None); - } DockerMessage::ConfirmDelete(id) => { self.gui_state.lock().set_delete_container(Some(id)); } + DockerMessage::Control((command, id)) => self.execute_command(command, id).await, + DockerMessage::Exec(docker_tx) => { + docker_tx.send(Arc::clone(&self.docker)).ok(); + } DockerMessage::Update => self.update_everything().await, DockerMessage::Quit => { self.spawns diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 8228f59..9acefb8 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -22,7 +22,7 @@ use uuid::Uuid; mod message; use crate::{ - app_data::{AppData, DockerControls, Header}, + app_data::{AppData, DockerCommand, Header}, app_error::AppError, docker_data::DockerMessage, exec::{tty_readable, ExecMode}, @@ -112,7 +112,10 @@ impl InputHandler { async fn confirm_delete(&self) { let id = self.gui_state.lock().get_delete_container(); if let Some(id) = id { - self.docker_tx.send(DockerMessage::Delete(id)).await.ok(); + self.docker_tx + .send(DockerMessage::Control((DockerCommand::Delete, id))) + .await + .ok(); } } @@ -281,26 +284,17 @@ impl InputHandler { let option_id = self.app_data.lock().get_selected_container_id(); if let Some(id) = option_id { match command { - DockerControls::Delete => self + DockerCommand::Delete => self .docker_tx .send(DockerMessage::ConfirmDelete(id)) .await .ok(), - DockerControls::Pause => { - self.docker_tx.send(DockerMessage::Pause(id)).await.ok() - } - DockerControls::Resume => { - self.docker_tx.send(DockerMessage::Resume(id)).await.ok() - } - DockerControls::Start => { - self.docker_tx.send(DockerMessage::Start(id)).await.ok() - } - DockerControls::Stop => { - self.docker_tx.send(DockerMessage::Stop(id)).await.ok() - } - DockerControls::Restart => { - self.docker_tx.send(DockerMessage::Restart(id)).await.ok() - } + + _ => self + .docker_tx + .send(DockerMessage::Control((command, id))) + .await + .ok(), }; } }