rustpm 0.1.0

A fast, friendly APT frontend with kernel, desktop, and sources management
use anyhow::{bail, Result};
use std::collections::HashSet;
use std::process::Command;

use super::profiles::{find_profile, DesktopProfile, PROFILES};

pub struct InstalledStatus {
    pub profile: &'static DesktopProfile,
    pub installed: bool,
}

fn dpkg_installed() -> HashSet<String> {
    let stdout = Command::new("dpkg-query")
        .args(["-f", "${Package}\\t${db:Status-Abbrev}\\n", "-W"])
        .output()
        .map(|o| o.stdout)
        .unwrap_or_default();

    String::from_utf8_lossy(&stdout)
        .lines()
        .filter_map(|line| {
            let mut parts = line.splitn(2, '\t');
            let pkg = parts.next()?.to_string();
            let status = parts.next().unwrap_or("").trim();
            if status.starts_with("ii") { Some(pkg) } else { None }
        })
        .collect()
}

pub fn list_desktops() -> Vec<InstalledStatus> {
    let installed = dpkg_installed();

    PROFILES
        .iter()
        .map(|p| {
            let is_installed = p.packages.iter().any(|pkg| installed.contains(*pkg));
            InstalledStatus { profile: p, installed: is_installed }
        })
        .collect()
}

pub fn install_desktop(name: &str) -> Result<()> {
    let profile = find_profile(name)
        .ok_or_else(|| anyhow::anyhow!("Unknown desktop environment: {}", name))?;

    let mut args = vec!["install", "-y"];
    args.extend(profile.packages.iter().copied());

    let status = Command::new("apt-get").args(&args).status()?;

    if !status.success() {
        bail!("Failed to install {}", profile.display_name);
    }

    Ok(())
}

pub fn remove_desktop(name: &str) -> Result<()> {
    let profile = find_profile(name)
        .ok_or_else(|| anyhow::anyhow!("Unknown desktop environment: {}", name))?;

    let mut args = vec!["remove", "-y"];
    args.extend(profile.packages.iter().copied());

    let status = Command::new("apt-get").args(&args).status()?;

    if !status.success() {
        bail!("Failed to remove {}", profile.display_name);
    }

    Ok(())
}

pub fn switch_desktop(name: &str) -> Result<()> {
    install_desktop(name)?;

    let profile = find_profile(name).unwrap();

    let status = Command::new("dpkg-reconfigure")
        .args([profile.display_manager])
        .status()?;

    if !status.success() {
        bail!("Failed to reconfigure display manager to {}", profile.display_manager);
    }

    println!(
        "Display manager switched to {}. Restart display manager to apply.",
        profile.display_manager
    );

    Ok(())
}