1use std::{
2 collections::HashMap,
3 io,
4 sync::{Arc, Mutex},
5 time::SystemTime,
6};
7
8use log::{debug, warn};
9
10#[derive(thiserror::Error, Debug)]
11pub enum FetchError {
12 #[cfg(feature = "reqwest")]
13 #[error("reqwest err")]
14 R(#[from] reqwest::Error),
15 #[error("io err")]
16 I(#[from] io::Error),
17 #[error("time err")]
18 T(#[from] std::time::SystemTimeError),
19 #[error("size limit exceed")]
20 S,
21}
22
23#[cfg(feature = "reqwest")]
24#[derive(Clone, Debug, Default)]
25pub struct HttpSource {
26 pub url: String,
27 pub proxy: Option<String>,
28 pub custom_request_headers: Option<Vec<(String, String)>>,
29 pub should_use_proxy: bool,
30 pub size_limit_bytes: Option<usize>,
31 pub update_interval_seconds: Option<u64>,
32
33 pub cached_file: Arc<Mutex<Option<String>>>,
34
35 pub cache_file_path: Option<String>,
36}
37#[cfg(feature = "reqwest")]
38impl HttpSource {
39 pub fn get_url(
40 &self,
41 c: reqwest::blocking::Client,
42 ) -> reqwest::Result<reqwest::blocking::Response> {
43 let mut rb = c.get(&self.url);
44 if let Some(h) = &self.custom_request_headers {
45 for h in h.iter() {
46 rb = rb.header(&h.0, &h.1);
47 }
48 }
49 rb.send()
50 }
51 pub fn set_proxy(
52 &self,
53 mut cb: reqwest::blocking::ClientBuilder,
54 ) -> reqwest::Result<reqwest::blocking::ClientBuilder> {
55 let ps = self.proxy.as_ref().unwrap();
56 let proxy = reqwest::Proxy::https(ps)?;
57 cb = cb.proxy(proxy);
58 let proxy = reqwest::Proxy::http(ps)?;
59 Ok(cb.proxy(proxy))
60 }
61
62 pub fn fetch(&self) -> Result<Vec<u8>, FetchError> {
63 let ocf = { self.cached_file.lock().unwrap().take() };
64 if let Some(cf) = &ocf {
65 if std::fs::exists(cf)? {
66 let mut ok = true;
67 if let Some(uis) = self.update_interval_seconds {
68 let m = std::fs::metadata(cf)?;
69 let lu = m.modified()?;
70 let d = SystemTime::now().duration_since(lu)?.as_secs();
71 if d <= uis {
72 let _ = self.cached_file.lock().unwrap().insert(cf.to_string());
73 } else {
74 ok = false;
75 }
76 }
77 if ok {
78 let s = std::fs::read_to_string(cf)?;
79 return Ok(s.into_bytes());
80 }
81 }
82 }
83
84 let mut cb = reqwest::blocking::ClientBuilder::new();
85 if self.should_use_proxy {
86 cb = self.set_proxy(cb)?;
87 }
88 let c = cb.build()?;
89 let r = self.get_url(c);
90 let r = match r {
91 Ok(r) => r,
92 Err(e) => {
93 if !self.should_use_proxy && self.proxy.is_some() {
94 let mut cb = reqwest::blocking::ClientBuilder::new();
95 cb = self.set_proxy(cb)?;
96 let c = cb.build()?;
97 self.get_url(c)?
98 } else {
99 return Err(FetchError::R(e));
100 }
101 }
102 };
103 if let Some(sl) = self.size_limit_bytes {
104 if let Some(s) = r.content_length() {
105 if s as usize > sl {
106 return Err(FetchError::S);
107 }
108 }
109 }
110 let b = r.bytes()?;
111 let v = b.to_vec();
112 if let Some(cfp) = &self.cache_file_path {
113 let r = std::fs::write(cfp, &v);
114 match r {
115 Ok(_) => {
116 let _ = self.cached_file.lock().unwrap().insert(cfp.to_string());
117 }
118 Err(e) => warn!("write to cache file failed, {e}"),
119 }
120 }
121 Ok(v)
122 }
123}
124
125#[derive(Clone, Debug)]
126pub enum SingleFileSource {
127 #[cfg(feature = "reqwest")]
128 Http(HttpSource),
129 FilePath(String),
130 Inline(Vec<u8>),
131}
132impl Default for SingleFileSource {
133 fn default() -> Self {
134 Self::Inline(Vec::new())
135 }
136}
137#[derive(Clone, Debug, Default)]
143pub enum DataSource {
144 #[default]
145 StdReadFile,
146 Folders(Vec<String>), #[cfg(feature = "tar")]
149 Tar(Vec<u8>), FileMap(HashMap<String, SingleFileSource>),
153}
154
155impl DataSource {
156 pub fn insert_current_working_dir(&mut self) -> io::Result<()> {
157 if let DataSource::Folders(ref mut v) = self {
158 v.push(std::env::current_dir()?.to_string_lossy().to_string())
159 }
160 Ok(())
161 }
162
163 pub fn read_to_string<P>(&self, file_name: P) -> io::Result<String>
164 where
165 P: AsRef<std::path::Path>,
166 {
167 let r = self.get_file_content(file_name)?;
168 Ok(String::from_utf8_lossy(r.0.as_slice()).to_string())
169 }
170
171 pub fn get_file_content<P>(&self, file_name: P) -> io::Result<(Vec<u8>, Option<String>)>
173 where
174 P: AsRef<std::path::Path>,
175 {
176 match self {
177 #[cfg(feature = "tar")]
178 DataSource::Tar(tar_binary) => get_file_from_tar(file_name, tar_binary),
179
180 DataSource::Folders(possible_addrs) => {
181 for dir in possible_addrs {
182 let real_file_name = std::path::Path::new(dir).join(file_name.as_ref());
183
184 if real_file_name.exists() {
185 return std::fs::read(&real_file_name).map(|v| (v, Some(dir.to_owned())));
186 }
187 }
188 Err(std::io::Error::new(
189 std::io::ErrorKind::NotFound,
190 format!(
191 "File not found in specified directories: {:?}",
192 file_name.as_ref()
193 ),
194 ))
195 }
196 DataSource::StdReadFile => {
197 let s = std::fs::read_to_string(file_name)?;
198 Ok((s.into_bytes(), None))
199 }
200
201 DataSource::FileMap(map) => {
202 let r = map.get(&file_name.as_ref().to_string_lossy().to_string());
203
204 match r {
205 Some(sf) => match sf {
206 #[cfg(feature = "reqwest")]
207 SingleFileSource::Http(http_source) => {
208 let r = http_source.fetch();
209 match r {
210 Ok(v) => Ok((v, None)),
211 Err(e) => Err(io::Error::other(e)),
212 }
213 }
214 SingleFileSource::FilePath(f) => {
215 let s = std::fs::read_to_string(f)?;
216 Ok((s.into_bytes(), None))
217 }
218 SingleFileSource::Inline(v) => Ok((v.clone(), None)),
219 },
220 None => Err(io::Error::new(io::ErrorKind::NotFound, "")),
221 }
222 }
223 }
224 }
225}
226
227#[cfg(feature = "tar")]
228pub fn get_file_from_tar<P>(
229 file_name_in_tar: P,
230 tar_binary: &Vec<u8>,
231) -> io::Result<(Vec<u8>, Option<String>)>
232where
233 P: AsRef<std::path::Path>,
234{
235 let mut a = tar::Archive::new(std::io::Cursor::new(tar_binary));
236
237 debug!(
238 "finding {} from tar, tar whole size is {}",
239 file_name_in_tar.as_ref().to_str().unwrap(),
240 tar_binary.len()
241 );
242
243 let mut e = a
244 .entries()
245 .unwrap()
246 .find(|a| {
247 a.as_ref()
248 .is_ok_and(|b| b.path().is_ok_and(|c| c == file_name_in_tar.as_ref()))
249 })
250 .ok_or_else(|| {
251 io::Error::new(
252 io::ErrorKind::NotFound,
253 format!(
254 "get_file_from_tar: can't find the file, {}",
255 file_name_in_tar.as_ref().to_str().unwrap()
256 ),
257 )
258 })??;
259
260 debug!("found {}", file_name_in_tar.as_ref().to_str().unwrap());
261
262 let mut result = vec![];
263 use std::io::Read;
264 e.read_to_end(&mut result)?;
265 Ok((
266 result,
267 Some(e.path().unwrap().to_str().unwrap().to_string()),
268 ))
269}