diff --git a/rust/agama-cli/src/progress.rs b/rust/agama-cli/src/progress.rs index d66f244724..d9b42ba9a3 100644 --- a/rust/agama-cli/src/progress.rs +++ b/rust/agama-cli/src/progress.rs @@ -19,16 +19,14 @@ // find current contact information at www.suse.com. use agama_lib::monitor::MonitorClient; -use agama_utils::api; -use console::style; +use agama_utils::api::{self, Scope}; use indicatif::{ProgressBar, ProgressStyle}; -use std::time::Duration; +use std::{collections::HashMap, time::Duration}; /// Displays the progress on the terminal. +#[derive(Debug)] pub struct ProgressMonitor { monitor: MonitorClient, - bar: Option, - current_step: u32, running: bool, stop_on_idle: bool, } @@ -40,8 +38,6 @@ impl ProgressMonitor { pub fn new(monitor: MonitorClient) -> Self { Self { monitor, - bar: None, - current_step: 0, running: true, stop_on_idle: true, } @@ -57,16 +53,43 @@ impl ProgressMonitor { pub async fn run(&mut self) -> anyhow::Result<()> { let mut updates = self.monitor.subscribe(); let status = self.monitor.get_status().await?; - self.update(status).await; + self.update(&status).await; if !self.running { return Ok(()); } + let multibar = indicatif::MultiProgress::new(); + let mut bars: HashMap = HashMap::new(); + multibar.println("Installaton Tasks:")?; loop { if let Ok(status) = updates.recv().await { - if !self.update(status).await { + if !self.update(&status).await { return Ok(()); } + + let mut active_scopes = vec![]; + for progress in status.progresses { + active_scopes.push(progress.scope); + let bar = bars.entry(progress.scope).or_insert_with(|| { + let style = ProgressStyle::with_template("{spinner:.green} {msg}").unwrap(); + let new_bar = ProgressBar::new(progress.size as u64).with_style(style); + new_bar.enable_steady_tick(Duration::from_millis(120)); + multibar.add(new_bar) + }); + bar.set_message(progress.step); + bar.set_position(progress.index as u64); + } + // and finish all that no longer have progress + let mut to_remove = vec![]; + for (scope, bar) in &bars { + if !active_scopes.contains(&scope) { + bar.finish_with_message("done"); + to_remove.push(scope.clone()); + } + } + for scope in to_remove { + bars.remove(&scope); + } } } } @@ -74,7 +97,7 @@ impl ProgressMonitor { /// Updates the progress. /// /// It returns true if the monitor should continue. - async fn update(&mut self, status: api::Status) -> bool { + async fn update(&mut self, status: &api::Status) -> bool { if status.progresses.is_empty() && self.running { self.finish(); if self.stop_on_idle { @@ -82,16 +105,11 @@ impl ProgressMonitor { } } - // TODO: adapt to multi progresses - true } /// Stops the representation. fn finish(&mut self) { self.running = false; - if let Some(bar) = self.bar.take() { - bar.finish_with_message("Done"); - } } } diff --git a/rust/agama-lib/src/monitor.rs b/rust/agama-lib/src/monitor.rs index dd1134b12d..b66611f12d 100644 --- a/rust/agama-lib/src/monitor.rs +++ b/rust/agama-lib/src/monitor.rs @@ -68,7 +68,7 @@ pub enum MonitorError { /// It allows connecting to the Agama monitor to get the status or listen for changes. /// /// It can be cloned and moved between threads. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MonitorClient { commands: mpsc::Sender, pub updates: broadcast::Sender, diff --git a/rust/agama-software/src/callbacks/commit_download.rs b/rust/agama-software/src/callbacks/commit_download.rs index 1d87a99f96..6cc39a40fb 100644 --- a/rust/agama-software/src/callbacks/commit_download.rs +++ b/rust/agama-software/src/callbacks/commit_download.rs @@ -119,17 +119,10 @@ impl Callback for CommitDownload { .progress .cast(progress::message::NextWithStep::new(Scope::Software, &msg)); } else { - let labels = [gettext("Ok")]; - let actions = [("Ok", labels[0].as_str())]; - let question = - QuestionSpec::new(&error_details, "software.package_error.preload_error") - .with_actions(&actions) - .with_data(&[ - ("package", file_str), - ("error_code", error.to_string().as_str()), - ]); - // answer can be only OK so ignore it - let _ = ask_software_question(&self.questions, question); + // just log that download failed as libzypp will automatically use next mirror + // so we should not bother user. But also do not update progress otherwise it will + // mess us steps. + tracing::info!("preload failed with {:?}", error_details); } } } diff --git a/rust/agama-utils/src/api/proposal.rs b/rust/agama-utils/src/api/proposal.rs index 1dbb13eb88..348eb6f8a3 100644 --- a/rust/agama-utils/src/api/proposal.rs +++ b/rust/agama-utils/src/api/proposal.rs @@ -19,10 +19,10 @@ // find current contact information at www.suse.com. use crate::api::{l10n, network, software}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; -#[derive(Clone, Debug, Serialize, utoipa::ToSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub struct Proposal { #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/agama-utils/src/api/software/proposal.rs b/rust/agama-utils/src/api/software/proposal.rs index aec0bd1dca..637805a195 100644 --- a/rust/agama-utils/src/api/software/proposal.rs +++ b/rust/agama-utils/src/api/software/proposal.rs @@ -18,11 +18,11 @@ // To contact SUSE LLC about this file by physical or electronic mail, you may // find current contact information at www.suse.com. -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Represents the reason why a pattern is selected. -#[derive(Clone, Copy, Debug, PartialEq, Serialize, utoipa::ToSchema)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub enum SelectedBy { /// The pattern was selected by the user. @@ -34,7 +34,7 @@ pub enum SelectedBy { } /// Software proposal information. -#[derive(Clone, Debug, Serialize, utoipa::ToSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub struct SoftwareProposal { /// Space required for installation in KiB. @@ -45,7 +45,7 @@ pub struct SoftwareProposal { } /// Describes what Agama proposes for the target system. -#[derive(Clone, Default, Debug, Serialize, utoipa::ToSchema)] +#[derive(Clone, Default, Debug, Deserialize, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub struct Proposal { /// Software specific proposal