2024-02-15 13:50:58 +01:00
// Copyright (C) 2024 Philipp Hofer
//
2024-02-15 16:12:14 +01:00
// Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
2024-02-15 13:50:58 +01:00
// the European Commission - subsequent versions of the EUPL (the "Licence").
// You may not use this work except in compliance with the Licence.
//
// You should have received a copy of the European Union Public License along
// with this program. If not, you may obtain a copy of the Licence at:
// <https://joinup.ec.europa.eu/software/page/eupl>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Licence is distributed on an "AS IS" basis,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the Licence for the specific language governing permissions and
// limitations under the Licence.
2024-02-15 09:24:30 +01:00
use std ::{ fs , io , path ::Path } ;
2024-02-04 16:09:59 +01:00
2024-02-04 21:15:15 +01:00
use time ::{ format_description , OffsetDateTime } ;
2024-02-04 16:09:59 +01:00
#[ derive(Debug) ]
2024-02-04 21:46:38 +01:00
#[ allow(dead_code) ]
2024-02-04 16:09:59 +01:00
pub struct Error {
2024-02-15 09:24:30 +01:00
pub ( crate ) msg : String ,
2024-02-04 16:09:59 +01:00
}
2024-02-06 10:17:14 +01:00
impl Error {
2024-02-27 08:40:23 +01:00
pub ( crate ) fn new ( msg : & str ) -> Self {
2024-02-06 10:17:14 +01:00
Self { msg : msg . into ( ) }
}
}
2024-02-04 16:09:59 +01:00
impl From < ureq ::Error > for Error {
fn from ( value : ureq ::Error ) -> Self {
Self {
msg : value . to_string ( ) ,
}
}
}
impl From < io ::Error > for Error {
fn from ( value : io ::Error ) -> Self {
Self {
msg : value . to_string ( ) ,
}
}
}
impl From < serde_json ::Error > for Error {
fn from ( value : serde_json ::Error ) -> Self {
Self {
msg : value . to_string ( ) ,
}
}
}
2024-02-06 10:17:14 +01:00
impl From < toml ::de ::Error > for Error {
fn from ( value : toml ::de ::Error ) -> Self {
Self {
msg : value . to_string ( ) ,
}
}
}
2024-02-04 16:09:59 +01:00
impl From < roxmltree ::Error > for Error {
fn from ( value : roxmltree ::Error ) -> Self {
Self {
msg : value . to_string ( ) ,
}
}
}
2024-02-04 21:15:15 +01:00
/// Returns the current date in YYYY-MM-DD format. Needed for RIS API query to get current version of the overview.
pub ( crate ) fn current_date ( ) -> String {
let local_date = OffsetDateTime ::now_utc ( ) ;
2024-02-27 12:24:27 +01:00
let format = format_description ::parse ( " [year]-[month]-[day] " )
. expect ( " Can't fail, supplied format is fine " ) ;
2024-02-04 21:15:15 +01:00
local_date . format ( & format ) . expect ( " Failed to format date " )
}
2024-02-15 09:24:30 +01:00
#[ cfg(not(test)) ]
pub ( crate ) fn get_cache_dir ( ) -> Result < String , Error > {
let cache_dir = directories ::BaseDirs ::new ( ) . ok_or ( Error {
msg : " directories crate could not find basedirs. " . into ( ) ,
} ) ? ;
let cache_dir = cache_dir . cache_dir ( ) ;
2024-02-27 12:24:27 +01:00
Ok ( format! (
" {}/risp/ " ,
cache_dir
. to_str ( )
. ok_or ( Error ::new ( " Cache dir can't be found " ) ) ?
) )
2024-02-15 09:24:30 +01:00
}
#[ cfg(test) ]
pub ( crate ) fn get_cache_dir ( ) -> Result < String , Error > {
Ok ( " ./data/cache/ " . into ( ) )
}
2024-02-27 08:40:23 +01:00
/// Clears the cache directory used for storing law texts from RIS.
///
/// This function deletes all files and directories within the cache directory, effectively clearing all cached law texts.
/// The cache is used to avoid repeated web requests for the same information, by storing the law texts locally after the first retrieval.
/// The cache directory location is determined by the [`cache_dir` method from the `directories` crate's `BaseDirs` struct](https://docs.rs/directories/latest/directories/struct.BaseDirs.html#method.cache_dir).
///
/// # Returns
///
/// Returns `Ok(())` if the cache was successfully cleared. If an error occurs during the operation, it returns an `Error`.
///
/// # Errors
///
/// This function can return an `Error` in cases such as:
///
/// - Failure to retrieve the cache directory path.
/// - Issues reading the cache directory contents.
/// - Errors encountered while attempting to delete files or directories within the cache directory.
2024-02-15 09:24:30 +01:00
pub fn clear_cache ( ) -> Result < ( ) , Error > {
Ok ( delete_all_in_dir ( Path ::new ( & get_cache_dir ( ) ? ) ) ? )
}
fn delete_all_in_dir < P : AsRef < Path > > ( dir_path : P ) -> std ::io ::Result < ( ) > {
let entries = fs ::read_dir ( dir_path ) ? ;
for entry in entries {
let entry = entry ? ;
let path = entry . path ( ) ;
if path . is_dir ( ) {
delete_all_in_dir ( & path ) ? ;
fs ::remove_dir ( & path ) ? ;
} else {
// If it's a file, delete it
fs ::remove_file ( & path ) ? ;
}
}
Ok ( ( ) )
}
2024-02-18 17:00:15 +01:00
pub ( crate ) fn fetch_with_retries ( url : & str ) -> Result < String , Error > {
let mut attempts = 0 ;
let max_attempts = 100 ;
loop {
match ureq ::get ( url ) . call ( ) {
Ok ( response ) = > {
// If the request is successful, return the response body
match response . into_string ( ) {
Ok ( d ) = > return Ok ( d ) ,
Err ( e ) = > return Err ( e . into ( ) ) ,
}
}
Err ( ureq ::Error ::Transport ( _ ) ) = > {
// Increment the attempt counter
attempts + = 1 ;
// Check if the maximum number of attempts has been reached
if attempts > = max_attempts {
return Err ( Error ::new ( " Maximum attempts reached. " ) ) ;
}
// TODO: Implement a backoff strategy here. As a simple example, we sleep for 1 second.
std ::thread ::sleep ( std ::time ::Duration ::from_secs ( 1 ) ) ;
}
Err ( e ) = > {
// For non-transport errors, return the error immediately.
return Err ( e . into ( ) ) ;
}
}
}
}