1use crate::FileSource;
2use crate::{
3 util::accumulate_child_keys, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider,
4 ConfigurationSource, LoadError, LoadResult, Value
5};
6use configparser::ini::Ini;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use tokens::{ChangeToken, FileChangeToken, SharedChangeToken, SingleChangeToken, Subscription};
10
11struct InnerProvider {
12 file: FileSource,
13 data: RwLock<HashMap<String, (String, Value)>>,
14 token: RwLock<SharedChangeToken<SingleChangeToken>>,
15}
16
17impl InnerProvider {
18 fn new(file: FileSource) -> Self {
19 Self {
20 file,
21 data: RwLock::new(HashMap::with_capacity(0)),
22 token: Default::default(),
23 }
24 }
25
26 fn get(&self, key: &str) -> Option<Value> {
27 self.data
28 .read()
29 .unwrap()
30 .get(&key.to_uppercase())
31 .map(|t| t.1.clone())
32 }
33
34 fn reload_token(&self) -> Box<dyn ChangeToken> {
35 Box::new(self.token.read().unwrap().clone())
36 }
37
38 fn load(&self, reload: bool) -> LoadResult {
39 if !self.file.path.is_file() {
40 if self.file.optional || reload {
41 let mut data = self.data.write().unwrap();
42 if !data.is_empty() {
43 *data = HashMap::with_capacity(0);
44 }
45
46 return Ok(());
47 } else {
48 return Err(LoadError::File {
49 message: format!(
50 "The configuration file '{}' was not found and is not optional.",
51 self.file.path.display()
52 ),
53 path: self.file.path.clone(),
54 });
55 }
56 }
57
58 let mut ini = Ini::new_cs();
59 let data = if let Ok(sections) = ini.load(&self.file.path) {
60 let capacity = sections.iter().map(|p| p.1.len()).sum();
61 let mut map = HashMap::with_capacity(capacity);
62
63 for (section, pairs) in sections {
64 for (key, value) in pairs {
65 let mut new_key = section.to_owned();
66 let new_value = value.unwrap_or_default();
67
68 new_key.push_str(ConfigurationPath::key_delimiter());
69 new_key.push_str(&key);
70 map.insert(new_key.to_uppercase(), (new_key, new_value.into()));
71 }
72 }
73
74 map
75 } else {
76 HashMap::with_capacity(0)
77 };
78
79 *self.data.write().unwrap() = data;
80
81 let previous = std::mem::replace(
82 &mut *self.token.write().unwrap(),
83 SharedChangeToken::default(),
84 );
85
86 previous.notify();
87 Ok(())
88 }
89
90 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
91 let data = self.data.read().unwrap();
92 accumulate_child_keys(&data, earlier_keys, parent_path)
93 }
94}
95
96pub struct IniConfigurationProvider {
98 inner: Arc<InnerProvider>,
99 _subscription: Option<Box<dyn Subscription>>,
100}
101
102impl IniConfigurationProvider {
103 pub fn new(file: FileSource) -> Self {
109 let path = file.path.clone();
110 let inner = Arc::new(InnerProvider::new(file));
111 let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
112 Some(Box::new(tokens::on_change(
113 move || FileChangeToken::new(path.clone()),
114 |state| {
115 let provider = state.unwrap();
116 std::thread::sleep(provider.file.reload_delay);
117 provider.load(true).ok();
118 },
119 Some(inner.clone()),
120 )))
121 } else {
122 None
123 };
124
125 Self {
126 inner,
127 _subscription: subscription,
128 }
129 }
130}
131
132impl ConfigurationProvider for IniConfigurationProvider {
133 fn get(&self, key: &str) -> Option<Value> {
134 self.inner.get(key)
135 }
136
137 fn reload_token(&self) -> Box<dyn ChangeToken> {
138 self.inner.reload_token()
139 }
140
141 fn load(&mut self) -> LoadResult {
142 self.inner.load(false)
143 }
144
145 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
146 self.inner.child_keys(earlier_keys, parent_path)
147 }
148}
149
150pub struct IniConfigurationSource {
152 file: FileSource,
153}
154
155impl IniConfigurationSource {
156 pub fn new(file: FileSource) -> Self {
162 Self { file }
163 }
164}
165
166impl ConfigurationSource for IniConfigurationSource {
167 fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
168 Box::new(IniConfigurationProvider::new(self.file.clone()))
169 }
170}
171
172pub mod ext {
173
174 use super::*;
175
176 pub trait IniConfigurationExtensions {
178 fn add_ini_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
184 }
185
186 impl IniConfigurationExtensions for dyn ConfigurationBuilder + '_ {
187 fn add_ini_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
188 self.add(Box::new(IniConfigurationSource::new(file.into())));
189 self
190 }
191 }
192
193 impl<T: ConfigurationBuilder> IniConfigurationExtensions for T {
194 fn add_ini_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
195 self.add(Box::new(IniConfigurationSource::new(file.into())));
196 self
197 }
198 }
199}