use std::fmt::Display;
use ahash::HashMap;
use cxx::UniquePtr;
use oma_apt::{
BaseDep, DepType, Dependency, Package, PackageFile, Version,
cache::Cache,
raw::{IntoRawIter, PkgIterator, VerIterator},
records::RecordField,
};
use oma_utils::human_bytes::HumanBytes;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::apt::{OmaAptError, OmaAptResult};
#[derive(Debug, Serialize, Deserialize)]
pub struct OmaDependency {
pub name: String,
pub comp_symbol: Option<String>,
pub ver: Option<String>,
pub target_ver: Option<String>,
pub comp_ver: Option<String>,
}
impl From<&BaseDep<'_>> for OmaDependency {
fn from(dep: &BaseDep) -> Self {
Self {
name: dep.name().to_owned(),
comp_symbol: dep.comp_type().map(|x| x.to_string()),
ver: dep.version().map(|x| x.to_string()),
target_ver: dep.target_ver().ok().map(|x| x.to_string()),
comp_ver: dep
.comp_type()
.and_then(|x| Some(format!("{x} {}", dep.version()?))),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OmaDependencyGroup(Vec<Vec<OmaDependency>>);
impl OmaDependencyGroup {
pub fn inner(self) -> Vec<Vec<OmaDependency>> {
self.0
}
}
impl Display for OmaDependencyGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, d) in self.0.iter().enumerate() {
if d.len() == 1 {
let dep = d.first().unwrap();
f.write_str(&dep.name)?;
if let Some(comp) = &dep.comp_ver {
f.write_str(&format!(" ({comp})"))?;
}
if i != self.0.len() - 1 {
f.write_str(", ")?;
}
} else {
let total = d.len() - 1;
for (num, base_dep) in d.iter().enumerate() {
f.write_str(&base_dep.name)?;
if let Some(comp) = &base_dep.comp_ver {
f.write_str(&format!(" ({comp})"))?;
}
if i != self.0.len() - 1 {
if num != total {
f.write_str(" | ")?;
} else {
f.write_str(", ")?;
}
}
}
}
}
Ok(())
}
}
impl OmaDependency {
pub fn map_deps(deps: &[Dependency]) -> OmaDependencyGroup {
let mut res = vec![];
for dep in deps {
if dep.is_or() {
let mut v = vec![];
for base_dep in dep.iter() {
v.push(Self::from(base_dep));
}
res.push(v);
} else {
let lone_dep = dep.first();
res.push(vec![Self::from(lone_dep)]);
}
}
OmaDependencyGroup(res)
}
}
#[derive(Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum OmaDepType {
Depends,
PreDepends,
Suggests,
Recommends,
Conflicts,
Replaces,
Obsoletes,
Breaks,
Enhances,
}
impl Display for OmaDepType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<&DepType> for OmaDepType {
fn from(v: &oma_apt::DepType) -> Self {
match v {
oma_apt::DepType::Depends => OmaDepType::Depends,
oma_apt::DepType::PreDepends => OmaDepType::PreDepends,
oma_apt::DepType::Suggests => OmaDepType::Suggests,
oma_apt::DepType::Recommends => OmaDepType::Recommends,
oma_apt::DepType::Conflicts => OmaDepType::Conflicts,
oma_apt::DepType::Replaces => OmaDepType::Replaces,
oma_apt::DepType::Obsoletes => OmaDepType::Obsoletes,
oma_apt::DepType::DpkgBreaks => OmaDepType::Breaks,
oma_apt::DepType::Enhances => OmaDepType::Enhances,
}
}
}
pub struct OmaPackage {
pub version_raw: UniquePtr<VerIterator>,
pub raw_pkg: UniquePtr<PkgIterator>,
}
pub struct OmaPackageWithoutVersion {
pub raw_pkg: UniquePtr<PkgIterator>,
}
#[derive(Debug, Error)]
#[error("BUG: pointer should is some")]
pub struct PtrIsNone;
#[derive(Debug, Deserialize, Serialize)]
pub struct PackageInfo {
pub package: Box<str>,
pub version: Box<str>,
pub section: Box<str>,
pub maintainer: String,
pub install_size: u64,
pub dep_map: HashMap<OmaDepType, OmaDependencyGroup>,
pub download_size: u64,
pub apt_sources: Vec<AptSource>,
pub description: String,
pub short_description: String,
}
impl Display for PackageInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let PackageInfo {
package,
version,
section,
maintainer,
install_size,
dep_map,
download_size,
apt_sources,
description,
..
} = self;
writeln!(f, "Package: {package}")?;
writeln!(f, "Version: {version}")?;
writeln!(f, "Section: {section}")?;
writeln!(f, "Maintainer: {maintainer}")?;
writeln!(f, "Install-Size: {}", HumanBytes(*install_size))?;
for (k, v) in dep_map {
writeln!(f, "{k}: {v}")?;
}
writeln!(f, "Download-Size: {}", HumanBytes(*download_size))?;
write!(f, "APT-Sources:")?;
let apt_sources_without_dpkg = apt_sources
.iter()
.filter(|x| x.index_type.as_deref() != Some("Debian dpkg status file"))
.collect::<Vec<_>>();
match apt_sources_without_dpkg.len() {
0 => writeln!(f, " {}", &apt_sources[0])?,
1 => writeln!(f, " {}", &apt_sources_without_dpkg[0])?,
2.. => {
writeln!(f)?;
for i in apt_sources_without_dpkg {
writeln!(f, " {i}")?;
}
}
}
writeln!(f, "Description: {description}")?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct AptSource {
pub archive: Option<Box<str>>,
pub component: Option<Box<str>>,
pub arch: Option<Box<str>>,
pub index_type: Option<Box<str>>,
pub archive_uri: String,
}
impl From<PackageFile<'_>> for AptSource {
fn from(value: PackageFile<'_>) -> Self {
let index = value.index_file();
let archive_uri = index.archive_uri("");
Self {
archive: value.archive().map(Box::from),
component: value.component().map(Box::from),
arch: value.arch().map(Box::from),
index_type: value.index_type().map(Box::from),
archive_uri,
}
}
}
impl Display for AptSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.archive_uri)?;
if let Some(archive) = &self.archive {
write!(f, " {archive}")?;
}
if let Some(comp) = &self.component {
write!(f, "/{comp}")?;
}
if let Some(arch) = &self.arch {
write!(f, " {arch}")?;
}
if let Some(ft) = &self.index_type {
let ft = match ft.as_ref() {
"Debian Package Index" => "Packages",
"Debian Translation Index" => "Translation",
_ => "",
};
write!(f, " {ft}")?;
}
Ok(())
}
}
impl OmaPackage {
pub fn new(version: &Version, pkg: &Package) -> Result<Self, PtrIsNone> {
let raw_pkg = unsafe { pkg.unique() }.make_safe().ok_or(PtrIsNone)?;
let version_raw = unsafe { version.unique() }.make_safe().ok_or(PtrIsNone)?;
Ok(Self {
version_raw,
raw_pkg,
})
}
pub fn try_clone(&self) -> Result<Self, PtrIsNone> {
Ok(Self {
version_raw: unsafe { self.version_raw.unique() }
.make_safe()
.ok_or(PtrIsNone)?,
raw_pkg: unsafe { self.raw_pkg.unique() }
.make_safe()
.ok_or(PtrIsNone)?,
})
}
pub fn pkg_info(&self, cache: &Cache) -> OmaAptResult<PackageInfo> {
let package: Box<str> = Box::from(self.raw_pkg.fullname(true));
let version: Box<str> = Box::from(self.version_raw.version());
let ver = Version::new(
unsafe { self.version_raw.unique() }
.make_safe()
.ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
cache,
);
let section: Box<str> = Box::from(ver.section().unwrap_or("unknown"));
let maintainer = ver
.get_record(RecordField::Maintainer)
.unwrap_or_else(|| "unknown".to_string());
let install_size = ver.installed_size();
let deps_map = self.get_deps(cache)?;
let download_size = ver.size();
let pkg_files = ver.package_files().map(AptSource::from).collect::<Vec<_>>();
let description = ver
.description()
.unwrap_or_else(|| "No description".to_string());
let short_description = ver
.summary()
.unwrap_or_else(|| "No description".to_string());
Ok(PackageInfo {
package,
version,
section,
maintainer,
install_size,
dep_map: deps_map,
download_size,
apt_sources: pkg_files,
description,
short_description,
})
}
pub fn package<'a>(&'a self, cache: &'a Cache) -> Package<'a> {
Package::new(cache, unsafe { self.raw_pkg.unique() })
}
pub fn version<'a>(&'a self, cache: &'a Cache) -> Version<'a> {
Version::new(unsafe { self.version_raw.unique() }, cache)
}
pub fn is_candidate_version(&self, cache: &Cache) -> bool {
self.package(cache)
.candidate()
.is_some_and(|cand| cand == self.version(cache))
}
pub fn get_deps(&self, cache: &Cache) -> OmaAptResult<HashMap<OmaDepType, OmaDependencyGroup>> {
let map = Version::new(
unsafe { self.version_raw.unique() }
.make_safe()
.ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
cache,
)
.depends_map()
.iter()
.map(|(x, y)| (OmaDepType::from(x), OmaDependency::map_deps(y)))
.collect::<HashMap<_, _>>();
Ok(map)
}
pub fn get_rdeps(
&self,
cache: &Cache,
) -> OmaAptResult<HashMap<OmaDepType, OmaDependencyGroup>> {
let map = Package::new(
cache,
unsafe { self.raw_pkg.unique() }
.make_safe()
.ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
)
.rdepends()
.iter()
.map(|(x, y)| (OmaDepType::from(x), OmaDependency::map_deps(y)))
.collect::<HashMap<_, _>>();
Ok(map)
}
pub fn into_oma_package_without_version(&self) -> Result<OmaPackageWithoutVersion, PtrIsNone> {
Ok(OmaPackageWithoutVersion {
raw_pkg: unsafe { self.raw_pkg.unique() }
.make_safe()
.ok_or(PtrIsNone)?,
})
}
}
impl OmaPackageWithoutVersion {
pub fn package<'a>(&'a self, cache: &'a Cache) -> Package<'a> {
Package::new(cache, unsafe { self.raw_pkg.unique() })
}
pub fn into_oma_package(&self, version: &Version) -> Result<OmaPackage, PtrIsNone> {
Ok(OmaPackage {
version_raw: unsafe { version.unique() }.make_safe().ok_or(PtrIsNone)?,
raw_pkg: unsafe { self.raw_pkg.unique() }
.make_safe()
.ok_or(PtrIsNone)?,
})
}
}
impl TryFrom<OmaPackage> for OmaPackageWithoutVersion {
type Error = PtrIsNone;
fn try_from(value: OmaPackage) -> Result<Self, Self::Error> {
value.into_oma_package_without_version()
}
}
#[test]
fn test_pkginfo_display() {
use crate::test::TEST_LOCK;
use oma_apt::new_cache;
let _lock = TEST_LOCK.lock().unwrap();
let cache = new_cache!().unwrap();
let pkg = cache.get("apt").unwrap();
let version = pkg.candidate().unwrap();
let info = OmaPackage::new(&version, &pkg).unwrap();
let info = info.pkg_info(&cache).unwrap();
println!("{info}");
}