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,
});
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))
}