From 9f5fee194ed5b5eb3657eca8ee68a8c8c15898c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 13:52:00 +0000 Subject: [PATCH 01/16] Move status tracking to the progress service * Probably it should be a new agama-status service. --- rust/agama-manager/src/service.rs | 14 +------ rust/agama-utils/src/api/status.rs | 5 ++- rust/agama-utils/src/progress/message.rs | 7 ++++ rust/agama-utils/src/progress/service.rs | 51 +++++++++++++++++++----- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 522b12e7f0..8bf063017a 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -233,8 +233,6 @@ impl Starter { products: products::Registry::default(), licenses: licenses::Registry::default(), hardware, - // FIXME: state is already used for service state. - state: State::Configuring, config: Config::default(), system: manager::SystemInfo::default(), product: None, @@ -258,7 +256,6 @@ pub struct Service { licenses: licenses::Registry, hardware: hardware::Registry, product: Option>>, - state: State, config: Config, system: manager::SystemInfo, events: event::Sender, @@ -425,8 +422,6 @@ impl Service { } async fn install(&mut self) -> Result<(), Error> { - self.state = State::Installing; - self.events.send(Event::StateChanged)?; // TODO: translate progress steps. self.progress .call(progress::message::StartWithSteps::new( @@ -438,8 +433,6 @@ impl Service { self.progress .call(progress::message::Finish::new(Scope::Manager)) .await?; - self.state = State::Finished; - self.events.send(Event::StateChanged)?; Ok(()) } @@ -490,11 +483,8 @@ impl Actor for Service { impl MessageHandler for Service { /// It returns the status of the installation. async fn handle(&mut self, _message: message::GetStatus) -> Result { - let progresses = self.progress.call(progress::message::Get).await?; - Ok(Status { - state: self.state.clone(), - progresses, - }) + let status = self.progress.call(progress::message::GetStatus).await?; + Ok(status) } } diff --git a/rust/agama-utils/src/api/status.rs b/rust/agama-utils/src/api/status.rs index 156560c942..206a3541c7 100644 --- a/rust/agama-utils/src/api/status.rs +++ b/rust/agama-utils/src/api/status.rs @@ -22,7 +22,7 @@ use crate::api::progress::Progress; use serde::Serialize; // Information about the status of the installation. -#[derive(Serialize, utoipa::ToSchema)] +#[derive(Clone, Default, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub struct Status { /// State of the installation @@ -32,9 +32,10 @@ pub struct Status { } /// Represents the current state of the installation process. -#[derive(Clone, Serialize, utoipa::ToSchema)] +#[derive(Clone, Copy, Default, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub enum State { + #[default] /// Configuring the installation Configuring, /// Installing the system diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index 2ca5b8e5db..0917c40f6f 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -21,6 +21,13 @@ use crate::actor::Message; use crate::api::progress::Progress; use crate::api::scope::Scope; +use crate::api::Status; + +pub struct GetStatus; + +impl Message for GetStatus { + type Reply = Status; +} pub struct Get; diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index 791300987c..7d57eb335a 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -19,6 +19,7 @@ // find current contact information at www.suse.com. use crate::actor::{self, Actor, Handler, MessageHandler}; +use crate::api::Status; use crate::{ api::event::{self, Event}, api::progress::{self, Progress}, @@ -57,7 +58,7 @@ impl Starter { pub fn start(self) -> Handler { let service = Service { events: self.event, - progresses: vec![], + status: Status::default(), }; let handler = actor::spawn(service); @@ -67,7 +68,7 @@ impl Starter { pub struct Service { events: event::Sender, - progresses: Vec, + status: Status, } impl Service { @@ -75,16 +76,37 @@ impl Service { Starter::new(events) } + fn get_status(&self) -> &Status { + &self.status + } + + // NOTE: this method might be implemented by Status. + fn get_progresses(&mut self) -> &Vec { + &self.status.progresses + } + + fn add_progress(&mut self, progress: Progress) { + self.status.progresses.push(progress); + } + + fn update_progress(&mut self, index: usize, progress: Progress) { + self.status.progresses[index] = progress; + } + fn get_progress(&self, scope: Scope) -> Option<&Progress> { - self.progresses.iter().find(|p| p.scope == scope) + self.status.progresses.iter().find(|p| p.scope == scope) } fn get_mut_progress(&mut self, scope: Scope) -> Option<&mut Progress> { - self.progresses.iter_mut().find(|p| p.scope == scope) + self.status.progresses.iter_mut().find(|p| p.scope == scope) } fn get_progress_index(&self, scope: Scope) -> Option { - self.progresses.iter().position(|p| p.scope == scope) + self.status.progresses.iter().position(|p| p.scope == scope) + } + + fn remove_progress(&mut self, index: usize) { + self.status.progresses.remove(index); } fn send_progress_changed(&self, progress: Progress) -> Result<(), Error> { @@ -97,10 +119,17 @@ impl Actor for Service { type Error = Error; } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::GetStatus) -> Result { + Ok(self.get_status().clone()) + } +} + #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, _message: message::Get) -> Result, Error> { - Ok(self.progresses.clone()) + Ok(self.get_progresses().clone()) } } @@ -109,9 +138,9 @@ impl MessageHandler for Service { async fn handle(&mut self, message: message::Set) -> Result<(), Error> { let progress = message.progress; if let Some(index) = self.get_progress_index(progress.scope) { - self.progresses[index] = progress.clone(); + self.update_progress(index, progress.clone()); } else { - self.progresses.push(progress.clone()); + self.add_progress(progress.clone()); } self.send_progress_changed(progress)?; Ok(()) @@ -125,7 +154,7 @@ impl MessageHandler for Service { return Err(Error::DuplicatedProgress(message.scope)); } let progress = Progress::new(message.scope, message.size, message.step); - self.progresses.push(progress.clone()); + self.add_progress(progress.clone()); self.send_progress_changed(progress)?; Ok(()) } @@ -138,7 +167,7 @@ impl MessageHandler for Service { return Err(Error::DuplicatedProgress(message.scope)); } let progress = Progress::new_with_steps(message.scope, message.steps); - self.progresses.push(progress.clone()); + self.add_progress(progress.clone()); self.send_progress_changed(progress)?; Ok(()) } @@ -176,7 +205,7 @@ impl MessageHandler for Service { let index = self .get_progress_index(message.scope) .ok_or(Error::MissingProgress(message.scope))?; - self.progresses.remove(index); + self.remove_progress(index); self.events.send(Event::ProgressFinished { scope: message.scope, })?; From 028b157df07fdd96f5ad826e64382fcf2dedb58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 13:54:55 +0000 Subject: [PATCH 02/16] Unify and forward progress GetStatus message --- rust/agama-manager/src/message.rs | 7 ------- rust/agama-manager/src/service.rs | 7 +++---- rust/agama-server/src/server/web.rs | 4 ++-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/rust/agama-manager/src/message.rs b/rust/agama-manager/src/message.rs index 6c839e5663..7d84979008 100644 --- a/rust/agama-manager/src/message.rs +++ b/rust/agama-manager/src/message.rs @@ -27,13 +27,6 @@ use agama_utils::{ }; use serde_json::Value; -/// Gets the installation status. -pub struct GetStatus; - -impl Message for GetStatus { - type Reply = Status; -} - /// Gets the information of the underlying system. #[derive(Debug)] pub struct GetSystem; diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 8bf063017a..efc94abb52 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -25,7 +25,6 @@ use agama_utils::{ self, event, files::scripts::ScriptsGroup, manager::{self, LicenseContent}, - status::State, Action, Config, Event, Issue, IssueMap, Proposal, Scope, Status, SystemInfo, }, issue, licenses, @@ -480,10 +479,10 @@ impl Actor for Service { } #[async_trait] -impl MessageHandler for Service { +impl MessageHandler for Service { /// It returns the status of the installation. - async fn handle(&mut self, _message: message::GetStatus) -> Result { - let status = self.progress.call(progress::message::GetStatus).await?; + async fn handle(&mut self, message: progress::message::GetStatus) -> Result { + let status = self.progress.call(message).await?; Ok(status) } } diff --git a/rust/agama-server/src/server/web.rs b/rust/agama-server/src/server/web.rs index c9201b70cd..a9ff8c6d48 100644 --- a/rust/agama-server/src/server/web.rs +++ b/rust/agama-server/src/server/web.rs @@ -33,7 +33,7 @@ use agama_utils::{ question::{Question, QuestionSpec, UpdateQuestion}, Action, Config, IssueWithScope, Patch, Status, SystemInfo, }, - question, + progress, question, }; use axum::{ extract::{Path, Query, State}, @@ -136,7 +136,7 @@ pub fn server_with_state(state: ServerState) -> Result { ) )] async fn get_status(State(state): State) -> ServerResult> { - let status = state.manager.call(message::GetStatus).await?; + let status = state.manager.call(progress::message::GetStatus).await?; Ok(Json(status)) } From 314db1a85139f3afafefa6494d4035a66f1c97ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 14:04:25 +0000 Subject: [PATCH 03/16] Implement an Install action * The workflow is not complete yet. --- rust/agama-manager/src/service.rs | 101 +++++++++++++++++++---- rust/agama-utils/src/progress/message.rs | 15 ++++ rust/agama-utils/src/progress/service.rs | 10 ++- 3 files changed, 109 insertions(+), 17 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index efc94abb52..e1d0f42cb0 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -25,6 +25,7 @@ use agama_utils::{ self, event, files::scripts::ScriptsGroup, manager::{self, LicenseContent}, + status::State, Action, Config, Event, Issue, IssueMap, Proposal, Scope, Status, SystemInfo, }, issue, licenses, @@ -420,21 +421,6 @@ impl Service { Ok(()) } - async fn install(&mut self) -> Result<(), Error> { - // TODO: translate progress steps. - self.progress - .call(progress::message::StartWithSteps::new( - Scope::Manager, - &["Installing l10n"], - )) - .await?; - self.l10n.call(l10n::message::Install).await?; - self.progress - .call(progress::message::Finish::new(Scope::Manager)) - .await?; - Ok(()) - } - fn set_product(&mut self, config: &Config) -> Result<(), Error> { self.product = None; self.update_product(config) @@ -623,7 +609,15 @@ impl MessageHandler for Service { self.probe_storage().await?; } Action::Install => { - self.install().await?; + let action = InstallAction { + l10n: self.l10n.clone(), + network: self.network.clone(), + software: self.software.clone(), + storage: self.storage.clone(), + files: self.files.clone(), + progress: self.progress.clone(), + }; + action.run(); } } Ok(()) @@ -672,3 +666,78 @@ impl MessageHandler for Service { Ok(()) } } + +struct InstallAction { + l10n: Handler, + network: NetworkSystemClient, + software: Handler, + storage: Handler, + files: Handler, + progress: Handler, +} + +impl InstallAction { + pub fn run(mut self) { + tokio::spawn(async move { + self.install().await.unwrap(); + }); + } + + async fn install(&mut self) -> Result<(), Error> { + // NOTE: consider a NextState message? + self.progress + .call(progress::message::SetState::new(State::Installing)) + .await?; + + // + // Preparation + // + self.progress + .call(progress::message::StartWithSteps::new( + Scope::Manager, + &[ + "Prepare the system", + "Install software", + "Configure the system", + ], + )) + .await?; + + self.storage.call(storage::message::Install).await?; + self.files + .call(files::message::RunScripts::new( + ScriptsGroup::PostPartitioning, + )) + .await?; + + // + // Installation + // + self.progress + .call(progress::message::Next::new(Scope::Manager)) + .await?; + self.software.call(software::message::Install).await?; + + // + // Configuration + // + self.progress + .call(progress::message::Next::new(Scope::Manager)) + .await?; + self.l10n.call(l10n::message::Install).await?; + self.software.call(software::message::Finish).await?; + self.storage.call(storage::message::Finish).await?; + + // + // Finish progress and changes + // + self.progress + .call(progress::message::Finish::new(Scope::Manager)) + .await?; + + self.progress + .call(progress::message::SetState::new(State::Finished)) + .await?; + Ok(()) + } +} diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index 0917c40f6f..e94efdaabb 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -21,6 +21,7 @@ use crate::actor::Message; use crate::api::progress::Progress; use crate::api::scope::Scope; +use crate::api::status::State; use crate::api::Status; pub struct GetStatus; @@ -132,3 +133,17 @@ impl Finish { impl Message for Finish { type Reply = (); } + +pub struct SetState { + pub state: State, +} + +impl SetState { + pub fn new(state: State) -> Self { + Self { state } + } +} + +impl Message for SetState { + type Reply = (); +} diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index 7d57eb335a..36a83e0e09 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -121,11 +121,19 @@ impl Actor for Service { #[async_trait] impl MessageHandler for Service { - async fn handle(&mut self, message: message::GetStatus) -> Result { + async fn handle(&mut self, _message: message::GetStatus) -> Result { Ok(self.get_status().clone()) } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, message: message::SetState) -> Result<(), Error> { + self.status.state = message.state; + Ok(()) + } +} + #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, _message: message::Get) -> Result, Error> { From 776273d6a68fe71302455c878f9bd660bc55933b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 14:06:08 +0000 Subject: [PATCH 04/16] Rename State to Stage --- rust/agama-manager/src/service.rs | 6 +++--- rust/agama-server/src/web/docs/config.rs | 2 +- rust/agama-utils/src/api/event.rs | 4 ++-- rust/agama-utils/src/api/status.rs | 6 +++--- rust/agama-utils/src/progress/message.rs | 14 +++++++------- rust/agama-utils/src/progress/service.rs | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index e1d0f42cb0..2670c3fb46 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -25,7 +25,7 @@ use agama_utils::{ self, event, files::scripts::ScriptsGroup, manager::{self, LicenseContent}, - status::State, + status::Stage, Action, Config, Event, Issue, IssueMap, Proposal, Scope, Status, SystemInfo, }, issue, licenses, @@ -686,7 +686,7 @@ impl InstallAction { async fn install(&mut self) -> Result<(), Error> { // NOTE: consider a NextState message? self.progress - .call(progress::message::SetState::new(State::Installing)) + .call(progress::message::SetStage::new(Stage::Installing)) .await?; // @@ -736,7 +736,7 @@ impl InstallAction { .await?; self.progress - .call(progress::message::SetState::new(State::Finished)) + .call(progress::message::SetStage::new(Stage::Finished)) .await?; Ok(()) } diff --git a/rust/agama-server/src/web/docs/config.rs b/rust/agama-server/src/web/docs/config.rs index 4b4342ea05..ab11952ca3 100644 --- a/rust/agama-server/src/web/docs/config.rs +++ b/rust/agama-server/src/web/docs/config.rs @@ -174,7 +174,7 @@ impl ApiDocBuilder for ConfigApiDocBuilder { .schema_from::() .schema_from::() .schema_from::() - .schema_from::() + .schema_from::() .schema_from::() .build() } diff --git a/rust/agama-utils/src/api/event.rs b/rust/agama-utils/src/api/event.rs index 3506038445..576bdfc206 100644 --- a/rust/agama-utils/src/api/event.rs +++ b/rust/agama-utils/src/api/event.rs @@ -26,8 +26,8 @@ use tokio::sync::broadcast; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Event { - // The state of the installation changed. - StateChanged, + // The stage of the installation changed. + StageChanged, /// Progress changed. ProgressChanged { progress: Progress, diff --git a/rust/agama-utils/src/api/status.rs b/rust/agama-utils/src/api/status.rs index 206a3541c7..ad8f849233 100644 --- a/rust/agama-utils/src/api/status.rs +++ b/rust/agama-utils/src/api/status.rs @@ -25,8 +25,8 @@ use serde::Serialize; #[derive(Clone, Default, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub struct Status { - /// State of the installation - pub state: State, + /// Stage of the installation + pub stage: Stage, /// Active progresses pub progresses: Vec, } @@ -34,7 +34,7 @@ pub struct Status { /// Represents the current state of the installation process. #[derive(Clone, Copy, Default, Serialize, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] -pub enum State { +pub enum Stage { #[default] /// Configuring the installation Configuring, diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index e94efdaabb..7025c4563d 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -21,7 +21,7 @@ use crate::actor::Message; use crate::api::progress::Progress; use crate::api::scope::Scope; -use crate::api::status::State; +use crate::api::status::Stage; use crate::api::Status; pub struct GetStatus; @@ -134,16 +134,16 @@ impl Message for Finish { type Reply = (); } -pub struct SetState { - pub state: State, +pub struct SetStage { + pub stage: Stage, } -impl SetState { - pub fn new(state: State) -> Self { - Self { state } +impl SetStage { + pub fn new(stage: Stage) -> Self { + Self { stage } } } -impl Message for SetState { +impl Message for SetStage { type Reply = (); } diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index 36a83e0e09..f6dfd969d5 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -127,9 +127,9 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::SetState) -> Result<(), Error> { - self.status.state = message.state; +impl MessageHandler for Service { + async fn handle(&mut self, message: message::SetStage) -> Result<(), Error> { + self.status.stage = message.stage; Ok(()) } } From 03737021432e4f1bf1ca571127171bad146c91a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 14:19:29 +0000 Subject: [PATCH 05/16] Emit the StageChanged event --- rust/agama-utils/src/progress/service.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index f6dfd969d5..bef6c1217d 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -130,6 +130,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { async fn handle(&mut self, message: message::SetStage) -> Result<(), Error> { self.status.stage = message.stage; + self.events.send(Event::StageChanged)?; Ok(()) } } From 6ea6c57068acabc88f3527c3169e1bc2e412b129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 14:33:58 +0000 Subject: [PATCH 06/16] Adapt Status to the changes in /api/v2/status --- web/src/App.tsx | 8 ++++---- web/src/model/status.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index e5c60f5b10..d41a455684 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -41,22 +41,22 @@ import { isEmpty } from "radashi"; const Content = () => { const location = useLocation(); const product = useProduct(); - const { progresses, state } = useStatus(); + const { progresses, stage } = useStatus(); const isBusy = !isEmpty(progresses); console.log("App Content component", { progresses, - state, + stage, product, location: location.pathname, }); - if (state === "installing") { + if (stage === "installing") { console.log("Navigating to the installation progress page"); return ; } - if (state === "finished") { + if (stage === "finished") { console.log("Navigating to the finished page"); return ; } diff --git a/web/src/model/status.ts b/web/src/model/status.ts index f6c3ec4be2..3bac8dfcd2 100644 --- a/web/src/model/status.ts +++ b/web/src/model/status.ts @@ -36,7 +36,7 @@ const fetchInstallerStatus = async (): Promise => { // TODO: remove export { fetchInstallerStatus }; -type State = "installing" | "configuring" | "finished"; +type Stage = "installing" | "configuring" | "finished"; type Scope = "manager" | "l10n" | "product" | "software" | "storage" | "iscsci" | "users"; type Progress = { index: number; @@ -47,8 +47,8 @@ type Progress = { }; type Status = { - state: State; + stage: Stage; progresses: Progress[]; }; -export type { Status, State, Scope, Progress }; +export type { Status, Stage, Scope, Progress }; From 6fb77c3f834f16f6636c0e3897f702687414a752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 14:57:42 +0000 Subject: [PATCH 07/16] Rename progress service actions --- rust/agama-storage/src/monitor.rs | 2 +- rust/agama-utils/src/progress.rs | 26 ++++++++++++------------ rust/agama-utils/src/progress/message.rs | 10 ++++----- rust/agama-utils/src/progress/service.rs | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/rust/agama-storage/src/monitor.rs b/rust/agama-storage/src/monitor.rs index 5a5b623e14..1b293bbece 100644 --- a/rust/agama-storage/src/monitor.rs +++ b/rust/agama-storage/src/monitor.rs @@ -183,7 +183,7 @@ impl Monitor { return Err(Error::ProgressChangedData); }; self.progress - .cast(progress::message::Set::new(progress_data.into()))?; + .cast(progress::message::SetProgress::new(progress_data.into()))?; Ok(()) } diff --git a/rust/agama-utils/src/progress.rs b/rust/agama-utils/src/progress.rs index f958b10286..b43e29db25 100644 --- a/rust/agama-utils/src/progress.rs +++ b/rust/agama-utils/src/progress.rs @@ -68,7 +68,7 @@ mod tests { assert_eq!(event_progress.step, "first step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::Get).await?; + let progresses = handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); @@ -82,7 +82,7 @@ mod tests { let event = receiver.recv().await.unwrap(); assert!(matches!(event, Event::ProgressChanged { progress: _ })); - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -96,7 +96,7 @@ mod tests { let event = receiver.recv().await.unwrap(); assert!(matches!(event, Event::ProgressChanged { progress: _ })); - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -113,7 +113,7 @@ mod tests { Event::ProgressFinished { scope: Scope::L10n } )); - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); assert!(progresses.is_empty()); Ok(()) @@ -125,7 +125,7 @@ mod tests { // Set first progress. let progress = Progress::new(Scope::Storage, 3, "first step".to_string()); - handler.call(message::Set::new(progress)).await?; + handler.call(message::SetProgress::new(progress)).await?; let event = receiver.recv().await.unwrap(); let Event::ProgressChanged { @@ -141,7 +141,7 @@ mod tests { assert_eq!(event_progress.step, "first step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::Get).await?; + let progresses = handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); @@ -149,7 +149,7 @@ mod tests { // Set second progress let progress = Progress::new(Scope::Storage, 3, "second step".to_string()); - handler.call(message::Set::new(progress)).await?; + handler.call(message::SetProgress::new(progress)).await?; let event = receiver.recv().await.unwrap(); let Event::ProgressChanged { @@ -165,7 +165,7 @@ mod tests { assert_eq!(event_progress.step, "second step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::Get).await?; + let progresses = handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); @@ -186,7 +186,7 @@ mod tests { )) .await?; - let progresses = handler.call(message::Get).await?; + let progresses = handler.call(message::GetProgress).await?; let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -200,7 +200,7 @@ mod tests { // Second step handler.call(message::Next::new(Scope::L10n)).await?; - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.step, "second step"); assert_eq!(progress.index, 2); @@ -208,7 +208,7 @@ mod tests { // Third step handler.call(message::Next::new(Scope::L10n)).await?; - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.step, "third step"); assert_eq!(progress.index, 3); @@ -216,7 +216,7 @@ mod tests { // Finish the progress handler.call(message::Finish::new(Scope::L10n)).await?; - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); assert!(progresses.is_empty()); Ok(()) @@ -233,7 +233,7 @@ mod tests { .call(message::Start::new(Scope::L10n, 2, "")) .await?; - let progresses = handler.call(message::Get).await.unwrap(); + let progresses = handler.call(message::GetProgress).await.unwrap(); assert_eq!(progresses.len(), 2); assert_eq!(progresses[0].scope, Scope::Manager); assert_eq!(progresses[1].scope, Scope::L10n); diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index 7025c4563d..40bb901b1c 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -30,23 +30,23 @@ impl Message for GetStatus { type Reply = Status; } -pub struct Get; +pub struct GetProgress; -impl Message for Get { +impl Message for GetProgress { type Reply = Vec; } -pub struct Set { +pub struct SetProgress { pub progress: Progress, } -impl Set { +impl SetProgress { pub fn new(progress: Progress) -> Self { Self { progress } } } -impl Message for Set { +impl Message for SetProgress { type Reply = (); } diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index bef6c1217d..81bce4eb02 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -136,15 +136,15 @@ impl MessageHandler for Service { } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, _message: message::Get) -> Result, Error> { +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::GetProgress) -> Result, Error> { Ok(self.get_progresses().clone()) } } #[async_trait] -impl MessageHandler for Service { - async fn handle(&mut self, message: message::Set) -> Result<(), Error> { +impl MessageHandler for Service { + async fn handle(&mut self, message: message::SetProgress) -> Result<(), Error> { let progress = message.progress; if let Some(index) = self.get_progress_index(progress.scope) { self.update_progress(index, progress.clone()); From 85a3196885066bdda6d9b3ed8f934dd0ff5f86ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 15:04:55 +0000 Subject: [PATCH 08/16] Extend progress service tests --- rust/agama-utils/src/api/status.rs | 2 +- rust/agama-utils/src/progress.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/rust/agama-utils/src/api/status.rs b/rust/agama-utils/src/api/status.rs index ad8f849233..0ea9c813b7 100644 --- a/rust/agama-utils/src/api/status.rs +++ b/rust/agama-utils/src/api/status.rs @@ -32,7 +32,7 @@ pub struct Status { } /// Represents the current state of the installation process. -#[derive(Clone, Copy, Default, Serialize, utoipa::ToSchema)] +#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub enum Stage { #[default] diff --git a/rust/agama-utils/src/progress.rs b/rust/agama-utils/src/progress.rs index b43e29db25..5b3b79147c 100644 --- a/rust/agama-utils/src/progress.rs +++ b/rust/agama-utils/src/progress.rs @@ -31,6 +31,7 @@ mod tests { event::{self, Event}, progress::{self, Progress}, scope::Scope, + status::Stage, }, progress::{ message, @@ -299,4 +300,20 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_set_and_get_stage() -> Result<(), Box> { + let (_receiver, handler) = start_testing_service(); + + let status = handler.call(message::GetStatus).await?; + assert_eq!(status.stage, Stage::Configuring); + + handler + .call(message::SetStage::new(Stage::Installing)) + .await?; + + let status = handler.call(message::GetStatus).await?; + assert_eq!(status.stage, Stage::Installing); + Ok(()) + } } From ea142becdc23ca42d09deef74ba41d45b626c668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 15:18:18 +0000 Subject: [PATCH 09/16] Use test_context in progress services tests --- rust/agama-utils/src/progress.rs | 144 +++++++++++++++++-------------- 1 file changed, 77 insertions(+), 67 deletions(-) diff --git a/rust/agama-utils/src/progress.rs b/rust/agama-utils/src/progress.rs index 5b3b79147c..6ab7dd6702 100644 --- a/rust/agama-utils/src/progress.rs +++ b/rust/agama-utils/src/progress.rs @@ -38,24 +38,31 @@ mod tests { service::{self, Service}, }, }; + use test_context::{test_context, AsyncTestContext}; use tokio::sync::broadcast; - fn start_testing_service() -> (event::Receiver, Handler) { - let (events, receiver) = broadcast::channel::(16); - let handler = Service::starter(events).start(); - (receiver, handler) + struct Context { + events_rx: event::Receiver, + handler: Handler, } - #[tokio::test] - async fn test_progress() -> Result<(), Box> { - let (mut receiver, handler) = start_testing_service(); + impl AsyncTestContext for Context { + async fn setup() -> Self { + let (events_tx, events_rx) = broadcast::channel::(16); + let handler = Service::starter(events_tx).start(); + Self { events_rx, handler } + } + } + #[test_context(Context)] + #[tokio::test] + async fn test_progress(ctx: &mut Context) -> Result<(), Box> { // Start a progress (first step) - handler + ctx.handler .call(message::Start::new(Scope::L10n, 3, "first step")) .await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); let Event::ProgressChanged { progress: event_progress, } = event @@ -69,21 +76,21 @@ mod tests { assert_eq!(event_progress.step, "first step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::GetProgress).await?; + let progresses = ctx.handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); assert_eq!(*progress, event_progress); // Second step - handler + ctx.handler .call(message::NextWithStep::new(Scope::L10n, "second step")) .await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); assert!(matches!(event, Event::ProgressChanged { progress: _ })); - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -92,12 +99,12 @@ mod tests { assert_eq!(progress.index, 2); // Last step (without step text) - handler.call(message::Next::new(Scope::L10n)).await?; + ctx.handler.call(message::Next::new(Scope::L10n)).await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); assert!(matches!(event, Event::ProgressChanged { progress: _ })); - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -106,29 +113,30 @@ mod tests { assert_eq!(progress.index, 3); // Finish the progress - handler.call(message::Finish::new(Scope::L10n)).await?; + ctx.handler.call(message::Finish::new(Scope::L10n)).await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); assert!(matches!( event, Event::ProgressFinished { scope: Scope::L10n } )); - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); assert!(progresses.is_empty()); Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_set_progress() -> Result<(), Box> { - let (mut receiver, handler) = start_testing_service(); - + async fn test_set_progress(ctx: &mut Context) -> Result<(), Box> { // Set first progress. let progress = Progress::new(Scope::Storage, 3, "first step".to_string()); - handler.call(message::SetProgress::new(progress)).await?; + ctx.handler + .call(message::SetProgress::new(progress)) + .await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); let Event::ProgressChanged { progress: event_progress, } = event @@ -142,7 +150,7 @@ mod tests { assert_eq!(event_progress.step, "first step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::GetProgress).await?; + let progresses = ctx.handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); @@ -150,9 +158,11 @@ mod tests { // Set second progress let progress = Progress::new(Scope::Storage, 3, "second step".to_string()); - handler.call(message::SetProgress::new(progress)).await?; + ctx.handler + .call(message::SetProgress::new(progress)) + .await?; - let event = receiver.recv().await.unwrap(); + let event = ctx.events_rx.recv().await.unwrap(); let Event::ProgressChanged { progress: event_progress, } = event @@ -166,7 +176,7 @@ mod tests { assert_eq!(event_progress.step, "second step"); assert_eq!(event_progress.index, 1); - let progresses = handler.call(message::GetProgress).await?; + let progresses = ctx.handler.call(message::GetProgress).await?; assert_eq!(progresses.len(), 1); let progress = progresses.first().unwrap(); @@ -175,19 +185,18 @@ mod tests { Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_progress_with_steps() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - + async fn test_progress_with_steps(ctx: &mut Context) -> Result<(), Box> { // Start a progress (first step) - handler + ctx.handler .call(message::StartWithSteps::new( Scope::L10n, &["first step", "second step", "third step"], )) .await?; - let progresses = handler.call(message::GetProgress).await?; + let progresses = ctx.handler.call(message::GetProgress).await?; let progress = progresses.first().unwrap(); assert_eq!(progress.scope, Scope::L10n); assert_eq!(progress.size, 3); @@ -199,42 +208,41 @@ mod tests { assert_eq!(progress.index, 1); // Second step - handler.call(message::Next::new(Scope::L10n)).await?; + ctx.handler.call(message::Next::new(Scope::L10n)).await?; - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.step, "second step"); assert_eq!(progress.index, 2); // Third step - handler.call(message::Next::new(Scope::L10n)).await?; + ctx.handler.call(message::Next::new(Scope::L10n)).await?; - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); let progress = progresses.first().unwrap(); assert_eq!(progress.step, "third step"); assert_eq!(progress.index, 3); // Finish the progress - handler.call(message::Finish::new(Scope::L10n)).await?; + ctx.handler.call(message::Finish::new(Scope::L10n)).await?; - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); assert!(progresses.is_empty()); Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_several_progresses() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - - handler + async fn test_several_progresses(ctx: &mut Context) -> Result<(), Box> { + ctx.handler .call(message::Start::new(Scope::Manager, 2, "")) .await?; - handler + ctx.handler .call(message::Start::new(Scope::L10n, 2, "")) .await?; - let progresses = handler.call(message::GetProgress).await.unwrap(); + let progresses = ctx.handler.call(message::GetProgress).await.unwrap(); assert_eq!(progresses.len(), 2); assert_eq!(progresses[0].scope, Scope::Manager); assert_eq!(progresses[1].scope, Scope::L10n); @@ -242,14 +250,15 @@ mod tests { Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_progress_missing_step() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - - handler + async fn test_progress_missing_step( + ctx: &mut Context, + ) -> Result<(), Box> { + ctx.handler .call(message::Start::new(Scope::L10n, 1, "")) .await?; - let error = handler.call(message::Next::new(Scope::L10n)).await; + let error = ctx.handler.call(message::Next::new(Scope::L10n)).await; assert!(matches!( error, Err(service::Error::Progress(progress::Error::MissingStep( @@ -260,14 +269,13 @@ mod tests { Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_missing_progress() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - - handler + async fn test_missing_progress(ctx: &mut Context) -> Result<(), Box> { + ctx.handler .call(message::Start::new(Scope::Manager, 2, "")) .await?; - let error = handler.call(message::Next::new(Scope::L10n)).await; + let error = ctx.handler.call(message::Next::new(Scope::L10n)).await; assert!(matches!( error, Err(service::Error::MissingProgress(Scope::L10n)) @@ -276,21 +284,24 @@ mod tests { Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_duplicated_progress() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - - handler + async fn test_duplicated_progress(ctx: &mut Context) -> Result<(), Box> { + ctx.handler .call(message::Start::new(Scope::L10n, 2, "")) .await?; - let error = handler.call(message::Start::new(Scope::L10n, 1, "")).await; + let error = ctx + .handler + .call(message::Start::new(Scope::L10n, 1, "")) + .await; assert!(matches!( error, Err(service::Error::DuplicatedProgress(Scope::L10n)) )); - let error = handler + let error = ctx + .handler .call(message::StartWithSteps::new(Scope::L10n, &["step"])) .await; assert!(matches!( @@ -301,18 +312,17 @@ mod tests { Ok(()) } + #[test_context(Context)] #[tokio::test] - async fn test_set_and_get_stage() -> Result<(), Box> { - let (_receiver, handler) = start_testing_service(); - - let status = handler.call(message::GetStatus).await?; + async fn test_set_and_get_stage(ctx: &mut Context) -> Result<(), Box> { + let status = ctx.handler.call(message::GetStatus).await?; assert_eq!(status.stage, Stage::Configuring); - handler + ctx.handler .call(message::SetStage::new(Stage::Installing)) .await?; - let status = handler.call(message::GetStatus).await?; + let status = ctx.handler.call(message::GetStatus).await?; assert_eq!(status.stage, Stage::Installing); Ok(()) } From 12e41c8be375f75aab7fbdcda8428e384dae9971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 15:36:38 +0000 Subject: [PATCH 10/16] Prevent Set* and RunAction actions --- rust/agama-manager/src/service.rs | 17 +++++++++++++++++ rust/agama-utils/src/api/status.rs | 2 +- rust/agama-utils/src/progress.rs | 8 ++++---- rust/agama-utils/src/progress/message.rs | 6 ++++++ rust/agama-utils/src/progress/service.rs | 12 ++++++++++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 2670c3fb46..826de50e94 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -75,6 +75,8 @@ pub enum Error { NetworkSystem(#[from] network::NetworkSystemError), #[error(transparent)] Hardware(#[from] hardware::Error), + #[error("Cannot dispatch this action in {current} stage (expected {expected}).")] + UnexpectedStage { current: Stage, expected: Stage }, } pub struct Starter { @@ -458,6 +460,14 @@ impl Service { } Ok(()) } + + async fn ensure_stage(&self, expected: Stage) -> Result<(), Error> { + let current = self.progress.call(progress::message::GetStage).await?; + if current != expected { + return Err(Error::UnexpectedStage { expected, current }); + } + Ok(()) + } } impl Actor for Service { @@ -529,6 +539,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// Sets the user configuration with the given values. async fn handle(&mut self, message: message::SetConfig) -> Result<(), Error> { + self.ensure_stage(Stage::Configuring).await?; self.set_config(message.config).await } } @@ -552,6 +563,7 @@ impl MessageHandler for Service { /// It merges the current config with the given one. If some scope is missing in the given /// config, then it keeps the values from the current config. async fn handle(&mut self, message: message::UpdateConfig) -> Result<(), Error> { + self.ensure_stage(Stage::Configuring).await?; let config = merge(&self.config, &message.config).map_err(|_| Error::MergeConfig)?; let config = merge_network(config, message.config); self.update_config(config).await @@ -598,6 +610,8 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It runs the given action. async fn handle(&mut self, message: message::RunAction) -> Result<(), Error> { + self.ensure_stage(Stage::Configuring).await?; + match message.action { Action::ConfigureL10n(config) => { self.configure_l10n(config).await?; @@ -636,6 +650,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It sets the storage model. async fn handle(&mut self, message: message::SetStorageModel) -> Result<(), Error> { + self.ensure_stage(Stage::Configuring).await?; Ok(self .storage .call(storage::message::SetConfigModel::new(message.model)) @@ -650,6 +665,7 @@ impl MessageHandler for Service { &mut self, message: message::SolveStorageModel, ) -> Result, Error> { + self.ensure_stage(Stage::Configuring).await?; Ok(self .storage .call(storage::message::SolveConfigModel::new(message.model)) @@ -662,6 +678,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It sets the software resolvables. async fn handle(&mut self, message: software::message::SetResolvables) -> Result<(), Error> { + self.ensure_stage(Stage::Configuring).await?; self.software.call(message).await?; Ok(()) } diff --git a/rust/agama-utils/src/api/status.rs b/rust/agama-utils/src/api/status.rs index 0ea9c813b7..82605f9eb0 100644 --- a/rust/agama-utils/src/api/status.rs +++ b/rust/agama-utils/src/api/status.rs @@ -32,7 +32,7 @@ pub struct Status { } /// Represents the current state of the installation process. -#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq, utoipa::ToSchema)] +#[derive(Clone, Copy, Debug, Default, Serialize, PartialEq, strum::Display, utoipa::ToSchema)] #[serde(rename_all = "camelCase")] pub enum Stage { #[default] diff --git a/rust/agama-utils/src/progress.rs b/rust/agama-utils/src/progress.rs index 6ab7dd6702..00bdad120e 100644 --- a/rust/agama-utils/src/progress.rs +++ b/rust/agama-utils/src/progress.rs @@ -315,15 +315,15 @@ mod tests { #[test_context(Context)] #[tokio::test] async fn test_set_and_get_stage(ctx: &mut Context) -> Result<(), Box> { - let status = ctx.handler.call(message::GetStatus).await?; - assert_eq!(status.stage, Stage::Configuring); + let stage = ctx.handler.call(message::GetStage).await?; + assert_eq!(stage, Stage::Configuring); ctx.handler .call(message::SetStage::new(Stage::Installing)) .await?; - let status = ctx.handler.call(message::GetStatus).await?; - assert_eq!(status.stage, Stage::Installing); + let stage = ctx.handler.call(message::GetStage).await?; + assert_eq!(stage, Stage::Installing); Ok(()) } } diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index 40bb901b1c..76b642d744 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -147,3 +147,9 @@ impl SetStage { impl Message for SetStage { type Reply = (); } + +pub struct GetStage; + +impl Message for GetStage { + type Reply = Stage; +} diff --git a/rust/agama-utils/src/progress/service.rs b/rust/agama-utils/src/progress/service.rs index 81bce4eb02..b591c8b8ad 100644 --- a/rust/agama-utils/src/progress/service.rs +++ b/rust/agama-utils/src/progress/service.rs @@ -19,6 +19,7 @@ // find current contact information at www.suse.com. use crate::actor::{self, Actor, Handler, MessageHandler}; +use crate::api::status::Stage; use crate::api::Status; use crate::{ api::event::{self, Event}, @@ -80,6 +81,10 @@ impl Service { &self.status } + fn get_stage(&self) -> Stage { + self.status.stage + } + // NOTE: this method might be implemented by Status. fn get_progresses(&mut self) -> &Vec { &self.status.progresses @@ -126,6 +131,13 @@ impl MessageHandler for Service { } } +#[async_trait] +impl MessageHandler for Service { + async fn handle(&mut self, _message: message::GetStage) -> Result { + Ok(self.get_stage()) + } +} + #[async_trait] impl MessageHandler for Service { async fn handle(&mut self, message: message::SetStage) -> Result<(), Error> { From e2a23898707be6372dde2293cc3be16a40a3e854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 11 Dec 2025 15:37:17 +0000 Subject: [PATCH 11/16] Remove uneeded events handler in the manager service --- rust/agama-manager/src/service.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 826de50e94..26c07b05f2 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -223,7 +223,6 @@ impl Starter { }; let mut service = Service { - events: self.events, questions: self.questions, progress, issues, @@ -260,7 +259,6 @@ pub struct Service { product: Option>>, config: Config, system: manager::SystemInfo, - events: event::Sender, } impl Service { From e1ec8b7318270b9c755440bdcf3cbc0501f0b075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Dec 2025 07:04:32 +0000 Subject: [PATCH 12/16] Write user files to the installed system --- rust/agama-manager/src/service.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index 26c07b05f2..e718769a7c 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -741,6 +741,7 @@ impl InstallAction { .await?; self.l10n.call(l10n::message::Install).await?; self.software.call(software::message::Finish).await?; + self.files.call(files::message::WriteFiles).await?; self.storage.call(storage::message::Finish).await?; // From 52db50a0f4e5c7fbfc6a993ddafc607999ab9171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Dec 2025 07:06:55 +0000 Subject: [PATCH 13/16] Describe the InstallAction struct --- rust/agama-manager/src/service.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index e718769a7c..bbe1bb866f 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -682,6 +682,9 @@ impl MessageHandler for Service { } } +/// Implements the installation process. +/// +/// This action runs on a separate Tokio task to prevent the manager from blocking. struct InstallAction { l10n: Handler, network: NetworkSystemClient, @@ -692,6 +695,7 @@ struct InstallAction { } impl InstallAction { + /// Runs the installation process on a separate Tokio task. pub fn run(mut self) { tokio::spawn(async move { self.install().await.unwrap(); From fcb37ef9725cc525abdee5326413370cbf5e9b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Dec 2025 07:54:23 +0000 Subject: [PATCH 14/16] Rename ensure_stage to check_stage --- rust/agama-manager/src/service.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index bbe1bb866f..f458e9c119 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -459,7 +459,7 @@ impl Service { Ok(()) } - async fn ensure_stage(&self, expected: Stage) -> Result<(), Error> { + async fn check_stage(&self, expected: Stage) -> Result<(), Error> { let current = self.progress.call(progress::message::GetStage).await?; if current != expected { return Err(Error::UnexpectedStage { expected, current }); @@ -537,7 +537,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// Sets the user configuration with the given values. async fn handle(&mut self, message: message::SetConfig) -> Result<(), Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; self.set_config(message.config).await } } @@ -561,7 +561,7 @@ impl MessageHandler for Service { /// It merges the current config with the given one. If some scope is missing in the given /// config, then it keeps the values from the current config. async fn handle(&mut self, message: message::UpdateConfig) -> Result<(), Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; let config = merge(&self.config, &message.config).map_err(|_| Error::MergeConfig)?; let config = merge_network(config, message.config); self.update_config(config).await @@ -608,7 +608,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It runs the given action. async fn handle(&mut self, message: message::RunAction) -> Result<(), Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; match message.action { Action::ConfigureL10n(config) => { @@ -648,7 +648,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It sets the storage model. async fn handle(&mut self, message: message::SetStorageModel) -> Result<(), Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; Ok(self .storage .call(storage::message::SetConfigModel::new(message.model)) @@ -663,7 +663,7 @@ impl MessageHandler for Service { &mut self, message: message::SolveStorageModel, ) -> Result, Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; Ok(self .storage .call(storage::message::SolveConfigModel::new(message.model)) @@ -676,7 +676,7 @@ impl MessageHandler for Service { impl MessageHandler for Service { /// It sets the software resolvables. async fn handle(&mut self, message: software::message::SetResolvables) -> Result<(), Error> { - self.ensure_stage(Stage::Configuring).await?; + self.check_stage(Stage::Configuring).await?; self.software.call(message).await?; Ok(()) } From f931b8f115882143356d0ccfbd4a483b5e4a224f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Dec 2025 10:36:39 +0000 Subject: [PATCH 15/16] Introduce Stage::Failed --- rust/agama-manager/src/service.rs | 14 +++++++++++++- rust/agama-utils/src/api/status.rs | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index f458e9c119..a79f30d164 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -698,7 +698,19 @@ impl InstallAction { /// Runs the installation process on a separate Tokio task. pub fn run(mut self) { tokio::spawn(async move { - self.install().await.unwrap(); + if let Err(error) = self.install().await { + tracing::error!("Installation failed: {error}"); + if let Err(error) = self + .progress + .call(progress::message::SetStage::new(Stage::Failed)) + .await + { + tracing::error!( + "It was not possible to set the stage to {}: {error}", + Stage::Failed + ); + } + } }); } diff --git a/rust/agama-utils/src/api/status.rs b/rust/agama-utils/src/api/status.rs index 82605f9eb0..185295d641 100644 --- a/rust/agama-utils/src/api/status.rs +++ b/rust/agama-utils/src/api/status.rs @@ -42,4 +42,6 @@ pub enum Stage { Installing, /// Installation finished Finished, + /// Installation failed + Failed, } From 7bf5ad505cbe7194e67e168e7ed87ab2ee5b8536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Dec 2025 11:03:53 +0000 Subject: [PATCH 16/16] Translate progress messages --- rust/Cargo.lock | 5 +++-- rust/agama-files/src/runner.rs | 5 ++--- rust/agama-manager/Cargo.toml | 1 + rust/agama-manager/src/service.rs | 9 +++++---- rust/agama-software/src/zypp_server.rs | 9 +++++---- rust/agama-utils/src/progress.rs | 11 +++++++++-- rust/agama-utils/src/progress/message.rs | 7 ++----- 7 files changed, 27 insertions(+), 20 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0798a9c22c..00799263c2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -149,6 +149,7 @@ dependencies = [ "agama-storage", "agama-utils", "async-trait", + "gettext-rs", "merge-struct", "serde", "serde_json", @@ -1893,9 +1894,9 @@ dependencies = [ [[package]] name = "gettext-rs" -version = "0.7.2" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44e92f7dc08430aca7ed55de161253a22276dfd69c5526e5c5e95d1f7cf338a" +checksum = "5d5857dc1b7f0fee86961de833f434e29494d72af102ce5355738c0664222bdf" dependencies = [ "gettext-sys", "locale_config", diff --git a/rust/agama-files/src/runner.rs b/rust/agama-files/src/runner.rs index 1657af4a25..d7ff1bbb02 100644 --- a/rust/agama-files/src/runner.rs +++ b/rust/agama-files/src/runner.rs @@ -188,12 +188,11 @@ impl ScriptsRunner { /// Ancillary function to start the progress. fn start_progress(&self, scripts: &[&Script]) { - let messages: Vec<_> = scripts + let steps: Vec<_> = scripts .iter() .map(|s| format!("Running user script '{}'", s.name())) .collect(); - let steps: Vec<_> = messages.iter().map(|s| s.as_ref()).collect(); - let progress_action = progress::message::StartWithSteps::new(Scope::Files, &steps); + let progress_action = progress::message::StartWithSteps::new(Scope::Files, steps); _ = self.progress.cast(progress_action); } diff --git a/rust/agama-manager/Cargo.toml b/rust/agama-manager/Cargo.toml index 8c4d82daae..cdb190f426 100644 --- a/rust/agama-manager/Cargo.toml +++ b/rust/agama-manager/Cargo.toml @@ -20,6 +20,7 @@ serde_json = "1.0.140" tracing = "0.1.41" serde = { version = "1.0.228", features = ["derive"] } serde_with = "3.16.1" +gettext-rs = { version = "0.7.7", features = ["gettext-system"] } [dev-dependencies] test-context = "0.4.1" diff --git a/rust/agama-manager/src/service.rs b/rust/agama-manager/src/service.rs index a79f30d164..1277968faa 100644 --- a/rust/agama-manager/src/service.rs +++ b/rust/agama-manager/src/service.rs @@ -33,6 +33,7 @@ use agama_utils::{ progress, question, }; use async_trait::async_trait; +use gettextrs::gettext; use merge_struct::merge; use network::NetworkSystemClient; use serde_json::Value; @@ -726,10 +727,10 @@ impl InstallAction { self.progress .call(progress::message::StartWithSteps::new( Scope::Manager, - &[ - "Prepare the system", - "Install software", - "Configure the system", + vec![ + gettext("Prepare the system"), + gettext("Install software"), + gettext("Configure the system"), ], )) .await?; diff --git a/rust/agama-software/src/zypp_server.rs b/rust/agama-software/src/zypp_server.rs index 4833efc941..de5478f8e2 100644 --- a/rust/agama-software/src/zypp_server.rs +++ b/rust/agama-software/src/zypp_server.rs @@ -28,6 +28,7 @@ use agama_utils::{ products::ProductSpec, progress, question, }; +use gettextrs::gettext; use std::{collections::HashMap, path::Path}; use tokio::sync::{ mpsc::{self, UnboundedSender}, @@ -246,10 +247,10 @@ impl ZyppServer { _ = progress.cast(progress::message::StartWithSteps::new( Scope::Software, - &[ - "Updating the list of repositories", - "Refreshing metadata from the repositories", - "Calculating the software proposal", + vec![ + gettext("Updating the list of repositories"), + gettext("Refreshing metadata from the repositories"), + gettext("Calculating the software proposal"), ], )); let old_state = self.read(zypp)?; diff --git a/rust/agama-utils/src/progress.rs b/rust/agama-utils/src/progress.rs index 00bdad120e..4dd7e3dba6 100644 --- a/rust/agama-utils/src/progress.rs +++ b/rust/agama-utils/src/progress.rs @@ -192,7 +192,11 @@ mod tests { ctx.handler .call(message::StartWithSteps::new( Scope::L10n, - &["first step", "second step", "third step"], + vec![ + "first step".to_string(), + "second step".to_string(), + "third step".to_string(), + ], )) .await?; @@ -302,7 +306,10 @@ mod tests { let error = ctx .handler - .call(message::StartWithSteps::new(Scope::L10n, &["step"])) + .call(message::StartWithSteps::new( + Scope::L10n, + vec!["step".to_string()], + )) .await; assert!(matches!( error, diff --git a/rust/agama-utils/src/progress/message.rs b/rust/agama-utils/src/progress/message.rs index 76b642d744..3ac6ffdf8f 100644 --- a/rust/agama-utils/src/progress/message.rs +++ b/rust/agama-utils/src/progress/message.rs @@ -76,11 +76,8 @@ pub struct StartWithSteps { } impl StartWithSteps { - pub fn new(scope: Scope, steps: &[&str]) -> Self { - Self { - scope, - steps: steps.into_iter().map(ToString::to_string).collect(), - } + pub fn new(scope: Scope, steps: Vec) -> Self { + Self { scope, steps } } }