Skip to main content

config/
ini.rs

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