1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct Device {
6 pub node_id: u64,
7 pub address: String,
8 pub name: String,
9}
10
11pub(crate) struct DeviceRegistry {
12 path: String,
13 devices: Vec<Device>,
14}
15
16impl DeviceRegistry {
17 pub fn load(path: &str) -> Result<Self> {
18 let devices = match std::fs::read_to_string(path) {
19 Ok(data) => serde_json::from_str(&data).context("parsing devices.json")?,
20 Err(_) => Vec::new(),
21 };
22 Ok(Self {
23 path: path.to_owned(),
24 devices,
25 })
26 }
27
28 fn save(&self) -> Result<()> {
29 let data = serde_json::to_string_pretty(&self.devices)?;
30 std::fs::write(&self.path, data).context(format!("writing devices to {}", self.path))
31 }
32
33 pub fn add(&mut self, device: Device) -> Result<()> {
34 if let Some(existing) = self.devices.iter().find(|d| d.name == device.name) {
36 if existing.node_id != device.node_id {
37 anyhow::bail!("device name '{}' already in use by node {}", device.name, existing.node_id);
38 }
39 }
40 if let Some(pos) = self.devices.iter().position(|d| d.node_id == device.node_id) {
42 self.devices[pos] = device;
43 } else {
44 self.devices.push(device);
45 }
46 self.save()
47 }
48
49 pub fn remove(&mut self, node_id: u64) -> Result<()> {
50 self.devices.retain(|d| d.node_id != node_id);
51 self.save()
52 }
53
54 pub fn get(&self, node_id: u64) -> Option<&Device> {
55 self.devices.iter().find(|d| d.node_id == node_id)
56 }
57
58 pub fn get_by_name(&self, name: &str) -> Option<&Device> {
59 self.devices.iter().find(|d| d.name == name)
60 }
61
62 pub fn list(&self) -> &[Device] {
63 &self.devices
64 }
65
66 pub fn update_address(&mut self, node_id: u64, address: &str) -> Result<()> {
67 let dev = self.devices.iter_mut().find(|d| d.node_id == node_id)
68 .context(format!("device {} not found", node_id))?;
69 dev.address = address.to_owned();
70 self.save()
71 }
72
73 pub fn rename(&mut self, node_id: u64, name: &str) -> Result<()> {
74 if let Some(existing) = self.devices.iter().find(|d| d.name == name) {
76 if existing.node_id != node_id {
77 anyhow::bail!("device name '{}' already in use by node {}", name, existing.node_id);
78 }
79 }
80 let dev = self.devices.iter_mut().find(|d| d.node_id == node_id)
81 .context(format!("device {} not found", node_id))?;
82 dev.name = name.to_owned();
83 self.save()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn test_path(name: &str) -> String {
92 let dir = std::env::temp_dir().join(format!("matc_test_{}", name));
93 let _ = std::fs::remove_dir_all(&dir);
94 std::fs::create_dir_all(&dir).unwrap();
95 println!("Using test directory: {:?}", dir);
96 dir.join("devices.json").to_str().unwrap().to_owned()
97 }
98
99 #[test]
100 fn registry_round_trip() {
101 let path = test_path("reg_rt");
102
103 let mut reg = DeviceRegistry::load(&path).unwrap();
104 assert!(reg.list().is_empty());
105
106 reg.add(Device { node_id: 1, address: "1.2.3.4:5540".into(), name: "light".into() }).unwrap();
107 reg.add(Device { node_id: 2, address: "1.2.3.5:5540".into(), name: "switch".into() }).unwrap();
108 assert_eq!(reg.list().len(), 2);
109
110 let reg2 = DeviceRegistry::load(&path).unwrap();
112 assert_eq!(reg2.list().len(), 2);
113 assert_eq!(reg2.get(1).unwrap().name, "light");
114 assert_eq!(reg2.get_by_name("switch").unwrap().node_id, 2);
115 }
116
117 #[test]
118 fn registry_replace_by_node_id() {
119 let path = test_path("reg_replace");
120
121 let mut reg = DeviceRegistry::load(&path).unwrap();
122 reg.add(Device { node_id: 1, address: "1.2.3.4:5540".into(), name: "light".into() }).unwrap();
123 reg.add(Device { node_id: 1, address: "1.2.3.5:5540".into(), name: "light2".into() }).unwrap();
124 assert_eq!(reg.list().len(), 1);
125 assert_eq!(reg.get(1).unwrap().name, "light2");
126 }
127
128 #[test]
129 fn registry_unique_names() {
130 let path = test_path("reg_unique");
131
132 let mut reg = DeviceRegistry::load(&path).unwrap();
133 reg.add(Device { node_id: 1, address: "1.2.3.4:5540".into(), name: "light".into() }).unwrap();
134 let err = reg.add(Device { node_id: 2, address: "1.2.3.5:5540".into(), name: "light".into() });
135 assert!(err.is_err());
136 }
137
138 #[test]
139 fn registry_rename_and_update_address() {
140 let path = test_path("reg_rename");
141
142 let mut reg = DeviceRegistry::load(&path).unwrap();
143 reg.add(Device { node_id: 1, address: "1.2.3.4:5540".into(), name: "light".into() }).unwrap();
144 reg.rename(1, "kitchen light").unwrap();
145 assert_eq!(reg.get(1).unwrap().name, "kitchen light");
146
147 reg.update_address(1, "10.0.0.1:5540").unwrap();
148 assert_eq!(reg.get(1).unwrap().address, "10.0.0.1:5540");
149 }
150
151 #[test]
152 fn registry_remove() {
153 let path = test_path("reg_remove");
154
155 let mut reg = DeviceRegistry::load(&path).unwrap();
156 reg.add(Device { node_id: 1, address: "1.2.3.4:5540".into(), name: "light".into() }).unwrap();
157 reg.remove(1).unwrap();
158 assert!(reg.list().is_empty());
159 }
160}