Skip to main content

dot_agent_core/
metadata.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::error::Result;
10
11const META_FILENAME: &str = ".dot-agent-meta.toml";
12
13#[derive(Debug, Serialize, Deserialize)]
14pub struct Metadata {
15    pub installed: InstalledInfo,
16    pub files: HashMap<String, String>,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct InstalledInfo {
21    pub installed_at: DateTime<Utc>,
22    pub profiles: Vec<String>,
23    pub base_dir: String,
24}
25
26impl Metadata {
27    pub fn new(base_dir: &Path) -> Self {
28        Self {
29            installed: InstalledInfo {
30                installed_at: Utc::now(),
31                profiles: Vec::new(),
32                base_dir: base_dir.display().to_string(),
33            },
34            files: HashMap::new(),
35        }
36    }
37
38    pub fn load(target: &Path) -> Result<Option<Self>> {
39        let meta_path = target.join(META_FILENAME);
40        if !meta_path.exists() {
41            return Ok(None);
42        }
43        let content = fs::read_to_string(&meta_path)?;
44        let meta: Metadata = toml::from_str(&content)?;
45        Ok(Some(meta))
46    }
47
48    pub fn save(&self, target: &Path) -> Result<()> {
49        let meta_path = target.join(META_FILENAME);
50        let content = toml::to_string_pretty(self)?;
51        fs::write(&meta_path, content)?;
52        Ok(())
53    }
54
55    pub fn add_profile(&mut self, name: &str) {
56        if !self.installed.profiles.contains(&name.to_string()) {
57            self.installed.profiles.push(name.to_string());
58        }
59    }
60
61    pub fn remove_profile(&mut self, name: &str) {
62        self.installed.profiles.retain(|p| p != name);
63    }
64
65    pub fn add_file(&mut self, path: &str, hash: &str) {
66        self.files.insert(path.to_string(), hash.to_string());
67    }
68
69    pub fn remove_file(&mut self, path: &str) {
70        self.files.remove(path);
71    }
72
73    pub fn get_file_hash(&self, path: &str) -> Option<&String> {
74        self.files.get(path)
75    }
76}
77
78pub fn compute_hash(content: &[u8]) -> String {
79    let mut hasher = Sha256::new();
80    hasher.update(content);
81    let result = hasher.finalize();
82    format!("sha256:{}", hex::encode(result))
83}
84
85pub fn compute_file_hash(path: &Path) -> Result<String> {
86    let content = fs::read(path)?;
87    Ok(compute_hash(&content))
88}