Skip to main content

interstice_cli/
node_registry.rs

1use crate::data_directory::data_file;
2use interstice_core::IntersticeError;
3use serde::{Deserialize, Serialize};
4use std::{
5    fs,
6    path::PathBuf,
7    time::{SystemTime, UNIX_EPOCH},
8};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct NodeRecord {
12    pub name: String,
13    pub address: String,
14    pub node_id: Option<String>,
15    pub local: bool,
16    pub last_seen: Option<u64>,
17}
18
19#[derive(Debug, Default, Serialize, Deserialize)]
20pub struct NodeRegistry {
21    pub nodes: Vec<NodeRecord>,
22}
23
24impl NodeRegistry {
25    pub fn load() -> Result<Self, IntersticeError> {
26        let path = registry_path();
27        if path.exists() {
28            let contents = fs::read_to_string(&path).map_err(|err| {
29                IntersticeError::Internal(format!(
30                    "Failed to read node registry {}: {err}",
31                    path.display()
32                ))
33            })?;
34            if contents.trim().is_empty() {
35                return Ok(Self::default());
36            }
37            toml::from_str(&contents).map_err(|err| {
38                IntersticeError::Internal(format!(
39                    "Failed to parse node registry {}: {err}",
40                    path.display()
41                ))
42            })
43        } else {
44            Ok(Self::default())
45        }
46    }
47
48    pub fn save(&self) -> Result<(), IntersticeError> {
49        let path = registry_path();
50        let contents = toml::to_string_pretty(self).map_err(|err| {
51            IntersticeError::Internal(format!("Failed to serialize node registry: {err}"))
52        })?;
53        fs::write(&path, contents).map_err(|err| {
54            IntersticeError::Internal(format!(
55                "Failed to write node registry {}: {err}",
56                path.display()
57            ))
58        })?;
59        Ok(())
60    }
61
62    pub fn add(&mut self, record: NodeRecord) -> Result<(), IntersticeError> {
63        if self.nodes.iter().any(|n| n.name == record.name) {
64            return Err(IntersticeError::Internal(format!(
65                "Node name '{}' already exists",
66                record.name
67            )));
68        }
69        self.nodes.push(record);
70        self.save()
71    }
72
73    pub fn remove(&mut self, name_or_id: &str) -> Result<NodeRecord, IntersticeError> {
74        let index = self
75            .nodes
76            .iter()
77            .position(|n| n.name == name_or_id || n.node_id.as_deref() == Some(name_or_id))
78            .ok_or_else(|| IntersticeError::Internal(format!("Unknown node '{}'", name_or_id)))?;
79        let removed = self.nodes.remove(index);
80        self.save()?;
81        Ok(removed)
82    }
83
84    pub fn rename(&mut self, old: &str, new: &str) -> Result<(), IntersticeError> {
85        if self.nodes.iter().any(|n| n.name == new) {
86            return Err(IntersticeError::Internal(format!(
87                "Node name '{}' already exists",
88                new
89            )));
90        }
91        let node = self
92            .nodes
93            .iter_mut()
94            .find(|n| n.name == old || n.node_id.as_deref() == Some(old))
95            .ok_or_else(|| IntersticeError::Internal(format!("Unknown node '{}'", old)))?;
96        node.name = new.to_string();
97        self.save()
98    }
99
100    pub fn get(&self, name_or_id: &str) -> Option<&NodeRecord> {
101        self.nodes
102            .iter()
103            .find(|n| n.name == name_or_id || n.node_id.as_deref() == Some(name_or_id))
104    }
105
106    pub fn get_mut(&mut self, name_or_id: &str) -> Option<&mut NodeRecord> {
107        self.nodes
108            .iter_mut()
109            .find(|n| n.name == name_or_id || n.node_id.as_deref() == Some(name_or_id))
110    }
111
112    pub fn resolve_address(&self, name_or_address: &str) -> Option<String> {
113        if name_or_address.contains(':') {
114            return Some(name_or_address.to_string());
115        }
116        self.get(name_or_address).map(|n| n.address.clone())
117    }
118
119    pub fn list_sorted(&self) -> Vec<NodeRecord> {
120        let mut nodes = self.nodes.clone();
121        nodes.sort_by(|a, b| a.name.cmp(&b.name));
122        nodes
123    }
124
125    pub fn set_last_seen(&mut self, name_or_id: &str) {
126        if let Some(node) = self.get_mut(name_or_id) {
127            node.last_seen = Some(now_epoch());
128        }
129    }
130
131    pub fn set_node_id(&mut self, name_or_id: &str, node_id: String) {
132        if let Some(node) = self.get_mut(name_or_id) {
133            node.node_id = Some(node_id);
134        }
135    }
136}
137
138fn registry_path() -> PathBuf {
139    data_file().join("nodes.toml")
140}
141
142fn now_epoch() -> u64 {
143    SystemTime::now()
144        .duration_since(UNIX_EPOCH)
145        .unwrap_or_default()
146        .as_secs()
147}