169 lines
5.3 KiB
Rust
169 lines
5.3 KiB
Rust
// Copyright (C) 2024 Philipp Hofer
|
|
//
|
|
// Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
|
|
// 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.
|
|
|
|
use std::{fs, io, path::Path};
|
|
|
|
use time::{format_description, OffsetDateTime};
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub struct Error {
|
|
pub(crate) msg: String,
|
|
}
|
|
|
|
impl Error {
|
|
pub(crate) fn new(msg: &str) -> Self {
|
|
Self { msg: msg.into() }
|
|
}
|
|
}
|
|
|
|
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(),
|
|
}
|
|
}
|
|
}
|
|
impl From<toml::de::Error> for Error {
|
|
fn from(value: toml::de::Error) -> Self {
|
|
Self {
|
|
msg: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<roxmltree::Error> for Error {
|
|
fn from(value: roxmltree::Error) -> Self {
|
|
Self {
|
|
msg: value.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
let format = format_description::parse("[year]-[month]-[day]")
|
|
.expect("Can't fail, supplied format is fine");
|
|
local_date.format(&format).expect("Failed to format date")
|
|
}
|
|
|
|
#[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();
|
|
Ok(format!(
|
|
"{}/risp/",
|
|
cache_dir
|
|
.to_str()
|
|
.ok_or(Error::new("Cache dir can't be found"))?
|
|
))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn get_cache_dir() -> Result<String, Error> {
|
|
Ok("./data/cache/".into())
|
|
}
|
|
|
|
/// 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.
|
|
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(())
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|