diff --git a/rust/agama-software/src/model/registration.rs b/rust/agama-software/src/model/registration.rs index 9c1c76edfc..43caa74310 100644 --- a/rust/agama-software/src/model/registration.rs +++ b/rust/agama-software/src/model/registration.rs @@ -45,6 +45,8 @@ pub enum RegistrationError { AddService(String, #[source] zypp_agama::ZyppError), #[error("Failed to refresh the service {0}: {1}")] RefreshService(String, #[source] zypp_agama::ZyppError), + #[error("Failed to select product from service {0}: {1}")] + SelectProduct(String, #[source] zypp_agama::ZyppError), #[error("Failed to copy file {0}: {1}")] IO(String, #[source] std::io::Error), #[error(transparent)] @@ -148,9 +150,20 @@ impl Registration { zypp.add_service(&service.name, &service.url) .map_err(|e| RegistrationError::AddService(service.name.clone(), e))?; let name = service.name.clone(); - self.services.push(service); + self.services.push(service.clone()); zypp.refresh_service(&name) - .map_err(|e| RegistrationError::RefreshService(name, e))?; + .map_err(|e| RegistrationError::RefreshService(name.clone(), e))?; + zypp.load_source( + zypp_agama::callbacks::empty_progress, + &mut zypp_agama::callbacks::security::EmptyCallback, + ) + .map_err(|e| RegistrationError::RefreshService(name, e))?; + + // skip for the first service (base product), the base product is selected differently + if self.services.len() > 1 { + zypp.select_products_from_service(&service.name) + .map_err(|e| RegistrationError::SelectProduct(service.name.clone(), e))?; + } Ok(()) } @@ -268,6 +281,15 @@ impl Registration { pub fn base_product_service_name(&self) -> Option { self.services.first().map(|s| s.name.clone()) } + + pub fn addon_product_service_names(&self) -> Vec { + self.services + .iter() + // skip the first service (base product) + .skip(1) + .map(|s| s.name.clone()) + .collect() + } } /// A builder for a [Registration] object. diff --git a/rust/agama-software/src/zypp_server.rs b/rust/agama-software/src/zypp_server.rs index d69a1a7f59..45ba78a830 100644 --- a/rust/agama-software/src/zypp_server.rs +++ b/rust/agama-software/src/zypp_server.rs @@ -451,6 +451,14 @@ impl ZyppServer { }; } + // if registered select products from add-on services + if let RegistrationStatus::Registered(boxed_registration) = &self.registration { + let registration = boxed_registration.as_ref(); + for name in registration.addon_product_service_names() { + zypp.select_products_from_service(&name)?; + } + } + self.only_required = state.options.only_required; tracing::info!("Install only required packages: {}", self.only_required); // run the solver to select the dependencies, ignore the errors, the solver runs again later diff --git a/rust/package/agama.changes b/rust/package/agama.changes index a179618f4b..7c6ed0b0b9 100644 --- a/rust/package/agama.changes +++ b/rust/package/agama.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Thu Mar 5 07:19:01 UTC 2026 - Ladislav Slezák + +- Refresh and load the repositories after registration + (bsc#1259217) +- Select addon product to install after registering an extension + (bsc#1258187) + ------------------------------------------------------------------- Thu Mar 5 07:14:07 UTC 2026 - Imobach Gonzalez Sosa diff --git a/rust/zypp-agama/src/lib.rs b/rust/zypp-agama/src/lib.rs index cc784f47a0..8d1b06bb6e 100644 --- a/rust/zypp-agama/src/lib.rs +++ b/rust/zypp-agama/src/lib.rs @@ -5,6 +5,7 @@ use std::{ }; use errors::ZyppResult; +use tracing::info; use zypp_agama_sys::{get_patterns, ProgressCallback, ProgressData, Status, ZyppProgressCallback}; pub mod errors; @@ -337,6 +338,40 @@ impl Zypp { } } + pub fn select_products_from_service(&self, service: &String) -> ZyppResult<()> { + unsafe { + let mut status: Status = Status::default(); + let status_ptr = &mut status as *mut _; + info!("Selecting products from service {}", service); + + // find products from the requested service + let products = zypp_agama_sys::get_products(self.ptr, status_ptr); + for i in 0..products.size as usize { + let c_product = *(products.list.add(i)); + + if string_from_ptr(c_product.service_alias) == *service { + let prod_name = string_from_ptr(c_product.name); + let repo_alias = string_from_ptr(c_product.repo_alias); + info!( + "Selecting product {} from repository {}", + prod_name, repo_alias + ); + zypp_agama_sys::resolvable_select( + self.ptr, + c_product.name, + zypp_agama_sys::RESOLVABLE_KIND_RESOLVABLE_PRODUCT, + ResolvableSelected::Installation.into(), + status_ptr, + ); + } + } + + zypp_agama_sys::free_products(&products); + + helpers::status_to_result_void(status) + } + } + pub fn unselect_resolvable( &self, name: &str, @@ -565,6 +600,7 @@ impl Zypp { { let repos = self.list_repositories()?; let enabled_repos: Vec<&Repository> = repos.iter().filter(|r| r.enabled).collect(); + info!("Found {} enabled repositories", enabled_repos.len()); // TODO: this step logic for progress can be enclosed to own struct let mut percent: f64 = 0.0; let percent_step: f64 = 100.0 / (enabled_repos.len() as f64 * 3.0); // 3 substeps @@ -579,6 +615,7 @@ impl Zypp { return abort_err; } + info!("Refreshing repository {}", &i.alias); self.refresh_repository( &i.alias, &callbacks::download_progress::EmptyCallback, @@ -592,6 +629,7 @@ impl Zypp { if !cont { return abort_err; } + info!("Creating repository cache for {}", &i.alias); self.create_repo_cache(&i.alias, callbacks::empty_progress)?; percent += percent_step; cont = progress( @@ -601,6 +639,7 @@ impl Zypp { if !cont { return abort_err; } + info!("Loading repository cache for {}", &i.alias); self.load_repo_cache(&i.alias)?; percent += percent_step; } diff --git a/rust/zypp-agama/zypp-agama-sys/c-layer/include/lib.h b/rust/zypp-agama/zypp-agama-sys/c-layer/include/lib.h index bcc2a73954..c13653140e 100644 --- a/rust/zypp-agama/zypp-agama-sys/c-layer/include/lib.h +++ b/rust/zypp-agama/zypp-agama-sys/c-layer/include/lib.h @@ -177,6 +177,24 @@ struct Patterns get_patterns(struct Zypp *_zypp, struct Status *status) noexcept; void free_patterns(const struct Patterns *patterns) noexcept; +/// Representation of zypp::Product +struct Product { + // so far we do not need more details about the products + const char *name; ///< owned + const char *repo_alias; ///< owned + const char *service_alias; ///< owned +}; + +struct Products { + struct Product *list; ///< owned, *size* items + unsigned size; +}; + +/// Get Product details. +struct Products get_products(struct Zypp *_zypp, + struct Status *status) noexcept; +void free_products(const struct Products *products) noexcept; + void import_gpg_key(struct Zypp *zypp, const char *const pathname, struct Status *status) noexcept; diff --git a/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx b/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx index 69e6e58398..04845778c8 100644 --- a/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx +++ b/rust/zypp-agama/zypp-agama-sys/c-layer/lib.cxx @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -422,6 +423,40 @@ void free_patterns(const struct Patterns *patterns) noexcept { free((void *)patterns->list); } +struct Products get_products(struct Zypp *zypp, + struct Status *status) noexcept { + auto iterator = + zypp->zypp_pointer->pool().proxy().byKind(zypp::ResKind::product); + + Products result = { + (struct Product *)malloc(iterator.size() * sizeof(Product)), + 0 // initialize with zero and increase after each successful add of + // product info + }; + + for (const auto iter : iterator) { + Product &product = result.list[result.size]; + auto zypp_product = iter->candidateAsKind(); + product.name = strdup(iter->name().c_str()); + product.repo_alias = strdup(zypp_product->repoInfo().alias().c_str()); + product.service_alias = strdup(zypp_product->repoInfo().service().c_str()); + result.size++; + } + + STATUS_OK(status); + return result; +} + +void free_products(const struct Products *products) noexcept { + for (unsigned i = 0; i < products->size; ++i) { + free((void *)products->list[i].name); + free((void *)products->list[i].repo_alias); + free((void *)products->list[i].service_alias); + } + + free((void *)products->list); +} + bool run_solver(struct Zypp *zypp, bool only_required, struct Status *status) noexcept { try { diff --git a/rust/zypp-agama/zypp-agama-sys/src/bindings.rs b/rust/zypp-agama/zypp-agama-sys/src/bindings.rs index e6d72e45e8..bc1056a2db 100644 --- a/rust/zypp-agama/zypp-agama-sys/src/bindings.rs +++ b/rust/zypp-agama/zypp-agama-sys/src/bindings.rs @@ -521,6 +521,40 @@ const _: () = { ["Offset of field: Patterns::list"][::std::mem::offset_of!(Patterns, list) - 0usize]; ["Offset of field: Patterns::size"][::std::mem::offset_of!(Patterns, size) - 8usize]; }; +#[doc = " Representation of zypp::Product"] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Product { + #[doc = "< owned"] + pub name: *const ::std::os::raw::c_char, + #[doc = "< owned"] + pub repo_alias: *const ::std::os::raw::c_char, + #[doc = "< owned"] + pub service_alias: *const ::std::os::raw::c_char, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of Product"][::std::mem::size_of::() - 24usize]; + ["Alignment of Product"][::std::mem::align_of::() - 8usize]; + ["Offset of field: Product::name"][::std::mem::offset_of!(Product, name) - 0usize]; + ["Offset of field: Product::repo_alias"][::std::mem::offset_of!(Product, repo_alias) - 8usize]; + ["Offset of field: Product::service_alias"] + [::std::mem::offset_of!(Product, service_alias) - 16usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Products { + #[doc = "< owned, *size* items"] + pub list: *mut Product, + pub size: ::std::os::raw::c_uint, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of Products"][::std::mem::size_of::() - 16usize]; + ["Alignment of Products"][::std::mem::align_of::() - 8usize]; + ["Offset of field: Products::list"][::std::mem::offset_of!(Products, list) - 0usize]; + ["Offset of field: Products::size"][::std::mem::offset_of!(Products, size) - 8usize]; +}; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Repository { @@ -616,6 +650,9 @@ unsafe extern "C" { #[doc = " Get Pattern details.\n Unknown patterns are simply omitted from the result. Match by\n PatternInfo.name, not by index."] pub fn get_patterns(_zypp: *mut Zypp, status: *mut Status) -> Patterns; pub fn free_patterns(patterns: *const Patterns); + #[doc = " Get Product details."] + pub fn get_products(_zypp: *mut Zypp, status: *mut Status) -> Products; + pub fn free_products(products: *const Products); pub fn import_gpg_key( zypp: *mut Zypp, pathname: *const ::std::os::raw::c_char,