interstice_cli/
node_registry.rs1use 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}