dot_agent_core/install/
metadata.rs1use 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 #[serde(default)]
19 pub merged: HashMap<String, HashMap<String, Vec<String>>>,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub struct InstalledInfo {
24 pub installed_at: DateTime<Utc>,
25 pub profiles: Vec<String>,
26 pub base_dir: String,
27}
28
29impl Metadata {
30 pub fn new(base_dir: &Path) -> Self {
31 Self {
32 installed: InstalledInfo {
33 installed_at: Utc::now(),
34 profiles: Vec::new(),
35 base_dir: base_dir.display().to_string(),
36 },
37 files: HashMap::new(),
38 merged: HashMap::new(),
39 }
40 }
41
42 pub fn load(target: &Path) -> Result<Option<Self>> {
43 let meta_path = target.join(META_FILENAME);
44 if !meta_path.exists() {
45 return Ok(None);
46 }
47 let content = fs::read_to_string(&meta_path)?;
48 let meta: Metadata = toml::from_str(&content)?;
49 Ok(Some(meta))
50 }
51
52 pub fn save(&self, target: &Path) -> Result<()> {
53 let meta_path = target.join(META_FILENAME);
54 let content = toml::to_string_pretty(self)?;
55 fs::write(&meta_path, content)?;
56 Ok(())
57 }
58
59 pub fn add_profile(&mut self, name: &str) {
60 if !self.installed.profiles.contains(&name.to_string()) {
61 self.installed.profiles.push(name.to_string());
62 }
63 }
64
65 pub fn remove_profile(&mut self, name: &str) {
66 self.installed.profiles.retain(|p| p != name);
67 }
68
69 pub fn add_file(&mut self, path: &str, hash: &str) {
70 self.files.insert(path.to_string(), hash.to_string());
71 }
72
73 pub fn remove_file(&mut self, path: &str) {
74 self.files.remove(path);
75 }
76
77 pub fn get_file_hash(&self, path: &str) -> Option<&String> {
78 self.files.get(path)
79 }
80
81 pub fn add_merged(&mut self, profile: &str, file_path: &str, json_paths: Vec<String>) {
83 let profile_merged = self.merged.entry(profile.to_string()).or_default();
84 profile_merged.insert(file_path.to_string(), json_paths);
85 }
86
87 pub fn get_merged(&self, profile: &str, file_path: &str) -> Option<&Vec<String>> {
89 self.merged.get(profile).and_then(|m| m.get(file_path))
90 }
91
92 pub fn get_merged_files(&self, profile: &str) -> Option<&HashMap<String, Vec<String>>> {
94 self.merged.get(profile)
95 }
96
97 pub fn remove_merged(&mut self, profile: &str) {
99 self.merged.remove(profile);
100 }
101}
102
103pub fn compute_hash(content: &[u8]) -> String {
104 let mut hasher = Sha256::new();
105 hasher.update(content);
106 let result = hasher.finalize();
107 format!("sha256:{}", hex::encode(result))
108}
109
110pub fn compute_file_hash(path: &Path) -> Result<String> {
111 let content = fs::read(path)?;
112 Ok(compute_hash(&content))
113}