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}