fix #2934: list to-be-installed packages in updates

As always, libapt is mocking us with complexity, but we can get the
approximate result we want by retrieving dependencies of all
to-be-updated packages and then seeing if they are missing.

If they are, we assume they will be installed.

For this, query_detailed_info is extended to allow reading details for
non-installed packages, and this is also exposed in
list_installed_apt_packages via 'all_versions_for'. This is necessary so
we can retrieve changelogs for such packages.

Note that we cannot retrieve all that information all the time, as
querying details for packages that aren't installed takes a rather long
time.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
This commit is contained in:
Stefan Reiter 2020-10-21 11:41:15 +02:00 committed by Fabian Grünbichler
parent 52d2ae48f0
commit 91c9b42da3
1 changed files with 101 additions and 15 deletions

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use apt_pkg_native::Cache; use apt_pkg_native::Cache;
use anyhow::{Error, bail}; use anyhow::{Error, bail};
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -71,34 +73,83 @@ fn get_changelog_url(
struct FilterData<'a> { struct FilterData<'a> {
// this is version info returned by APT // this is version info returned by APT
installed_version: &'a str, installed_version: Option<&'a str>,
candidate_version: &'a str, candidate_version: &'a str,
// this is the version info the filter is supposed to check // this is the version info the filter is supposed to check
active_version: &'a str, active_version: &'a str,
} }
fn list_installed_apt_packages<F: Fn(FilterData) -> bool>(filter: F) enum PackagePreSelect {
-> Vec<APTUpdateInfo> { OnlyInstalled,
OnlyNew,
All,
}
fn list_installed_apt_packages<F: Fn(FilterData) -> bool>(
filter: F,
only_versions_for: Option<&str>,
) -> Vec<APTUpdateInfo> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let mut depends = HashSet::new();
// note: this is not an 'apt update', it just re-reads the cache from disk // note: this is not an 'apt update', it just re-reads the cache from disk
let mut cache = Cache::get_singleton(); let mut cache = Cache::get_singleton();
cache.reload(); cache.reload();
let mut cache_iter = cache.iter(); let mut cache_iter = match only_versions_for {
Some(name) => cache.find_by_name(name),
None => cache.iter()
};
loop { loop {
match cache_iter.next() { match cache_iter.next() {
Some(view) => { Some(view) => {
let di = query_detailed_info(&filter, view); let di = if only_versions_for.is_some() {
query_detailed_info(
PackagePreSelect::All,
&filter,
view,
None
)
} else {
query_detailed_info(
PackagePreSelect::OnlyInstalled,
&filter,
view,
Some(&mut depends)
)
};
if let Some(info) = di { if let Some(info) = di {
ret.push(info); ret.push(info);
} }
if only_versions_for.is_some() {
break;
}
}, },
None => { None => {
drop(cache_iter);
// also loop through missing dependencies, as they would be installed
for pkg in depends.iter() {
let mut iter = cache.find_by_name(&pkg);
let view = match iter.next() {
Some(view) => view,
None => continue // package not found, ignore
};
let di = query_detailed_info(
PackagePreSelect::OnlyNew,
&filter,
view,
None
);
if let Some(info) = di {
ret.push(info);
}
}
break; break;
} }
} }
@ -108,8 +159,10 @@ fn list_installed_apt_packages<F: Fn(FilterData) -> bool>(filter: F)
} }
fn query_detailed_info<'a, F, V>( fn query_detailed_info<'a, F, V>(
pre_select: PackagePreSelect,
filter: F, filter: F,
view: V, view: V,
depends: Option<&mut HashSet<String>>,
) -> Option<APTUpdateInfo> ) -> Option<APTUpdateInfo>
where where
F: Fn(FilterData) -> bool, F: Fn(FilterData) -> bool,
@ -118,11 +171,25 @@ where
let current_version = view.current_version(); let current_version = view.current_version();
let candidate_version = view.candidate_version(); let candidate_version = view.candidate_version();
let (current_version, candidate_version) = match (current_version, candidate_version) { let (current_version, candidate_version) = match pre_select {
(Some(cur), Some(can)) => (cur, can), // package installed and there is an update PackagePreSelect::OnlyInstalled => match (current_version, candidate_version) {
(Some(cur), None) => (cur.clone(), cur), // package installed and up-to-date (Some(cur), Some(can)) => (Some(cur), can), // package installed and there is an update
(None, Some(_)) => return None, // package could be installed (Some(cur), None) => (Some(cur.clone()), cur), // package installed and up-to-date
(None, None) => return None, // broken (None, Some(_)) => return None, // package could be installed
(None, None) => return None, // broken
},
PackagePreSelect::OnlyNew => match (current_version, candidate_version) {
(Some(_), Some(_)) => return None,
(Some(_), None) => return None,
(None, Some(can)) => (None, can),
(None, None) => return None,
},
PackagePreSelect::All => match (current_version, candidate_version) {
(Some(cur), Some(can)) => (Some(cur), can),
(Some(cur), None) => (Some(cur.clone()), cur),
(None, Some(can)) => (None, can),
(None, None) => return None,
},
}; };
// get additional information via nested APT 'iterators' // get additional information via nested APT 'iterators'
@ -139,7 +206,7 @@ where
let mut long_desc = "".to_owned(); let mut long_desc = "".to_owned();
let fd = FilterData { let fd = FilterData {
installed_version: &current_version, installed_version: current_version.as_deref(),
candidate_version: &candidate_version, candidate_version: &candidate_version,
active_version: &version, active_version: &version,
}; };
@ -192,6 +259,22 @@ where
} }
} }
if let Some(depends) = depends {
let mut dep_iter = ver.dep_iter();
loop {
let dep = match dep_iter.next() {
Some(dep) if dep.dep_type() != "Depends" => continue,
Some(dep) => dep,
None => break
};
let dep_pkg = dep.target_pkg();
let name = dep_pkg.name();
depends.insert(name);
}
}
return Some(APTUpdateInfo { return Some(APTUpdateInfo {
package, package,
title: short_desc, title: short_desc,
@ -200,7 +283,10 @@ where
change_log_url, change_log_url,
origin: origin_res, origin: origin_res,
version: candidate_version.clone(), version: candidate_version.clone(),
old_version: current_version.clone(), old_version: match current_version {
Some(vers) => vers,
None => "".to_owned()
},
priority: priority_res, priority: priority_res,
section: section_res, section: section_res,
}); });
@ -229,10 +315,10 @@ where
)] )]
/// List available APT updates /// List available APT updates
fn apt_update_available(_param: Value) -> Result<Value, Error> { fn apt_update_available(_param: Value) -> Result<Value, Error> {
let all_upgradeable = list_installed_apt_packages(|data| let all_upgradeable = list_installed_apt_packages(|data| {
data.candidate_version == data.active_version && data.candidate_version == data.active_version &&
data.installed_version != data.candidate_version data.installed_version != Some(data.candidate_version)
); }, None);
Ok(json!(all_upgradeable)) Ok(json!(all_upgradeable))
} }