mod ui;
use std::{
collections::HashMap,
sync::mpsc::{Receiver, RecvTimeoutError},
};
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
use crate::{
buildkit::{BuildKitD, container_info::SCellContainerInfo},
cli::{MIN_FPS, terminal::Terminal},
};
pub enum App {
Loading {
rx: Receiver<color_eyre::Result<Vec<SCellContainerInfo>>>,
buildkit: BuildKitD,
},
Stopping(StoppingState),
Exit,
}
impl App {
pub fn run(
buildkit: &BuildKitD,
terminal: &mut Terminal,
) -> color_eyre::Result<()> {
let mut app = Self::loading(buildkit.clone());
loop {
if let App::Loading {
ref rx,
ref buildkit,
} = app
&& let Ok(result) = rx.recv_timeout(MIN_FPS)
{
let containers = result?;
app = Self::stopping(containers, buildkit.clone());
}
if let App::Stopping(ref mut state) = app
&& state.try_update()
{
app = App::Exit;
}
if matches!(app, App::Exit) {
return Ok(());
}
terminal.draw(|f| {
f.render_widget(&app, f.area());
})?;
app = app.handle_key_event()?;
}
}
fn loading(buildkit: BuildKitD) -> Self {
let (tx, rx) = std::sync::mpsc::channel();
tokio::spawn({
let buildkit = buildkit.clone();
async move {
let result = async {
let res = buildkit.list_containers().await;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
res
}
.await;
drop(tx.send(result));
}
});
App::Loading { rx, buildkit }
}
fn stopping(
containers: Vec<SCellContainerInfo>,
buildkit: BuildKitD,
) -> Self {
let (tx, rx) = std::sync::mpsc::channel();
tokio::spawn({
let containers = containers.clone();
async move {
for c in containers {
let res = buildkit.stop_container(&c).await;
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
drop(tx.send((c, res)));
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
});
App::Stopping(StoppingState::new(containers, rx))
}
fn handle_key_event(mut self) -> color_eyre::Result<Self> {
if event::poll(MIN_FPS)?
&& let Event::Key(key) = event::read()?
&& key.kind == KeyEventKind::Press
&& let KeyCode::Char('c' | 'd') = key.code
&& key.modifiers.contains(event::KeyModifiers::CONTROL)
{
self = App::Exit;
}
Ok(self)
}
}
pub struct StoppingState {
containers: HashMap<SCellContainerInfo, Option<color_eyre::Result<()>>>,
rx: Receiver<(SCellContainerInfo, color_eyre::Result<()>)>,
}
impl StoppingState {
pub fn new(
containers: Vec<SCellContainerInfo>,
rx: Receiver<(SCellContainerInfo, color_eyre::Result<()>)>,
) -> Self {
Self {
containers: containers.into_iter().map(|c| (c, None)).collect(),
rx,
}
}
fn try_update(&mut self) -> bool {
match self.rx.recv_timeout(MIN_FPS) {
Ok(update) => {
self.containers.insert(update.0, Some(update.1));
false
},
Err(RecvTimeoutError::Timeout) => false,
Err(RecvTimeoutError::Disconnected) => true,
}
}
}