From 1587dfb4fff9da0a4d3e6da9b9aede16da9f8f8f Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 29 Jun 2024 08:52:23 +0100 Subject: [PATCH] fix: stop and delete containers in tokio task --- src/callbacks/delete_container.rs | 30 +++++++++++++++++-- src/docker/container.rs | 17 ++++++++--- src/events/message.rs | 1 + src/main.rs | 4 +++ src/pages/containers.rs | 50 +++++++++++++++++++++++-------- src/ui/app.rs | 7 +---- 6 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/callbacks/delete_container.rs b/src/callbacks/delete_container.rs index f1a74c2..83f5995 100644 --- a/src/callbacks/delete_container.rs +++ b/src/callbacks/delete_container.rs @@ -1,27 +1,51 @@ -use crate::{docker::container::DockerContainer, traits::Callback}; +use crate::{ + docker::container::DockerContainer, + events::{Key, Message, Transition}, + traits::Callback, +}; use async_trait::async_trait; use color_eyre::eyre::Result; +use tokio::sync::mpsc::Sender; #[derive(Debug)] pub struct DeleteContainer { docker: bollard::Docker, container: DockerContainer, force: bool, + tx: Sender>, } impl DeleteContainer { - pub fn new(docker: bollard::Docker, container: DockerContainer, force: bool) -> Self { + pub fn new( + docker: bollard::Docker, + container: DockerContainer, + force: bool, + tx: Sender>, + ) -> Self { Self { docker, container, force, + tx, } } } #[async_trait] impl Callback for DeleteContainer { async fn call(&self) -> Result<()> { - let _ = self.container.delete(&self.docker, self.force).await?; + let container = self.container.clone(); + let docker = self.docker.clone(); + let force = self.force; + let tx = self.tx.clone(); + tokio::spawn(async move { + let message = if container.delete(&docker, force).await.is_ok() { + Message::Tick + } else { + let msg = format!("Failed to delete container {}", container.id); + Message::Error(msg) + }; + let _ = tx.send(message).await; + }); Ok(()) } } diff --git a/src/docker/container.rs b/src/docker/container.rs index c86e087..096d565 100644 --- a/src/docker/container.rs +++ b/src/docker/container.rs @@ -51,6 +51,15 @@ impl DockerContainer { let running = matches!(c.state.unwrap_or_default().as_str(), "running"); + let names = c + .names + .clone() + .unwrap_or_default() + .into_iter() + .map(|n| n.strip_prefix('/').unwrap_or_default().into()) + .collect::>() + .join(", "); + Self { id: c.id.clone().unwrap_or_default(), image: c.image.clone().unwrap_or_default(), @@ -58,13 +67,13 @@ impl DockerContainer { created: datetime, status: c.status.clone().unwrap_or_default(), ports, - names: c.names.clone().unwrap_or_default().join(", "), + names, running, } } pub async fn list(docker: &bollard::Docker) -> Result> { - let containrs = docker + let containers = docker .list_containers(Some(ListContainersOptions:: { all: true, ..Default::default() @@ -75,7 +84,7 @@ impl DockerContainer { .map(Self::from) .collect(); - Ok(containrs) + Ok(containers) } pub async fn delete(&self, docker: &bollard::Docker, force: bool) -> Result<()> { @@ -100,7 +109,7 @@ impl DockerContainer { docker .stop_container(&self.id, None) .await - .context("failed to start container")?; + .context("failed to stop container")?; Ok(()) } diff --git a/src/events/message.rs b/src/events/message.rs index d1dfe88..23b9ccc 100644 --- a/src/events/message.rs +++ b/src/events/message.rs @@ -3,6 +3,7 @@ pub enum Message { Tick, Input(I), Transition(T), + Error(String), } #[derive(PartialEq, Debug, Clone)] diff --git a/src/main.rs b/src/main.rs index 7d7995f..ece59a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,6 +124,10 @@ async fn main() -> color_eyre::Result<()> { Message::Tick => { app.update(Key::Null).await; } + + Message::Error(_) => { + // This needs implementing + } } } diff --git a/src/pages/containers.rs b/src/pages/containers.rs index fc683e3..0af5fc0 100644 --- a/src/pages/containers.rs +++ b/src/pages/containers.rs @@ -8,7 +8,10 @@ use ratatui::{ widgets::{Row, Table, TableState}, Frame, }; -use std::sync::{Arc, Mutex}; +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; use tokio::sync::mpsc::Sender; use crate::{ @@ -55,6 +58,7 @@ pub struct Containers { containers: Vec, list_state: TableState, modal: Option>, + stopping_containers: Arc>>, } #[async_trait::async_trait] @@ -197,6 +201,7 @@ impl Containers { containers: vec![], list_state: TableState::default(), modal: None, + stopping_containers: Arc::new(Mutex::new(HashSet::new())), } } @@ -249,7 +254,27 @@ impl Containers { async fn stop_container(&mut self) -> Result> { if let Ok(container) = self.get_container() { - container.stop(&self.docker).await?; + self.stopping_containers + .lock() + .unwrap() + .insert(container.id.clone()); + + let c = container.clone(); + let docker = self.docker.clone(); + let tx = self.tx.clone(); + let stopping_containers = self.stopping_containers.clone(); + tokio::spawn(async move { + let message = if c.stop(&docker).await.is_ok() { + Message::Tick + } else { + let msg = format!("Failed to delete container {}", c.id); + Message::Error(msg) + }; + stopping_containers.lock().unwrap().remove(&c.id); + let _ = tx.send(message).await; + }); + + // Second spawned taskt is used to update the state self.refresh().await?; return Ok(Some(())); @@ -262,19 +287,17 @@ impl Containers { let name = container.names.clone(); let image = container.image.clone(); - let message = match container.running { - true => { - format!("Are you sure you wish to delete container {name} (image = {image})? This container is currently running; this will result in a force deletion.") - } - false => { - format!("Are you sure you wish to delete container {name} (image = {image})?") - } + let message = if container.running { + format!("Are you sure you wish to delete container {name} (image = {image})? This container is currently running; this will result in a force deletion.") + } else { + format!("Are you sure you wish to delete container {name} (image = {image})?") }; let cb = Arc::new(FutureMutex::new(DeleteContainer::new( self.docker.clone(), container.clone(), container.running, + self.tx.clone(), ))); let mut modal = @@ -291,9 +314,12 @@ impl Containers { impl Component for Containers { fn draw(&mut self, f: &mut Frame<'_>, area: Rect) { let rows = self.containers.clone().into_iter().map(|c| { - let style = match c.running { - true => Style::default().fg(self.config.theme.positive_highlight()), - false => Style::default(), + let style = if self.stopping_containers.lock().unwrap().contains(&c.id) { + Style::default().fg(self.config.theme.negative_highlight()) + } else if c.running { + Style::default().fg(self.config.theme.positive_highlight()) + } else { + Style::default() }; Row::new(vec![ diff --git a/src/ui/app.rs b/src/ui/app.rs index 73c0124..f19c0d1 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -123,12 +123,7 @@ impl App { } async fn update_view_mode(&mut self, message: Key) -> Result { - if let MessageResponse::Consumed = self - .page_manager - .update(message) - .await - .context("unable to update body")? - { + if let MessageResponse::Consumed = self.page_manager.update(message).await? { return Ok(MessageResponse::Consumed); }