bpt 0.1.6

Bedrock Linux package manager
use crate::{cli::*, collection::*, color::Color, error::*, file::*, metadata::*};

#[derive(Clone, Copy)]
enum BackupCheckMode {
    Warn,
    Strict,
    Ignore,
}

pub fn check(
    flags: CommonFlags,
    pkgs: Vec<PartId>,
    strict: bool,
    ignore_backup: bool,
) -> Result<String, Err> {
    let backup_mode = match (strict, ignore_backup) {
        (true, true) => {
            return Err(Err::InputFieldInvalid(
                "check options",
                "--strict and --ignore-backup cannot be used together".to_string(),
            ));
        }
        (true, false) => BackupCheckMode::Strict,
        (false, true) => BackupCheckMode::Ignore,
        (false, false) => BackupCheckMode::Warn,
    };

    let bpt_conf = &BptConf::from_root_path(&flags.root_dir)?;
    let installed_pkgs = &InstalledPkgs::from_root_path_ro(&flags.root_dir)?;
    let archs = &bpt_conf.general.default_archs;

    let mut targets = Vec::new();
    if pkgs.is_empty() {
        targets.extend(installed_pkgs.pkgids().cloned());
    } else {
        for partid in pkgs {
            let pkgid = installed_pkgs
                .best_match(&partid, archs)
                .map(|instpkg| instpkg.pkgid().clone())
                .ok_or_else(|| Err::UnableToLocateInstalledPkg(partid.clone()))?;
            targets.push(pkgid);
        }
    }

    if targets.is_empty() {
        return Ok("No installed packages to check".to_owned());
    }

    targets.sort();
    targets.dedup();

    let mut error_issues_by_pkg = Vec::new();
    let mut warning_issues_by_pkg = Vec::new();
    for pkgid in &targets {
        let instpkg = installed_pkgs
            .get(pkgid)
            .expect("selected installed package disappeared during check");
        let mut errors = Vec::new();
        let mut warnings = Vec::new();
        for issue in instpkg.check(flags.root_dir.as_path())? {
            match issue {
                InstPkgCheckIssue::Error(message) => errors.push(message),
                InstPkgCheckIssue::BackupDiff(message) => match backup_mode {
                    BackupCheckMode::Warn => warnings.push(message),
                    BackupCheckMode::Strict => errors.push(message),
                    BackupCheckMode::Ignore => {}
                },
            }
        }
        if !errors.is_empty() {
            error_issues_by_pkg.push((pkgid.color().to_string(), errors));
        }
        if !warnings.is_empty() {
            warning_issues_by_pkg.push((pkgid.color().to_string(), warnings));
        }
    }

    if !warning_issues_by_pkg.is_empty() {
        println!(
            "{}Warning:{} Installed backup file differences:\n{}",
            Color::Warn,
            Color::Default,
            format_check_report(&warning_issues_by_pkg),
        );
    }

    if !error_issues_by_pkg.is_empty() {
        return Err(Err::CheckFailed(format_check_report(&error_issues_by_pkg)));
    }

    if targets.len() == 1 {
        Ok(format!("Checked installed package {}", targets[0]))
    } else {
        Ok(format!("Checked all {} installed packages", targets.len()))
    }
}

fn format_check_report(issues_by_pkg: &[(String, Vec<String>)]) -> String {
    let mut out = String::new();

    for (idx, (pkgid, issues)) in issues_by_pkg.iter().enumerate() {
        if idx != 0 {
            out.push('\n');
        }
        out.push_str(pkgid);
        out.push('\n');
        for issue in issues {
            out.push_str("  ");
            out.push_str(issue);
            out.push('\n');
        }
    }

    if out.ends_with('\n') {
        out.pop();
    }

    out
}

#[cfg(test)]
mod tests {
    use crate::command::check::format_check_report;

    #[test]
    fn format_check_report_groups_issues_by_pkg() {
        let report = format_check_report(&[
            (
                "fakeblock@1.0.0:noarch".to_owned(),
                vec![
                    "Missing: /root/usr/bin/fakeblock".to_owned(),
                    "Incorrect mode: /root/etc/fakeblock.conf (expected 644; found 600)".to_owned(),
                ],
            ),
            (
                "fakeblock-songs@1.0.0:noarch".to_owned(),
                vec!["Incorrect sha256: /root/usr/share/fakeblock/songs/main-theme".to_owned()],
            ),
        ]);

        assert_eq!(
            report,
            concat!(
                "fakeblock@1.0.0:noarch\n",
                "  Missing: /root/usr/bin/fakeblock\n",
                "  Incorrect mode: /root/etc/fakeblock.conf (expected 644; found 600)\n",
                "\n",
                "fakeblock-songs@1.0.0:noarch\n",
                "  Incorrect sha256: /root/usr/share/fakeblock/songs/main-theme"
            )
        );
    }
}