Skip to main content

instrument_core/
registry.rs

1use crate::kind::InstrumentKind;
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::path::Path;
5
6/// Model registry entry — hint only, not authoritative.
7#[derive(Debug, Clone, Deserialize)]
8struct RegistryFile {
9    #[serde(default)]
10    entry: Vec<ModelEntry>,
11    #[serde(default)]
12    usb_entry: Vec<UsbEntry>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
16struct ModelEntry {
17    manufacturer: String,
18    model: String,
19    kinds: Vec<String>,
20}
21
22#[derive(Debug, Clone, Deserialize)]
23struct UsbEntry {
24    vid: String,
25    pid: String,
26    manufacturer: Option<String>,
27    model: Option<String>,
28    kinds: Vec<String>,
29}
30
31/// IVI-registry-inspired model → kinds lookup (hints only).
32#[derive(Debug, Default, Clone)]
33pub struct ModelRegistry {
34    by_model: HashMap<String, Vec<InstrumentKind>>,
35    by_usb: HashMap<(String, String), UsbHint>,
36    runtime: HashMap<String, Vec<InstrumentKind>>,
37}
38
39#[derive(Debug, Clone)]
40pub struct UsbHint {
41    pub manufacturer: Option<String>,
42    pub model: Option<String>,
43    pub kinds: Vec<InstrumentKind>,
44}
45
46impl ModelRegistry {
47    pub fn embedded() -> Self {
48        const EMBEDDED: &str = include_str!("../data/model_registry.toml");
49        Self::from_toml(EMBEDDED).unwrap_or_default()
50    }
51
52    pub fn from_toml(content: &str) -> std::result::Result<Self, String> {
53        let file: RegistryFile =
54            toml::from_str(content).map_err(|e| format!("registry parse: {e}"))?;
55        let mut registry = Self::default();
56        for entry in file.entry {
57            let key = normalize_key(&entry.manufacturer, &entry.model);
58            registry.by_model.insert(key, parse_kinds(&entry.kinds));
59        }
60        for entry in file.usb_entry {
61            let key = (entry.vid.to_uppercase(), entry.pid.to_uppercase());
62            registry.by_usb.insert(
63                key,
64                UsbHint {
65                    manufacturer: entry.manufacturer,
66                    model: entry.model,
67                    kinds: parse_kinds(&entry.kinds),
68                },
69            );
70        }
71        Ok(registry)
72    }
73
74    pub fn load_path(path: impl AsRef<Path>) -> Result<Self, String> {
75        let content =
76            std::fs::read_to_string(path.as_ref()).map_err(|e| format!("read registry: {e}"))?;
77        Self::from_toml(&content)
78    }
79
80    pub fn merge(&mut self, other: &Self) {
81        for (k, v) in &other.by_model {
82            self.by_model.entry(k.clone()).or_insert_with(|| v.clone());
83        }
84        for (k, v) in &other.by_usb {
85            self.by_usb.entry(k.clone()).or_insert_with(|| v.clone());
86        }
87        for (k, v) in &other.runtime {
88            self.runtime.insert(k.clone(), v.clone());
89        }
90    }
91
92    pub fn add_runtime(&mut self, manufacturer: &str, model: &str, kinds: Vec<InstrumentKind>) {
93        let key = normalize_key(manufacturer, model);
94        self.runtime.insert(key, kinds);
95    }
96
97    pub fn lookup_model(&self, manufacturer: &str, model: &str) -> Option<Vec<InstrumentKind>> {
98        let key = normalize_key(manufacturer, model);
99        self.runtime
100            .get(&key)
101            .or_else(|| self.by_model.get(&key))
102            .cloned()
103    }
104
105    pub fn lookup_usb(&self, vid: &str, pid: &str) -> Option<UsbHint> {
106        let key = (vid.to_uppercase(), pid.to_uppercase());
107        self.by_usb.get(&key).cloned()
108    }
109}
110
111fn normalize_key(manufacturer: &str, model: &str) -> String {
112    format!(
113        "{}|{}",
114        manufacturer.trim().to_lowercase(),
115        model.trim().to_lowercase()
116    )
117}
118
119fn parse_kinds(labels: &[String]) -> Vec<InstrumentKind> {
120    labels
121        .iter()
122        .filter_map(|l| InstrumentKind::from_str_label(l))
123        .collect()
124}
125
126// Use minimal toml parsing - we need toml crate
127// Add toml to instrument-core dependencies