1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RepoEntry {
9 pub id: String,
11 pub name: String,
12 pub path: PathBuf,
13 pub db_path: PathBuf,
15 pub indexed_at: String,
16 pub node_count: u64,
17 pub edge_count: u64,
18 #[serde(default)]
20 pub language_breakdown: HashMap<String, f64>,
21 #[serde(default)]
24 pub last_used_at: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Registry {
30 #[serde(default = "default_version")]
31 pub version: u32,
32 #[serde(default)]
33 pub repos: Vec<RepoEntry>,
34}
35
36fn default_version() -> u32 {
37 1
38}
39
40impl Registry {
41 fn path() -> PathBuf {
42 dirs::home_dir()
43 .unwrap_or_else(|| PathBuf::from("."))
44 .join(".cgx")
45 .join("registry.json")
46 }
47
48 pub fn load() -> anyhow::Result<Self> {
50 let path = Self::path();
51 if path.exists() {
52 let content = std::fs::read_to_string(&path)?;
53 let registry: Registry = serde_json::from_str(&content)?;
54 Ok(registry)
55 } else {
56 if let Some(dir) = path.parent() {
57 std::fs::create_dir_all(dir)?;
58 }
59 Ok(Registry {
60 version: 1,
61 repos: Vec::new(),
62 })
63 }
64 }
65
66 pub fn save(&self) -> anyhow::Result<()> {
68 let path = Self::path();
69 if let Some(dir) = path.parent() {
70 std::fs::create_dir_all(dir)?;
71 }
72 let content = serde_json::to_string_pretty(self)?;
73 std::fs::write(&path, content)?;
74 Ok(())
75 }
76
77 pub fn register(&mut self, mut entry: RepoEntry) {
79 if entry.last_used_at.is_none() {
80 entry.last_used_at = Some(entry.indexed_at.clone());
81 }
82 self.repos.retain(|r| r.id != entry.id);
83 self.repos.push(entry);
84 }
85
86 pub fn touch_path(path: &Path) -> anyhow::Result<()> {
89 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
90 let mut reg = Self::load()?;
91 let now = chrono::Utc::now().to_rfc3339();
92 let mut changed = false;
93 for r in &mut reg.repos {
94 if r.path.canonicalize().ok().as_ref() == Some(&canonical) {
95 r.last_used_at = Some(now.clone());
96 changed = true;
97 break;
98 }
99 }
100 if changed {
101 reg.save()?;
102 }
103 Ok(())
104 }
105
106 pub fn find_by_path(&self, path: &Path) -> Option<&RepoEntry> {
108 let canonical = path.canonicalize().ok()?;
109 self.repos
110 .iter()
111 .find(|r| r.path.canonicalize().ok().as_ref() == Some(&canonical))
112 }
113
114 pub fn find_by_id(&self, id: &str) -> Option<&RepoEntry> {
116 self.repos.iter().find(|r| r.id == id)
117 }
118}