use std::time::{Duration, Instant};
use super::settings::Settings;
use super::store::{Connection, Store};
use crate::docker::{Starter, starter};
use crate::worker::{Request, Worker};
const PATIENCE: Duration = Duration::from_secs(180);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Offer {
Nothing,
Start(String),
Starting(String),
Failed { name: String, error: String },
}
#[derive(Default)]
pub struct EngineStarter {
started: Option<(Instant, String)>,
error: Option<String>,
tried_at_launch: bool,
provider_checked: bool,
}
fn host(store: &Store) -> Option<&str> {
let name = store.engine.as_deref()?;
store
.engines
.iter()
.find(|e| e.name == name)
.map(|e| e.host.as_str())
}
impl EngineStarter {
fn starter(store: &Store, settings: &Settings) -> Option<Starter> {
starter(host(store)?, settings.engine_app.as_deref())
}
pub fn start(&mut self, store: &Store, settings: &Settings, worker: &Worker) {
if let Some(starter) = Self::starter(store, settings) {
self.started = Some((Instant::now(), starter.name()));
self.error = None;
worker.send(Request::StartEngine(starter));
}
}
pub fn finished(&mut self, result: Result<(), String>) {
if let Err(e) = result {
self.started = None;
self.error = Some(e);
}
}
pub fn update(&mut self, store: &Store, settings: &Settings, worker: &Worker) {
match &store.connection {
Connection::Connected { .. } => {
self.started = None;
self.error = None;
self.tried_at_launch = true;
if !self.provider_checked {
self.provider_checked = true;
if let Some(host) = host(store) {
worker.send(Request::FindProvider(host.to_owned()));
}
}
}
Connection::Disconnected { reason } => {
if !self.tried_at_launch {
self.tried_at_launch = true;
if settings.start_engine && super::shell::is_not_running(reason) {
self.start(store, settings, worker);
}
}
if let Some((since, name)) = &self.started
&& since.elapsed() > PATIENCE
{
self.error = Some(format!("{name} didn't answer after 3 minutes"));
self.started = None;
}
}
Connection::Connecting => {}
}
}
pub fn engine_switched(&mut self) {
self.provider_checked = false;
self.started = None;
self.error = None;
}
pub fn offer(&self, store: &Store, settings: &Settings) -> Offer {
let name = || {
Self::starter(store, settings)
.map(|s| s.name())
.unwrap_or_else(|| "Docker".into())
};
if let Some((_, name)) = &self.started {
return Offer::Starting(name.clone());
}
if let Some(error) = &self.error {
return Offer::Failed {
name: name(),
error: error.clone(),
};
}
match Self::starter(store, settings) {
Some(starter) => Offer::Start(starter.name()),
None => Offer::Nothing,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failed_start_is_offered_again() {
let mut starter = EngineStarter {
started: Some((Instant::now(), "Colima".into())),
..Default::default()
};
let store = Store::new();
let settings = Settings::default();
assert_eq!(
starter.offer(&store, &settings),
Offer::Starting("Colima".into())
);
starter.finished(Err("no VM".into()));
assert!(matches!(
starter.offer(&store, &settings),
Offer::Failed { error, .. } if error == "no VM"
));
}
}