use std::collections::VecDeque;
use crate::install::InstallError;
use crate::install::InstallSteps;
use crate::install::RollbackError;
use crate::install::RollbackStep;
use crate::install::StepOptions;
use crate::Tense;
use dialoguer::Confirm;
use dialoguer::Select;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("canceled by the user")]
Canceled,
#[error("could not get input from the user")]
UserInputFailed(
#[from]
#[source]
dialoguer::Error,
),
#[error("ran into one or more errors and user chose to abort, errors: {0:#?}")]
AbortedAfterError(Vec<InstallError>),
#[error("user chose to cancel and rollback however rollback failed")]
RollbackFollowingCancel(#[source] RollbackError),
#[error("ran into error user chose to abort and rollback however rollback failed")]
RollbackFollowingError(#[source] RollbackError),
}
pub fn start(steps: InstallSteps, detailed: bool) -> Result<(), Error> {
let mut errors = Vec::new();
let mut rollback_steps = VecDeque::new();
for mut step in steps {
if detailed {
println!("{}", step.describe_detailed(Tense::Questioning));
} else {
println!("{}", step.describe(Tense::Questioning));
}
match step.options() {
Some(StepOptions::YesOrAbort) => {
if !Confirm::new().interact()? {
rollback_if_user_wants_to(rollback_steps)?;
return Err(Error::Canceled);
}
}
None => (),
}
match step.perform() {
Ok(None) => (),
Ok(Some(rollback)) => rollback_steps.push_front(rollback),
Err(e) => {
let details = format_error_chain(&e).replace('\n', "\n\t");
errors.push(e);
println!("An error occurred, details:\n\t{details}\t");
match Select::new()
.with_prompt("What do you want to do?")
.items(&["rollback and abort", "abort", "continue"])
.default(0)
.interact()?
{
2 => continue,
0 => rollback(rollback_steps).map_err(Error::RollbackFollowingError)?,
_ => (),
}
return Err(Error::AbortedAfterError(errors));
}
}
}
Ok(())
}
fn rollback_if_user_wants_to(rollback_steps: VecDeque<Box<dyn RollbackStep>>) -> Result<(), Error> {
if rollback_steps.is_empty() {
println!("Install aborted, no changes have been made");
} else if Confirm::new()
.with_prompt("Install aborted, do you want to roll back any changes made?")
.interact()?
{
rollback(rollback_steps).map_err(Error::RollbackFollowingCancel)?;
}
Ok(())
}
fn rollback(mut rollback_steps: VecDeque<Box<dyn RollbackStep>>) -> Result<(), RollbackError> {
for step in &mut rollback_steps {
let did = step.describe(Tense::Past);
step.perform()?;
println!("{did}");
}
Ok(())
}
fn format_error_chain(root: &dyn std::error::Error) -> String {
let mut res = format!("1 {root}\n");
let mut i = 2;
let mut curr = root;
while let Some(next) = curr.source() {
res.push_str(&format!("{i} {next}\n"));
curr = next;
i += 1;
}
res
}