reginleif_utils/save_path.rs
1//! The module for the save and load the data from the file.
2//! It provides the trait to save and load the data from the file,
3//! from the path that is constant to dynamic.
4
5use std::marker::PhantomData;
6use std::path::{Path, PathBuf};
7use reqwest::{Client, Error, Response};
8use serde::{Serialize};
9use serde::de::DeserializeOwned;
10use sha1::Digest as Digest1;
11use log::log;
12use crate::sha::SHA;
13
14/// A trait for the base path of the data.
15///
16/// You have to implement this trait to provide the base path of the data.
17/// You also have to care about the thread safety of the data.
18/// The data should be thread safe (Sync+Send).
19///
20/// You can use the derive macro to implement this trait.
21/// You can also implement [From](From) or [TryFrom](TryFrom)
22/// to data struct convert more easily.
23///
24/// # Example
25/// ```no_run
26/// use std::path::PathBuf;
27/// use reginleif_macro::BaseStorePoint;
28///
29/// #[derive(BaseStorePoint)] // the macro only accept one field struct.
30/// struct TestPath(PathBuf);
31///
32/// ```
33pub trait BaseStorePoint:Sync+Send{
34 /// Get the path of the data.
35 fn get_base(&self) -> PathBuf;
36}
37
38/// The trait that return the relative path of the data from base.
39///
40/// This trait is using by [Save](Save) trait to get the relative path of the data
41/// from base, and you have to implement this trait to provide the relative path of the data
42/// if you are about to use [Save](Save) trait.
43/// It is used by the struct which have a path that is not constant, varies from its field, and
44/// if you have a constant path, you should use [Store](Store) trait.
45///
46/// # Example
47/// ```no_run
48/// use std::path::PathBuf;
49/// use serde::{Deserialize, Serialize};
50/// use reginleif_macro::{BaseStorePoint, Storage};
51/// use reginleif_utils::save_path::{Store, Save, Load};
52///
53/// #[derive(BaseStorePoint,PartialEq,Debug)]
54/// struct TestPath(PathBuf);
55///
56/// impl From<PathBuf> for TestPath{
57/// fn from(path:PathBuf) -> Self{
58/// Self(path)
59/// }
60/// }
61/// ```
62pub trait ExpandStorePoint{
63 fn get_suffix(&self) -> PathBuf;
64}
65
66/// A trait for the store of the data which have a const path.
67///
68/// You have to implement this trait to provide the store path of the data.
69/// When compared to [Save](Save) and [Load](Load) trait, this trait is used for the data which have a constant path.
70///
71/// You can also use the derive macro to implement this trait.
72///
73/// # Example
74/// ```no_run
75/// use std::path::PathBuf;
76/// use serde::{Deserialize, Serialize};
77/// use reginleif_macro::{BaseStorePoint, Storage};
78/// use reginleif_utils::save_path::{Store, Save, Load};
79///
80/// #[derive(BaseStorePoint,PartialEq,Debug)]
81/// struct TestPath(PathBuf);
82///
83/// impl From<PathBuf> for TestPath{
84/// fn from(path:PathBuf) -> Self{
85/// Self(path)
86/// }
87/// }
88///
89/// #[derive(Deserialize,Serialize,PartialEq,Debug,Storage)]
90/// #[base_on(TestPath)] #[filepath(&["test.txt"])] // the file will store in TestPath + test.txt
91/// struct A;
92///
93///
94/// ```
95///
96/// The macro also support AcceptStorePoint as a generic type,
97/// so you can use the generic type to store the data with different base path.
98///
99/// # Example
100/// ```no_run
101/// use std::marker::PhantomData;
102/// use std::path::PathBuf;
103/// use serde::{Deserialize, Serialize};
104/// use reginleif_macro::{BaseStorePoint, Storage};
105/// use reginleif_utils::save_path::{Store, Save, Load, BaseStorePoint};
106///
107/// #[derive(BaseStorePoint,PartialEq,Debug)]
108/// struct TestPath(PathBuf);
109///
110/// impl From<PathBuf> for TestPath{
111/// fn from(path:PathBuf) -> Self{
112/// Self(path)
113/// }
114/// }
115///
116/// #[derive(Deserialize,Serialize,PartialEq,Debug,Storage)]
117/// #[filepath(&["test.txt"])]
118/// struct A<T> where T:BaseStorePoint{
119/// num:String,
120/// _t:PhantomData<T>
121/// }
122///
123/// ```
124///
125/// Now you can store the data with different base path with generic type.
126pub trait Store:Serialize+DeserializeOwned{
127
128 /// The const path of the file.
129 /// Separate the path by the array of the str.
130 const FILE_PATH:&'static [&'static str];
131 /// The type of the base path you have to accept.
132 /// This field will become save and load function's argument.
133 type AcceptStorePoint:BaseStorePoint;
134
135 /// Get the full path of the data.
136 fn full_path(base:&Self::AcceptStorePoint) -> PathBuf{
137 let mut base_path = base.get_base();
138 for i in Self::FILE_PATH{
139 base_path = base_path.join(i);
140 }
141 base_path
142 }
143
144 /// Save the data to the file.
145 ///
146 /// # Arguments
147 /// * `base`: the base path of the data.
148 fn save(&self, base: &Self::AcceptStorePoint) -> anyhow::Result<()> {
149 let base_path = Self::full_path(&base);
150
151 std::fs::create_dir_all(base_path.parent().ok_or(anyhow::anyhow!("No parent"))?)?;
152 std::fs::write(base_path,serde_json::to_string(self)?.as_bytes())?;
153
154 Ok(())
155
156 }
157
158 /// Load the data from the file.
159 ///
160 /// # Arguments
161 /// * `base`: the base path of the data.
162 fn load(base: &Self::AcceptStorePoint) -> anyhow::Result<Self> {
163
164 let base_path = Self::full_path(&base);
165
166 let json = std::fs::read_to_string(base_path)?;
167 Ok(serde_json::from_str(&json)?)
168 }
169}
170
171/// A trait for the save the data which have a dynamic path.
172///
173/// You have to implement this trait to save the data to the file.
174/// When compared to [Store](Store) trait, this trait is used for the data which have a dynamic path.
175///
176/// You can also use the derive macro to implement this trait.
177///
178/// # Example
179/// ```no_run
180/// use std::path::PathBuf;
181/// use serde::{Deserialize, Serialize};
182/// use reginleif_macro::{BaseStorePoint, Save, Load};
183/// use reginleif_utils::save_path::{ExpandStorePoint, Save, Load};
184///
185/// #[derive(BaseStorePoint,PartialEq,Debug)]
186/// struct TestPath(PathBuf);
187///
188/// impl From<PathBuf> for TestPath{
189/// fn from(path:PathBuf) -> Self{
190/// Self(path)
191/// }
192/// }
193///
194/// #[derive(Serialize,Deserialize,Save,Load,PartialEq,Debug)]
195/// #[base_on(TestPath)]
196/// struct B(i32);
197///
198/// impl ExpandStorePoint for B{ // you should implement this trait to provide the relative path of the data from base.
199/// fn get_suffix(&self) -> PathBuf {
200/// PathBuf::from(format!("{}.json",self.0))
201/// }
202/// }
203/// ```
204///
205/// The macro also support AcceptStorePoint as a generic type,
206/// so you can use the generic type to save the data with different base path.
207/// Note the generic argument should be the struct that impl [BaseStorePoint](BaseStorePoint) trait
208/// and only one.
209///
210/// # Example
211/// ```no_run
212/// use std::marker::PhantomData;
213/// use std::path::PathBuf;
214/// use serde::{Deserialize, Serialize};
215/// use reginleif_macro::{BaseStorePoint, Load, Save, Storage};
216/// use reginleif_utils::save_path::{Store, Save, Load, BaseStorePoint, ExpandStorePoint};
217///
218/// #[derive(BaseStorePoint,PartialEq,Debug)]
219/// struct TestPath(PathBuf);
220///
221/// impl From<PathBuf> for TestPath{
222/// fn from(path:PathBuf) -> Self{
223/// Self(path)
224/// }
225/// }
226///
227/// #[derive(Serialize,Deserialize,PartialEq,Debug,Save,Load)]
228/// struct C<T> where T:BaseStorePoint{
229/// num:String,
230/// _t:PhantomData<T>
231/// }
232///
233/// impl <T> ExpandStorePoint for C<T> where T:BaseStorePoint{ // you still need to implement this trait to provide the relative path of the data from base.
234/// fn get_suffix(&self) -> PathBuf {
235/// PathBuf::from(format!("{}.json",self.num))
236/// }
237/// }
238///
239/// ```
240pub trait Save:ExpandStorePoint+Serialize{
241
242 /// The type of the base path you have to accept.
243 /// This field will become save function's argument.
244 type AcceptStorePoint:BaseStorePoint;
245
246 /// Save the data to the file.
247 ///
248 /// # Arguments
249 /// * `base`: the base path of the data.
250 fn save(&self, base:&Self::AcceptStorePoint) -> anyhow::Result<()>{
251 let base_path = base.get_base().join(&self.get_suffix());
252
253 std::fs::create_dir_all(base_path.parent().ok_or(anyhow::anyhow!("No parent"))?)?;
254 std::fs::write(base_path,serde_json::to_string(self)?.as_bytes())?;
255
256 Ok(())
257 }
258}
259
260
261/// A trait to load the data which have a dynamic path.
262///
263/// You have to implement this trait to load the data from the file.
264///
265/// You can also use the derive macro to implement this trait.
266///
267/// # Example
268/// ```no_run
269/// use std::path::PathBuf;
270/// use serde::{Deserialize, Serialize};
271/// use reginleif_macro::{BaseStorePoint, Save, Load};
272/// use reginleif_utils::save_path::{ExpandStorePoint, Save, Load};
273///
274/// #[derive(BaseStorePoint,PartialEq,Debug)]
275/// struct TestPath(PathBuf);
276///
277/// impl From<PathBuf> for TestPath{
278/// fn from(path:PathBuf) -> Self{
279/// Self(path)
280/// }
281/// }
282///
283/// #[derive(Serialize,Deserialize,Save,Load,PartialEq,Debug)]
284/// #[base_on(TestPath)]
285/// struct B;
286///
287/// impl ExpandStorePoint for B{ // you should implement this trait to provide the relative path of the data from base.
288/// fn get_suffix(&self) -> PathBuf {
289/// PathBuf::from("test.txt")
290/// }
291/// }
292/// ```
293///
294/// The macro also support AcceptStorePoint as a generic type,
295/// so you can use the generic type to save the data with different base path.
296/// Note the generic argument should be the struct that impl [BaseStorePoint](BaseStorePoint) trait
297/// and only one.
298///
299/// # Example
300/// ```no_run
301/// use std::marker::PhantomData;
302/// use std::path::PathBuf;
303/// use serde::{Deserialize, Serialize};
304/// use reginleif_macro::{BaseStorePoint, Load, Save, Storage};
305/// use reginleif_utils::save_path::{Store, Save, Load, BaseStorePoint, ExpandStorePoint};
306///
307/// #[derive(BaseStorePoint,PartialEq,Debug)]
308/// struct TestPath(PathBuf);
309///
310/// impl From<PathBuf> for TestPath{
311/// fn from(path:PathBuf) -> Self{
312/// Self(path)
313/// }
314/// }
315///
316/// #[derive(Serialize,Deserialize,PartialEq,Debug,Save,Load)]
317/// struct C<T> where T:BaseStorePoint{
318/// num:String,
319/// _t:PhantomData<T>
320/// }
321///
322/// impl <T> ExpandStorePoint for C<T> where T:BaseStorePoint{ // you still need to implement this trait to provide the relative path of the data from base.
323/// fn get_suffix(&self) -> PathBuf {
324/// PathBuf::from(format!("{}.json",self.num))
325/// }
326/// }
327///
328/// ```
329
330pub trait Load:DeserializeOwned{
331
332 /// The type of the base path you have to accept.
333 type AcceptStorePoint:BaseStorePoint;
334
335 /// Load the data from the file.
336 ///
337 /// # Arguments
338 /// * `base`: the base path of the data.
339 /// * `suffix`: the relative path of the data from base.
340 fn load<P: AsRef<Path>>(base: &Self::AcceptStorePoint, suffix: P) -> anyhow::Result<Self>{
341 let path = base.get_base().join(suffix);
342 let content = std::fs::read_to_string(path)?;
343 // Remove the explicit lifetime annotation from the call to `serde_json::from_str`
344 let json = serde_json::from_str(&content)?;
345 Ok(json)
346 }
347}
348
349/// private function to handle the file which is not exist.
350async fn handle_file_not_exist(path:&PathBuf, client: &Client, url:&str) -> anyhow::Result<()>{
351 tokio::fs::create_dir_all(path.parent().ok_or(anyhow::anyhow!("No parent"))?).await?;
352
353 if !path.exists() { // fetching data
354 let data = client.get(url).send().await?.bytes().await?;
355 tokio::fs::write(path, data).await?;
356 }
357
358 Ok(())
359}
360
361/// try to download the content from the url and save it to the disk.
362/// if not success, we won't save it
363async fn try_download(client: &Client, url:&str, path:&PathBuf) -> anyhow::Result<()>{
364 match client.get(url).send().await?.bytes().await{
365 Ok(data) => {tokio::fs::write(&path, data).await?;}
366 Err(e) => {log::error!("Error while fetching {url}, details:{}",e.to_string())} // we won't do anything if the data is not fetched successfully.
367 };
368 Ok(())
369}
370
371pub trait Cache:DeserializeOwned{
372
373 type AcceptStorePoint:BaseStorePoint;
374
375
376 fn refresh_cache<P: AsRef<Path>+Send>(base:&Self::AcceptStorePoint, suffix:P, client: Client, url:&str)
377 -> impl std::future::Future<Output = anyhow::Result<Self>> + Send{async move {
378
379 let path = base.get_base().join(&suffix);
380 try_download(&client,&url,&path).await?;
381
382 let content = std::fs::read_to_string(path)?;
383 let json = serde_json::from_str(&content)?;
384
385 Ok(json)
386 }}
387
388
389 /// this will check file exist or not.
390 /// if the file exist, it will return the data from disk.
391 /// if the file not exist, it will fetch the data from the source and save it to the disk, then return the data.
392 /// a dirty way to avoid async trait warning, you should see this as `` async fn try_cache -> anyhow::Result<Self>; ``
393 fn try_cache<P: AsRef<Path>+Send>(base:&Self::AcceptStorePoint, suffix:P, client: Client, url:&str)
394 -> impl std::future::Future<Output = anyhow::Result<Self>> + Send{async move {
395
396 let path = base.get_base().join(suffix);
397
398 handle_file_not_exist(&path, &client, url).await?;
399
400 let content = std::fs::read_to_string(path)?;
401 let json = serde_json::from_str(&content)?;
402
403 Ok(json)
404 }}
405
406 /// 1. the file exist and the sha is valid, return the data from disk.
407 /// 2. the file exist and the sha is invalid, fetch the data from the source and save it to the disk, then return the data.
408 /// 3. the file not exist, fetch the data from the source and save it to the disk, then return the data.
409 fn check_cache<P: AsRef<Path>+Send>(base:&Self::AcceptStorePoint, suffix:P, client: Client, url: &str, sha:SHA)
410 -> impl std::future::Future<Output = anyhow::Result<Self>> + Send{async move {
411
412 let path = base.get_base().join(suffix);
413 handle_file_not_exist(&path, &client, url).await?;
414
415 let content = std::fs::read(path.clone())?;
416
417 let valid = match sha {
418 SHA::SHA1(a) => {
419 let mut hasher = sha1::Sha1::new();
420 hasher.update(&content);
421 hasher.finalize().as_slice() == a
422 }
423 SHA::SHA256(b) => {
424 let mut hasher = sha2::Sha256::new();
425 hasher.update(&content);
426 hasher.finalize().as_slice() == b
427 }
428 };
429
430 if !valid{
431 try_download(&client,&url,&path).await?;
432 }
433
434 let content = std::fs::read_to_string(path)?; // we won't check the sha again, because we already download it.
435
436 let json = serde_json::from_str(&content)?;
437 Ok(json)
438 }}
439
440 /// Return a builder for the cache.
441 fn builder() -> CacheBuilder<Self::AcceptStorePoint,Self> where Self::AcceptStorePoint:Clone{
442 CacheBuilder{
443 url:"".to_string(),
444 buf:PathBuf::new(),
445 base:None,
446 _t: PhantomData,
447 }
448 }
449
450}
451
452
453/// Using builder pattern for [Cache] trait.
454/// This builder is required [T] impl Clone trait to use.
455pub struct CacheBuilder<T:BaseStorePoint,U:Cache>{
456 url:String,
457 buf:PathBuf,
458 base:Option<T>,
459 _t:PhantomData<U>
460}
461
462
463impl <T,U> CacheBuilder<T, U> where U:Cache<AcceptStorePoint=T>, T:BaseStorePoint+Clone{
464
465 /// append the path to the buffer.
466 pub fn add<P: AsRef<Path>+Send>(mut self,args:P) -> Self{
467 self.buf.push(args);
468 self
469 }
470
471 /// change the url you want to fetch.
472 pub fn url<P: AsRef<str>>(mut self,args:P) -> Self{
473 self.url = args.as_ref().to_string();
474 self
475 }
476
477 /// set the base path of the data.
478 pub fn base_on(mut self, args:&T) -> Self{
479 self.base = Some(args.clone());
480 self
481 }
482
483 /// run [U::check_cache] from builder and return the result.
484 pub fn build_check(&self, client: Client, sha:SHA)
485 -> impl std::future::Future<Output=anyhow::Result<U>> + Send + '_{
486 let base = &self.base.as_ref().unwrap();
487 U::check_cache(base,&self.buf,client,&self.url,sha)
488 }
489
490 /// run [U::try_cache] from builder and return the result.
491 pub fn build_try(&self, client: Client) -> impl std::future::Future<Output = anyhow::Result<U>> + Send + '_{
492 let base = &self.base.as_ref().unwrap();
493 U::try_cache(base,&self.buf,client,&self.url)
494 }
495
496 pub fn build_refresh(&self, client: Client) -> impl std::future::Future<Output = anyhow::Result<U>> + Send + '_{
497 let base = &self.base.as_ref().unwrap();
498 U::refresh_cache(base,&self.buf,client,&self.url)
499 }
500
501}