1use crate::FileSource;
2use crate::{
3 util::accumulate_child_keys, ConfigurationBuilder, ConfigurationPath, ConfigurationProvider, ConfigurationSource,
4 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, String)>>,
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().into())
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));
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::take(&mut *self.token.write().unwrap());
82
83 previous.notify();
84 Ok(())
85 }
86
87 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
88 let data = self.data.read().unwrap();
89 accumulate_child_keys(&data, earlier_keys, parent_path)
90 }
91}
92
93pub struct IniConfigurationProvider {
95 inner: Arc<InnerProvider>,
96 _subscription: Option<Box<dyn Subscription>>,
97}
98
99impl IniConfigurationProvider {
100 pub fn new(file: FileSource) -> Self {
106 let path = file.path.clone();
107 let inner = Arc::new(InnerProvider::new(file));
108 let subscription: Option<Box<dyn Subscription>> = if inner.file.reload_on_change {
109 Some(Box::new(tokens::on_change(
110 move || FileChangeToken::new(path.clone()),
111 |state| {
112 let provider = state.unwrap();
113 std::thread::sleep(provider.file.reload_delay);
114 provider.load(true).ok();
115 },
116 Some(inner.clone()),
117 )))
118 } else {
119 None
120 };
121
122 Self {
123 inner,
124 _subscription: subscription,
125 }
126 }
127}
128
129impl ConfigurationProvider for IniConfigurationProvider {
130 fn get(&self, key: &str) -> Option<Value> {
131 self.inner.get(key)
132 }
133
134 fn reload_token(&self) -> Box<dyn ChangeToken> {
135 self.inner.reload_token()
136 }
137
138 fn load(&mut self) -> LoadResult {
139 self.inner.load(false)
140 }
141
142 fn child_keys(&self, earlier_keys: &mut Vec<String>, parent_path: Option<&str>) {
143 self.inner.child_keys(earlier_keys, parent_path)
144 }
145}
146
147pub struct IniConfigurationSource {
149 file: FileSource,
150}
151
152impl IniConfigurationSource {
153 pub fn new(file: FileSource) -> Self {
159 Self { file }
160 }
161}
162
163impl ConfigurationSource for IniConfigurationSource {
164 fn build(&self, _builder: &dyn ConfigurationBuilder) -> Box<dyn ConfigurationProvider> {
165 Box::new(IniConfigurationProvider::new(self.file.clone()))
166 }
167}
168
169pub mod ext {
170
171 use super::*;
172
173 pub trait IniConfigurationExtensions {
175 fn add_ini_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self;
181 }
182
183 impl IniConfigurationExtensions for dyn ConfigurationBuilder + '_ {
184 fn add_ini_file<T: Into<FileSource>>(&mut self, file: T) -> &mut Self {
185 self.add(Box::new(IniConfigurationSource::new(file.into())));
186 self
187 }
188 }
189
190 impl<T: ConfigurationBuilder> IniConfigurationExtensions for T {
191 fn add_ini_file<F: Into<FileSource>>(&mut self, file: F) -> &mut Self {
192 self.add(Box::new(IniConfigurationSource::new(file.into())));
193 self
194 }
195 }
196}