archiver_engine/
policy.rs1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use archiver_core::registry::SampleMode;
6use archiver_core::types::ArchDbType;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PvPolicy {
11 pub pv: String,
13 #[serde(default)]
17 pub name: Option<String>,
18 #[serde(default = "default_sample_mode")]
20 pub sample_mode: PolicySampleMode,
21 pub dbr_type: Option<ArchDbType>,
23 pub sampling_period: Option<f64>,
25}
26
27impl PvPolicy {
28 pub fn policy_name(&self) -> &str {
30 self.name.as_deref().unwrap_or(&self.pv)
31 }
32}
33
34#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35#[serde(rename_all = "lowercase")]
36pub enum PolicySampleMode {
37 #[default]
38 Monitor,
39 Scan,
40}
41
42fn default_sample_mode() -> PolicySampleMode {
43 PolicySampleMode::Monitor
44}
45
46impl PvPolicy {
47 pub fn to_sample_mode(&self) -> SampleMode {
48 match self.sample_mode {
49 PolicySampleMode::Monitor => SampleMode::Monitor,
50 PolicySampleMode::Scan => SampleMode::Scan {
51 period_secs: self.sampling_period.unwrap_or(1.0),
52 },
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct PolicyConfig {
60 #[serde(rename = "policy")]
61 pub policies: Vec<PvPolicy>,
62}
63
64impl PolicyConfig {
65 pub fn load(path: &Path) -> anyhow::Result<Self> {
66 let content = std::fs::read_to_string(path)?;
67 let config: PolicyConfig = toml::from_str(&content)?;
68 Ok(config)
69 }
70
71 pub fn find_policy(&self, pv_name: &str) -> Option<&PvPolicy> {
73 if let Some(p) = self.policies.iter().find(|p| p.pv == pv_name) {
75 return Some(p);
76 }
77 self.policies.iter().find(|p| glob_match(&p.pv, pv_name))
79 }
80}
81
82fn glob_match(pattern: &str, text: &str) -> bool {
85 let p: Vec<char> = pattern.chars().collect();
86 let t: Vec<char> = text.chars().collect();
87 let (mut pi, mut ti) = (0usize, 0usize);
88 let (mut star_pi, mut star_ti) = (usize::MAX, 0usize);
89
90 while ti < t.len() {
91 if pi < p.len() && (p[pi] == '?' || p[pi] == t[ti]) {
92 pi += 1;
93 ti += 1;
94 } else if pi < p.len() && p[pi] == '*' {
95 star_pi = pi;
96 star_ti = ti;
97 pi += 1;
98 } else if star_pi != usize::MAX {
99 pi = star_pi + 1;
100 star_ti += 1;
101 ti = star_ti;
102 } else {
103 return false;
104 }
105 }
106
107 while pi < p.len() && p[pi] == '*' {
108 pi += 1;
109 }
110
111 pi == p.len()
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_glob_match() {
120 assert!(glob_match("SIM:*", "SIM:Sine"));
121 assert!(glob_match("SIM:*", "SIM:Cosine:Value"));
122 assert!(!glob_match("SIM:*", "OTHER:Sine"));
123 assert!(glob_match("SIM:?ine", "SIM:Sine"));
124 assert!(glob_match("*", "anything"));
125 assert!(glob_match("", ""));
126 assert!(!glob_match("", "x"));
127 assert!(!glob_match("x", ""));
128 assert!(glob_match("**", "abc"));
129 assert!(glob_match("a*b*c", "aXXbYYc"));
130 assert!(!glob_match("a*b*c", "aXXbYY"));
131 }
132
133 #[test]
134 fn test_glob_no_exponential_backtracking() {
135 let pattern = "*a*a*a*a*b";
137 let text = "aaaaaaaaaaaaaaaaaaaaaaaa"; assert!(!glob_match(pattern, text));
139 }
140}