use crate::Error;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use crate::satisfies::{satisfies_aur_pkg, satisfies_repo_pkg};
use alpm::{Alpm, Dep, DepMod, Depend};
type ConflictMap = HashMap<String, Conflict>;
#[derive(Debug)]
pub struct Actions<'a> {
pub(crate) alpm: &'a Alpm,
pub missing: Vec<Missing>,
pub unneeded: Vec<Unneeded>,
pub build: Vec<Base>,
pub install: Vec<RepoPackage<'a>>,
}
impl<'a> Actions<'a> {
pub fn iter_build_pkgs(&self) -> impl Iterator<Item = &AurPackage> {
self.build.iter().flat_map(|b| &b.pkgs)
}
}
#[derive(Debug, Eq, Clone, PartialEq, Ord, PartialOrd, Hash)]
pub struct Unneeded {
pub name: String,
pub version: String,
}
impl Unneeded {
pub fn new<S: Into<String>>(name: S, version: S) -> Self {
Unneeded {
name: name.into(),
version: version.into(),
}
}
}
#[derive(Debug, Eq, Clone, PartialEq, Ord, PartialOrd, Hash)]
pub struct Package<T> {
pub pkg: T,
pub make: bool,
pub target: bool,
}
pub type AurPackage = Package<raur_ext::Package>;
pub type RepoPackage<'a> = Package<alpm::Package<'a>>;
#[derive(Debug, Eq, Clone, PartialEq, Ord, PartialOrd, Hash)]
pub struct Base {
pub pkgs: Vec<AurPackage>,
}
impl Display for Base {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.pkgs.len() == 1 && self.pkgs[0].pkg.name == self.package_base() {
write!(f, "{}-{}", self.package_base(), self.version())?;
} else {
write!(
f,
"{}-{} ({}",
self.package_base(),
self.version(),
self.pkgs[0].pkg.name
)?;
for pkg in self.pkgs.iter().skip(1) {
f.write_str(" ")?;
f.write_str(&pkg.pkg.name)?;
}
f.write_str(")")?;
}
Ok(())
}
}
impl Base {
pub fn package_base(&self) -> &str {
self.pkgs[0].pkg.package_base.as_str()
}
pub fn version(&self) -> &str {
self.pkgs[0].pkg.version.as_str()
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub struct Conflict {
pub pkg: String,
pub conflicting: Vec<Conflicting>,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub struct Conflicting {
pub pkg: String,
pub conflict: Option<String>,
}
impl Conflict {
pub fn new(pkg: String) -> Self {
Conflict {
pkg,
conflicting: Vec::with_capacity(1),
}
}
pub fn push(&mut self, pkg: String, conflict: &Dep) {
let conflict = if pkg != conflict.name() || conflict.depmod() != DepMod::Any {
Some(conflict.to_string())
} else {
None
};
self.conflicting.push(Conflicting { pkg, conflict });
}
}
#[derive(Debug)]
pub struct AurUpdate<'a> {
pub local: alpm::Package<'a>,
pub remote: raur_ext::Package,
}
#[derive(Debug, Default)]
pub struct AurUpdates<'a> {
pub updates: Vec<AurUpdate<'a>>,
pub missing: Vec<alpm::Package<'a>>,
pub ignored: Vec<AurUpdate<'a>>,
}
#[derive(Debug, Clone, Default)]
pub struct Missing {
pub dep: String,
pub stack: Vec<String>,
}
impl<'a> Actions<'a> {
fn has_pkg<S: AsRef<str>>(&self, name: S) -> bool {
let name = name.as_ref();
let install = &self.install;
self.iter_build_pkgs().any(|pkg| pkg.pkg.name == name)
|| install.iter().any(|pkg| pkg.pkg.name() == name)
}
fn check_reverse_conflict<S: AsRef<str>>(
&self,
name: S,
conflict: &Dep,
conflicts: &mut ConflictMap,
) {
let name = name.as_ref();
self.install
.iter()
.map(|pkg| &pkg.pkg)
.filter(|pkg| pkg.name() != name)
.filter(|pkg| satisfies_repo_pkg(conflict, pkg, false))
.for_each(|pkg| {
conflicts
.entry(pkg.name().to_string())
.or_insert_with(|| Conflict::new(pkg.name().to_string()))
.push(name.to_string(), conflict);
});
self.iter_build_pkgs()
.map(|pkg| &pkg.pkg)
.filter(|pkg| pkg.name != name)
.filter(|pkg| satisfies_aur_pkg(conflict, pkg, false))
.for_each(|pkg| {
conflicts
.entry(pkg.name.to_string())
.or_insert_with(|| Conflict::new(pkg.name.to_string()))
.push(name.to_string(), conflict);
});
}
fn check_forward_conflict<S: AsRef<str>>(
&self,
name: S,
conflict: &Dep,
conflicts: &mut ConflictMap,
) {
let name = name.as_ref();
self.alpm
.localdb()
.pkgs()
.iter()
.filter(|pkg| !self.has_pkg(pkg.name()))
.filter(|pkg| pkg.name() != name)
.filter(|pkg| satisfies_repo_pkg(conflict, pkg, false))
.for_each(|pkg| {
conflicts
.entry(name.to_string())
.or_insert_with(|| Conflict::new(name.to_string()))
.push(pkg.name().to_string(), conflict);
});
}
fn check_forward_conflicts(&self, conflicts: &mut ConflictMap) {
for pkg in self.install.iter() {
for conflict in pkg.pkg.conflicts() {
self.check_forward_conflict(pkg.pkg.name(), &conflict, conflicts);
}
}
for pkg in self.iter_build_pkgs() {
for conflict in &pkg.pkg.conflicts {
self.check_forward_conflict(
&pkg.pkg.name,
&Depend::new(conflict.to_string()),
conflicts,
);
}
}
}
fn check_inner_conflicts(&self, conflicts: &mut ConflictMap) {
for pkg in self.install.iter() {
for conflict in pkg.pkg.conflicts() {
self.check_reverse_conflict(pkg.pkg.name(), &conflict, conflicts)
}
}
for pkg in self.iter_build_pkgs() {
for conflict in pkg.pkg.conflicts.iter() {
self.check_reverse_conflict(
&pkg.pkg.name,
&Depend::new(conflict.to_string()),
conflicts,
)
}
}
}
fn check_reverse_conflicts(&self, conflicts: &mut ConflictMap) {
self.alpm
.localdb()
.pkgs()
.iter()
.filter(|pkg| !self.has_pkg(pkg.name()))
.for_each(|pkg| {
pkg.conflicts().iter().for_each(|conflict| {
self.check_reverse_conflict(pkg.name(), &conflict, conflicts)
})
});
}
pub fn calculate_conflicts(&self) -> Vec<Conflict> {
let mut conflicts = ConflictMap::new();
self.check_reverse_conflicts(&mut conflicts);
self.check_forward_conflicts(&mut conflicts);
let mut conflicts = conflicts
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<Conflict>>();
conflicts.sort();
conflicts
}
pub fn calculate_inner_conflicts(&self) -> Result<Vec<Conflict>, Error> {
let mut inner_conflicts = ConflictMap::new();
self.check_inner_conflicts(&mut inner_conflicts);
let mut inner_conflicts = inner_conflicts
.into_iter()
.map(|(_, v)| v)
.collect::<Vec<Conflict>>();
inner_conflicts.sort();
Ok(inner_conflicts)
}
pub fn duplicate_targets(&self) -> Vec<String> {
let mut names = HashSet::new();
let build = self.iter_build_pkgs().map(|pkg| pkg.pkg.name.as_str());
let duplicates = self
.install
.iter()
.map(|pkg| pkg.pkg.name())
.chain(build)
.filter(|&name| !names.insert(name))
.map(Into::into)
.collect::<Vec<_>>();
duplicates
}
}