rustpm 0.2.6

A fast, friendly APT frontend with kernel, desktop, and sources management
use anyhow::Result;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone)]
pub struct Config {
    pub color: bool,
    pub auto_update_grub: bool,
    pub kernel_filter: KernelFilter,
    pub history_max_entries: usize,
    pub updates: UpdateConfig,
}

#[derive(Debug, Clone, Default)]
pub struct KernelFilter {
    pub hide_debug: bool,
    pub hide_unsigned: bool,
    pub hide_rt: bool,
}

#[derive(Debug, Clone)]
pub struct UpdateConfig {
    pub check_enabled: bool,
    pub check_url: String,
    pub last_checked: Option<String>,
    pub check_interval_hours: u64,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            color: true,
            auto_update_grub: true,
            kernel_filter: KernelFilter::default(),
            history_max_entries: 50,
            updates: UpdateConfig::default(),
        }
    }
}

impl Default for UpdateConfig {
    fn default() -> Self {
        Self {
            check_enabled: true,
            check_url: "https://crates.io/api/v1/crates/rustpm".to_string(),
            last_checked: None,
            check_interval_hours: 24,
        }
    }
}

fn config_path() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
    PathBuf::from(home).join(".config").join("rustpm").join("config")
}

impl Config {
    pub fn load() -> Result<Self> {
        let path = config_path();
        if !path.exists() {
            return Ok(Self::default());
        }
        let content = fs::read_to_string(&path)?;
        Ok(Self::parse(&content))
    }

    fn parse(content: &str) -> Self {
        let mut cfg = Self::default();
        for line in content.lines() {
            let line = line.trim();
            if line.starts_with('#') || line.is_empty() {
                continue;
            }
            if let Some((key, val)) = line.split_once('=') {
                let key = key.trim();
                let val = val.trim();
                match key {
                    "color"               => cfg.color = val == "true",
                    "auto_update_grub"    => cfg.auto_update_grub = val == "true",
                    "history_max_entries" => cfg.history_max_entries = val.parse().unwrap_or(50),
                    "hide_debug"          => cfg.kernel_filter.hide_debug = val == "true",
                    "hide_unsigned"       => cfg.kernel_filter.hide_unsigned = val == "true",
                    "hide_rt"             => cfg.kernel_filter.hide_rt = val == "true",
                    "check_enabled"       => cfg.updates.check_enabled = val == "true",
                    "check_url"           => cfg.updates.check_url = val.to_string(),
                    "check_interval_hours" => cfg.updates.check_interval_hours = val.parse().unwrap_or(24),
                    _ => {}
                }
            }
        }
        cfg
    }

    pub fn save(&self) -> Result<()> {
        let path = config_path();
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        let content = format!(
            "color = {}\nauto_update_grub = {}\nhistory_max_entries = {}\n\
             hide_debug = {}\nhide_unsigned = {}\nhide_rt = {}\n\n\
             check_enabled = {}\ncheck_url = {}\ncheck_interval_hours = {}\n",
            self.color,
            self.auto_update_grub,
            self.history_max_entries,
            self.kernel_filter.hide_debug,
            self.kernel_filter.hide_unsigned,
            self.kernel_filter.hide_rt,
            self.updates.check_enabled,
            self.updates.check_url,
            self.updates.check_interval_hours,
        );
        fs::write(&path, content)?;
        Ok(())
    }
}