1use std::{
3 marker::PhantomData,
4 path::{Path, PathBuf},
5 time::SystemTime,
6};
7
8use crate::{err::ConfigLock, ConfigError, Mutex};
9
10use super::{
11 memory::{ConfigSourceBuilder, HashSource},
12 ConfigSource, ConfigSourceAdaptor, ConfigSourceParser,
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)?;
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 source::{ConfigSource, ConfigSourceAdaptor, ConfigSourceBuilder, ConfigSourceParser},
114 ConfigError, Configuration,
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)?;
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)?;
147 Ok(())
148 }
149
150 fn update_file(f: &mut File) -> Result<(), ConfigError> {
151 let last = f.metadata()?.modified()?;
152 let mut i = 0;
153 while last == f.metadata()?.modified()? {
154 i += 1;
155 println!("Round: {}", i);
156 f.write_all(b"hello")?;
157 f.flush()?;
158 std::thread::sleep(std::time::Duration::new(0, 1000000));
159 }
160 Ok(())
161 }
162
163 #[test]
164 fn refresh_test() -> Result<(), ConfigError> {
165 let path: PathBuf = "target/file.tmp".into();
166 let mut f = File::create(&path)?;
167 let mut config = Configuration::new().register_source(<FileLoader<Temp>>::new(
168 path.clone(),
169 false,
170 true,
171 ))?;
172 assert!(!config.refresh()?);
173 update_file(&mut f)?;
174 assert!(config.refresh()?);
175 std::fs::remove_file(path)?;
176 Ok(())
177 }
178
179 #[test]
180 fn inline_source_config_success() {
181 let result = super::inline_source_config::<Temp>("inline".to_string(), "abc");
183 assert!(result.is_ok());
184 let hs = result.unwrap();
185 assert_eq!(hs.name(), "inline");
186 }
187
188 #[test]
189 fn inline_source_config_parse_error() {
190 struct Bad;
191 impl ConfigSourceAdaptor for Bad {
192 fn convert_source(self, _: &mut ConfigSourceBuilder<'_>) -> Result<(), ConfigError> {
193 Ok(())
194 }
195 }
196 impl ConfigSourceParser for Bad {
197 type Adaptor = Bad;
198 fn parse_source(_: &str) -> Result<Self::Adaptor, ConfigError> {
199 Err(ConfigError::ConfigParseError(
200 "bad".to_string(),
201 "fail".to_string(),
202 ))
203 }
204 fn file_extensions() -> Vec<&'static str> {
205 vec!["bad"]
206 }
207 }
208 let result = super::inline_source_config::<Bad>("bad".to_string(), "abc");
209 assert!(matches!(result, Err(ConfigError::ConfigParseError(_, _))));
210 }
211
212 #[test]
213 fn file_loader_load_required_not_exists() {
214 let path: PathBuf = "target/not_exist_file.tmp".into();
216 let loader = <FileLoader<Temp>>::new(path.clone(), true, true);
217 let mut hash_source = crate::source::memory::HashSource::new("test");
218 let mut builder = hash_source.prefixed();
219 let result = loader.load(&mut builder);
220 assert!(matches!(result, Err(ConfigError::ConfigFileNotExists(p)) if p == path));
221 }
222
223 #[test]
224 fn file_loader_load_ext_false_all_exts() {
225 let path: PathBuf = "target/file_multi_ext".into();
227 let mut file_path = path.clone();
229 file_path.set_extension("tmp");
230 let mut f = File::create(&file_path).unwrap();
231 f.write_all(b"abc").unwrap();
232 f.flush().unwrap();
233
234 let mut hash_source = crate::source::memory::HashSource::new("test");
235 let mut builder = hash_source.prefixed();
236 let loader = <FileLoader<Temp>>::new(path.clone(), true, false);
238 let result = loader.load(&mut builder);
240 assert!(result.is_ok());
241 assert!(result.is_ok());
242
243 std::fs::remove_file(file_path).unwrap();
244 }
245}