Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/agama-manager/src/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub async fn start(
let progress = progress::start(events.clone()).await?;
let l10n = l10n::start(issues.clone(), events.clone()).await?;
let network = network::start().await?;
let software = software::start(issues.clone(), progress.clone(), events.clone()).await?;
let software = software::start(issues.clone(), &progress, &questions, events.clone()).await?;
let storage = storage::start(progress.clone(), issues.clone(), events.clone(), dbus).await?;

let mut service = Service::new(
Expand Down
1 change: 1 addition & 0 deletions rust/agama-software/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition.workspace = true
agama-locale-data = { path = "../agama-locale-data" }
agama-utils = { path = "../agama-utils" }
async-trait = "0.1.89"
gettext-rs = { version = "0.7.1", features = ["gettext-system"] }
glob = "0.3.1"
regex = "1.11.0"
serde = { version = "1.0.210", features = ["derive"] }
Expand Down
1 change: 1 addition & 0 deletions rust/agama-software/src/callbacks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod commit_download;
99 changes: 99 additions & 0 deletions rust/agama-software/src/callbacks/commit_download.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use agama_utils::{actor::Handler, api::question::QuestionSpec, progress, question};
use gettextrs::gettext;
use tokio::runtime::Handle;
use zypp_agama::callbacks::pkg_download::{Callback, DownloadError};

#[derive(Clone)]
pub struct CommitDownload {
progress: Handler<progress::Service>,
questions: Handler<question::Service>,
}

impl CommitDownload {
pub fn new(
progress: Handler<progress::Service>,
questions: Handler<question::Service>,
) -> Self {
Self {
progress,
questions,
}
}
}

impl Callback for CommitDownload {
fn start_preload(&self) {
// TODO: report progress that we start preloading packages
tracing::info!("Start preload");
}

fn problem(
&self,
name: &str,
error: DownloadError,
description: &str,
) -> zypp_agama::callbacks::ProblemResponse {
// TODO: make it generic for any problemResponse questions
let labels = [gettext("Retry"), gettext("Ignore")];
let actions = [
("Retry", labels[0].as_str()),
("Ignore", labels[1].as_str()),
];
let error_str = error.to_string();
let data = [("package", name), ("error_code", error_str.as_str())];
let question = QuestionSpec::new(description, "software.package_error.provide_error")
.with_actions(&actions)
.with_data(&data);
let result = Handle::current().block_on(async move {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NP: I feel the bottom half of the fn is boilerplate and we will want to factor it out as more callbacks appear

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I am sure that at least more helpers for ProblemResponse handling will be useful. So lets see how it will end.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to revisit it when more callbacks appears

self.questions
.call(question::message::Ask::new(question))
.await
});
let Ok(answer) = result else {
tracing::warn!("Failed to ask question {:?}", result);
return zypp_agama::callbacks::ProblemResponse::ABORT;
};

let Some(answer_str) = answer.answer else {
tracing::warn!("No answer provided");
return zypp_agama::callbacks::ProblemResponse::ABORT;
};

answer_str
.action
.as_str()
.parse::<zypp_agama::callbacks::ProblemResponse>()
.unwrap_or(zypp_agama::callbacks::ProblemResponse::ABORT)
}

fn gpg_check(
&self,
resolvable_name: &str,
_repo_url: &str,
check_result: zypp_agama::callbacks::pkg_download::GPGCheckResult,
) -> Option<zypp_agama::callbacks::ProblemResponse> {
if check_result == zypp_agama::callbacks::pkg_download::GPGCheckResult::Ok {
// GPG is happy, so we are also happy and lets just continue
return None;
}

// do not log URL here as it can contain sensitive info and it is visible from other logs
tracing::warn!(
"GPG check failed for {:?} with {:?}",
resolvable_name,
check_result
);

// TODO: implement the DUD case:
// DUD (Driver Update Disk)
// ignore the error when the package comes from the DUD repository and
// the DUD package GPG checks are disabled via a boot option
//
// if repo_url == Agama::Software::Manager.dud_repository_url && ignore_dud_packages_gpg_errors? {
// logger.info "Ignoring the GPG check failure for a DUD package"
// return Ok(ProblemResponse::IGNORE);
// }

None
}
}
1 change: 1 addition & 0 deletions rust/agama-software/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ pub use service::Service;
mod model;
pub use model::{Model, ModelAdapter, Resolvable, ResolvableType};

mod callbacks;
pub mod message;
mod zypp_server;
18 changes: 15 additions & 3 deletions rust/agama-software/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use agama_utils::{
Issue,
},
products::{ProductSpec, UserPattern},
progress,
progress, question,
};
use async_trait::async_trait;
use tokio::sync::{mpsc, oneshot};
Expand Down Expand Up @@ -79,14 +79,22 @@ pub struct Model {
zypp_sender: mpsc::UnboundedSender<SoftwareAction>,
// FIXME: what about having a SoftwareServiceState to keep business logic state?
selected_product: Option<ProductSpec>,
progress: Handler<progress::Service>,
question: Handler<question::Service>,
}

impl Model {
/// Initializes the struct with the information from the underlying system.
pub fn new(zypp_sender: mpsc::UnboundedSender<SoftwareAction>) -> Result<Self, service::Error> {
pub fn new(
zypp_sender: mpsc::UnboundedSender<SoftwareAction>,
progress: Handler<progress::Service>,
question: Handler<question::Service>,
) -> Result<Self, service::Error> {
Ok(Self {
zypp_sender,
selected_product: None,
progress,
question,
})
}
}
Expand Down Expand Up @@ -144,7 +152,11 @@ impl ModelAdapter for Model {

async fn install(&self) -> Result<bool, service::Error> {
let (tx, rx) = oneshot::channel();
self.zypp_sender.send(SoftwareAction::Install(tx))?;
self.zypp_sender.send(SoftwareAction::Install(
tx,
self.progress.clone(),
self.question.clone(),
))?;
Ok(rx.await??)
}

Expand Down
9 changes: 5 additions & 4 deletions rust/agama-software/src/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
use agama_utils::{
actor::{self, Handler},
api::event,
issue, progress,
issue, progress, question,
};

#[derive(thiserror::Error, Debug)]
Expand All @@ -49,12 +49,13 @@ pub enum Error {
/// * `issues`: handler to the issues service.
pub async fn start(
issues: Handler<issue::Service>,
progress: Handler<progress::Service>,
progress: &Handler<progress::Service>,
question: &Handler<question::Service>,
events: event::Sender,
) -> Result<Handler<Service>, Error> {
let zypp_sender = ZyppServer::start()?;
let model = Model::new(zypp_sender)?;
let mut service = Service::new(model, issues, progress, events);
let model = Model::new(zypp_sender, progress.clone(), question.clone())?;
let mut service = Service::new(model, issues, progress.clone(), events);
// FIXME: this should happen after spawning the task.
service.setup().await?;
let handler = actor::spawn(service);
Expand Down
28 changes: 21 additions & 7 deletions rust/agama-software/src/zypp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use agama_utils::{
Issue, IssueSeverity, Scope,
},
products::ProductSpec,
progress,
progress, question,
};
use std::path::Path;
use tokio::sync::{
Expand All @@ -34,7 +34,11 @@ use tokio::sync::{
};
use zypp_agama::ZyppError;

use crate::model::state::{self, SoftwareState};
use crate::{
callbacks::commit_download,
model::state::{self, SoftwareState},
};

const TARGET_DIR: &str = "/run/agama/software_ng_zypp";
const GPG_KEYS: &str = "/usr/lib/rpm/gnupg/keys/gpg-*";

Expand Down Expand Up @@ -86,7 +90,11 @@ pub enum ZyppServerError {
pub type ZyppServerResult<R> = Result<R, ZyppServerError>;

pub enum SoftwareAction {
Install(oneshot::Sender<ZyppServerResult<bool>>),
Install(
oneshot::Sender<ZyppServerResult<bool>>,
Handler<progress::Service>,
Handler<question::Service>,
),
Finish(oneshot::Sender<ZyppServerResult<()>>),
GetPatternsMetadata(Vec<String>, oneshot::Sender<ZyppServerResult<Vec<Pattern>>>),
ComputeProposal(
Expand Down Expand Up @@ -172,8 +180,9 @@ impl ZyppServer {
SoftwareAction::GetPatternsMetadata(names, tx) => {
self.get_patterns(names, tx, zypp).await?;
}
SoftwareAction::Install(tx) => {
tx.send(self.install(zypp))
SoftwareAction::Install(tx, progress, question) => {
let callback = commit_download::CommitDownload::new(progress, question);
tx.send(self.install(zypp, &callback))
.map_err(|_| ZyppDispatchError::ResponseChannelClosed)?;
}
SoftwareAction::Finish(tx) => {
Expand All @@ -187,10 +196,15 @@ impl ZyppServer {
}

// Install rpms
fn install(&self, zypp: &zypp_agama::Zypp) -> ZyppServerResult<bool> {
fn install(
&self,
zypp: &zypp_agama::Zypp,
download_callback: &commit_download::CommitDownload,
) -> ZyppServerResult<bool> {
let target = "/mnt";
zypp.switch_target(target)?;
let result = zypp.commit()?;
// TODO: write real install callbacks beside download ones
let result = zypp.commit(download_callback)?;
tracing::info!("libzypp commit ends with {}", result);
Ok(result)
}
Expand Down
1 change: 1 addition & 0 deletions rust/zypp-agama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ edition = "2021"
[dependencies]
zypp-agama-sys = { path="./zypp-agama-sys" }
url = "2.5.7"
tracing = "0.1.41"
22 changes: 22 additions & 0 deletions rust/zypp-agama/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Zypp Agama

crate which purpose is to have thin layer to libzypp for agama purpose.

### How to Add New Libzypp Call

- at first create its C API in `zypp-agama-sys/c-layer/include` directory and write its implementation to cxx file.
- generate new FFI bindings (in low level, unsafe Rust), in `rust/zypp-agama-sys` by running cargo build
- write a (regular, safe) Rust wrapper, in `src`

### Libzypp Notes

- libzypp is not thread safe
- for seeing how it works see yast2-pkg-bindings and zypper as some parameters in calls are ignored
- goal is to have thin layer close to libzypp and build logic on top of it in more advanced language

### Interesting Resources

- https://doc.rust-lang.org/nomicon/ffi.html
- https://adventures.michaelfbryan.com/posts/rust-closures-in-ffi/
- https://www.khoury.northeastern.edu/home/lth/larceny/notes/note7-ffi.html
- https://cliffle.com/blog/not-thread-safe/ ( interesting part how to ensure in rust that some data is not thread safe )
Loading
Loading