config/
ini.rs

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
96/// Represents a [`ConfigurationProvider`](crate::ConfigurationProvider) for `*.ini` files.
97pub struct IniConfigurationProvider {
98    inner: Arc<InnerProvider>,
99    _subscription: Option<Box<dyn Subscription>>,
100}
101
102impl IniConfigurationProvider {
103    /// Initializes a new `*.ini` file configuration provider.
104    ///
105    /// # Arguments
106    ///
107    /// * `file` - The `*.ini` [`FileSource`](crate::FileSource) information
108    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
150/// Represents a [`ConfigurationSource`](crate::ConfigurationSource) for `*.ini` files.
151pub struct IniConfigurationSource {
152    file: FileSource,
153}
154
155impl IniConfigurationSource {
156    /// Initializes a new `*.ini` file configuration source.
157    ///
158    /// # Arguments
159    ///
160    /// * `file` - The `*.ini` [`FileSource`](crate::FileSource) information
161    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    /// Defines extension methods for [`ConfigurationBuilder`](crate::ConfigurationBuilder).
177    pub trait IniConfigurationExtensions {
178        /// Adds an `*.ini` file as a configuration source.
179        ///
180        /// # Arguments
181        ///
182        /// * `file` - The `*.ini` [`FileSource`](crate::FileSource) information
183        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}