rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
    pub id: u64,
    pub timestamp: DateTime<Utc>,
    pub operation: String,
    pub packages: Vec<PackageHistoryItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageHistoryItem {
    pub name: String,
    pub old_version: Option<String>,
    pub new_version: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Default)]
struct HistoryFile {
    entries: Vec<HistoryEntry>,
    next_id: u64,
}

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

fn load_history_file() -> Result<HistoryFile> {
    let path = history_path();
    if !path.exists() {
        return Ok(HistoryFile::default());
    }
    let content = fs::read_to_string(&path)?;
    Ok(serde_json::from_str(&content)?)
}

fn save_history_file(hf: &HistoryFile) -> Result<()> {
    let path = history_path();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(&path, serde_json::to_string_pretty(hf)?)?;
    Ok(())
}

pub fn record_operation(operation: &str, packages: Vec<PackageHistoryItem>, max_entries: usize) -> Result<u64> {
    let mut hf = load_history_file()?;
    let id = hf.next_id;
    hf.next_id += 1;

    hf.entries.push(HistoryEntry {
        id,
        timestamp: Utc::now(),
        operation: operation.to_string(),
        packages,
    });

    // Trim to max
    if hf.entries.len() > max_entries {
        let drain_count = hf.entries.len() - max_entries;
        hf.entries.drain(0..drain_count);
    }

    save_history_file(&hf)?;
    Ok(id)
}

pub fn list_history(limit: usize) -> Result<Vec<HistoryEntry>> {
    let hf = load_history_file()?;
    let entries: Vec<HistoryEntry> = hf.entries.into_iter().rev().take(limit).collect();
    Ok(entries)
}

pub fn get_entry(id: u64) -> Result<Option<HistoryEntry>> {
    let hf = load_history_file()?;
    Ok(hf.entries.into_iter().find(|e| e.id == id))
}