lean_ctx/core/
vault_map.rs1use std::collections::BTreeMap;
7use std::path::Path;
8
9use serde::{Deserialize, Serialize};
10
11use lean_ctx_protocol::hive::vault::VaultV1;
12
13pub const VAULTS_FILE: &str = "projects.json";
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct VaultRecord {
20 #[serde(rename = "project_id", alias = "vault_id")]
22 pub vault_id: String,
23 pub ns: String,
24 pub name: String,
25 pub sealed: bool,
28 #[serde(default)]
29 pub roots: Vec<String>,
30}
31
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct VaultMap {
35 #[serde(default, rename = "projects", alias = "vaults")]
37 pub vaults: BTreeMap<String, VaultRecord>,
38}
39
40impl VaultMap {
41 pub fn load(data_dir: &Path) -> Self {
42 std::fs::read(data_dir.join(VAULTS_FILE))
43 .ok()
44 .and_then(|bytes| serde_json::from_slice(&bytes).ok())
45 .unwrap_or_default()
46 }
47
48 pub fn for_root(&self, root: &str) -> Option<&VaultRecord> {
52 let find = |root: &str| {
53 self.vaults.values().find(|record| {
54 record
55 .roots
56 .iter()
57 .any(|known| crate::core::pathutil::same_project(known, root))
58 })
59 };
60 find(root).or_else(|| {
61 let main = crate::core::git_worktree::main_worktree_root(Path::new(root))?;
62 find(&main.to_string_lossy())
63 })
64 }
65
66 pub fn save(&self, data_dir: &Path) -> std::io::Result<()> {
67 let bytes = serde_json::to_vec_pretty(self).map_err(std::io::Error::other)?;
68 crate::core::secure_fs::write_private(data_dir, VAULTS_FILE, &bytes)
69 }
70
71 pub fn record(&mut self, stable: &str, vault: &VaultV1, root: &Path) {
73 let root = root.to_string_lossy().into_owned();
74 let entry = self
75 .vaults
76 .entry(stable.to_string())
77 .or_insert_with(|| VaultRecord {
78 vault_id: vault.vault_id.clone(),
79 ns: vault.vault.ns.clone(),
80 name: vault.vault.name.clone(),
81 sealed: false,
82 roots: Vec::new(),
83 });
84 entry.vault_id.clone_from(&vault.vault_id);
85 entry.ns.clone_from(&vault.vault.ns);
86 entry.name.clone_from(&vault.vault.name);
87 entry.sealed = false;
88 if !entry.roots.contains(&root) {
89 entry.roots.push(root);
90 }
91 }
92}