1use std::{
3 marker::PhantomData,
4 path::{Path, PathBuf},
5 time::SystemTime,
6};
7
8use crate::{ConfigError, Mutex, err::ConfigLock};
9
10use super::{
11 ConfigSource, ConfigSourceAdaptor, ConfigSourceParser,
12 memory::{ConfigSourceBuilder, HashSource},
13};
14
15#[derive(Debug)]
17pub(crate) struct FileLoader<L: ConfigSourceParser> {
18 name: String,
19 path: PathBuf,
20 ext: bool,
21 required: bool,
22 modified: Mutex<Option<SystemTime>>,
23 _data: PhantomData<L>,
24}
25
26fn modified_time(path: &Path) -> Option<SystemTime> {
27 path.metadata().and_then(|a| a.modified()).ok()
28}
29
30impl<L: ConfigSourceParser> FileLoader<L> {
31 #[allow(dead_code)]
32 pub(crate) fn new(path: PathBuf, required: bool, ext: bool) -> Self {
33 Self {
34 name: format!(
35 "file:{}.[{}]",
36 path.display(),
37 L::file_extensions().join(",")
38 ),
39 modified: Mutex::new(modified_time(&path)),
40 path,
41 ext,
42 required,
43 _data: PhantomData,
44 }
45 }
46}
47
48fn load_path<L: ConfigSourceParser>(
49 path: PathBuf,
50 flag: &mut bool,
51 builder: &mut ConfigSourceBuilder<'_>,
52) -> Result<(), ConfigError> {
53 if path.exists() {
54 *flag = false;
55 let c = std::fs::read_to_string(&path).map_err(ConfigError::from_cause)?;
56 L::parse_source(&c)?.convert_source(builder)?;
57 }
58 Ok(())
59}
60
61impl<L: ConfigSourceParser> ConfigSource for FileLoader<L> {
62 fn name(&self) -> &str {
63 &self.name
64 }
65
66 fn load(&self, builder: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError> {
67 let mut flag = self.required;
68 if self.ext {
69 load_path::<L>(self.path.clone(), &mut flag, builder)?;
70 } else {
71 for ext in L::file_extensions() {
72 let mut path = self.path.clone();
73 path.set_extension(ext);
74 load_path::<L>(path, &mut flag, builder)?;
75 }
76 }
77 if flag {
78 return Err(ConfigError::ConfigFileNotExists(self.path.clone()));
79 }
80 Ok(())
81 }
82
83 fn allow_refresh(&self) -> bool {
84 true
85 }
86
87 fn refreshable(&self) -> Result<bool, ConfigError> {
88 let time = modified_time(&self.path);
89 let mut g = self.modified.lock_c()?;
90 let flag = time == *g;
91 *g = time;
92 Ok(!flag)
93 }
94}
95
96#[doc(hidden)]
97pub fn inline_source_config<S: ConfigSourceParser>(
98 name: String,
99 content: &'static str,
100) -> Result<HashSource, ConfigError> {
101 let v = S::parse_source(content)?;
102 let mut m = HashSource::new(name);
103 v.convert_source(&mut m.prefixed())?;
104 Ok(m)
105}
106
107#[cfg_attr(coverage_nightly, coverage(off))]
108#[cfg(test)]
109mod test {
110 use std::{fs::File, io::Write, path::PathBuf};
111
112 use crate::{
113 ConfigError, Configuration,
114 source::{ConfigSource, ConfigSourceAdaptor, ConfigSourceBuilder, ConfigSourceParser},
115 };
116
117 use super::FileLoader;
118
119 struct Temp;
120
121 impl ConfigSourceAdaptor for Temp {
122 fn convert_source(self, _: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError> {
123 Ok(())
124 }
125 }
126 impl ConfigSourceParser for Temp {
127 type Adaptor = Temp;
128
129 fn parse_source(_: &str) -> Result<Self::Adaptor, ConfigError> {
130 Ok(Temp)
131 }
132
133 fn file_extensions() -> Vec<&'static str> {
134 vec!["tmp"]
135 }
136 }
137
138 #[test]
139 fn refresh_file_test() -> Result<(), ConfigError> {
140 let path: PathBuf = "target/file_2.tmp".into();
141 let mut f = File::create(&path).map_err(ConfigError::from_cause)?;
142 let config = <FileLoader<Temp>>::new(path.clone(), false, true);
143 assert!(!config.refreshable()?);
144 update_file(&mut f)?;
145 assert!(config.refreshable()?);
146 std::fs::remove_file(path).map_err(ConfigError::from_cause)?;
147 Ok(())
148 }
149
150 fn update_file(f: &mut File) -> Result<(), ConfigError> {
151 let last = f
152 .metadata()
153 .map_err(ConfigError::from_cause)?
154 .modified()
155 .map_err(ConfigError::from_cause)?;
156 let mut i = 0;
157 while last
158 == f.metadata()
159 .map_err(ConfigError::from_cause)?
160 .modified()
161 .map_err(ConfigError::from_cause)?
162 {
163 i += 1;
164 println!("Round: {}", i);
165 f.write_all(b"hello").map_err(ConfigError::from_cause)?;
166 f.flush().map_err(ConfigError::from_cause)?;
167 std::thread::sleep(std::time::Duration::new(0, 1000000));
168 }
169 Ok(())
170 }
171
172 #[test]
173 fn refresh_test() -> Result<(), ConfigError> {
174 let path: PathBuf = "target/file.tmp".into();
175 let mut f = File::create(&path).map_err(ConfigError::from_cause)?;
176 let mut config = Configuration::new().register_source(<FileLoader<Temp>>::new(
177 path.clone(),
178 false,
179 true,
180 ))?;
181 assert!(!config.refresh()?);
182 update_file(&mut f)?;
183 assert!(config.refresh()?);
184 std::fs::remove_file(path).map_err(ConfigError::from_cause)?;
185 Ok(())
186 }
187
188 #[test]
189 fn inline_source_config_success() {
190 let result = super::inline_source_config::<Temp>("inline".to_string(), "abc");
192 assert!(result.is_ok());
193 let hs = result.unwrap();
194 assert_eq!(hs.name(), "inline");
195 }
196
197 #[test]
198 fn inline_source_config_parse_error() {
199 struct Bad;
200 impl ConfigSourceAdaptor for Bad {
201 fn convert_source(self, _: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError> {
202 Ok(())
203 }
204 }
205 impl ConfigSourceParser for Bad {
206 type Adaptor = Bad;
207 fn parse_source(_: &str) -> Result<Self::Adaptor, ConfigError> {
208 Err(ConfigError::ConfigParseError(
209 "bad".to_string(),
210 "fail".to_string(),
211 ))
212 }
213 fn file_extensions() -> Vec<&'static str> {
214 vec!["bad"]
215 }
216 }
217 let result = super::inline_source_config::<Bad>("bad".to_string(), "abc");
218 assert!(matches!(result, Err(ConfigError::ConfigParseError(_, _))));
219 }
220
221 #[test]
222 fn file_loader_load_required_not_exists() {
223 let path: PathBuf = "target/not_exist_file.tmp".into();
225 let loader = <FileLoader<Temp>>::new(path.clone(), true, true);
226 let mut hash_source = crate::source::memory::HashSource::new("test");
227 let mut builder = hash_source.prefixed();
228 let result = loader.load(&mut builder);
229 assert!(matches!(result, Err(ConfigError::ConfigFileNotExists(p)) if p == path));
230 }
231
232 #[test]
233 fn file_loader_load_ext_false_all_exts() {
234 let path: PathBuf = "target/file_multi_ext".into();
236 let mut file_path = path.clone();
238 file_path.set_extension("tmp");
239 let mut f = File::create(&file_path).unwrap();
240 f.write_all(b"abc").unwrap();
241 f.flush().unwrap();
242
243 let mut hash_source = crate::source::memory::HashSource::new("test");
244 let mut builder = hash_source.prefixed();
245 let loader = <FileLoader<Temp>>::new(path.clone(), true, false);
247 let result = loader.load(&mut builder);
249 assert!(result.is_ok());
250 assert!(result.is_ok());
251
252 std::fs::remove_file(file_path).unwrap();
253 }
254}