use crate::{
error::{Error, ErrorSerializable},
Result,
};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::{
collections::{hash_map::Entry, HashMap},
path::Path,
};
#[derive(Debug, Serialize, Deserialize, Default)]
struct Cache {
parts: HashMap<i32, HashMap<String, Response>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Response {
submission_time: DateTime<Utc>,
response: std::result::Result<(), ErrorSerializable>,
}
fn get_remaining_time(submission_time: &DateTime<Utc>, rate_limit_str: &str) -> Option<i64> {
let mut remaining_seconds = None;
if let Some(ratelimit_seconds) = rate_limit_str
.strip_suffix('s')
.and_then(|s| s.parse::<i64>().ok())
.map(Duration::seconds)
{
let time_since_ratelimit_response = Utc::now() - *submission_time;
remaining_seconds = ratelimit_seconds
.checked_sub(&time_since_ratelimit_response)
.map(|d| d.num_seconds())
.and_then(|x| {
if x.is_positive() {
Some(x)
}
else {
None
}
});
}
remaining_seconds
}
pub fn cache_wrapper(
cache_path: Option<impl AsRef<Path>>,
part: i32,
result: &str,
post_fn: impl FnOnce(&str) -> Result<()>,
) -> Result<()> {
if let Some(cache_path) = cache_path {
let cache_path = cache_path.as_ref();
let mut full_cache = std::fs::read_to_string(cache_path)
.ok()
.and_then(|cache_data| serde_json::from_str::<Cache>(&cache_data).ok())
.unwrap_or_default();
let part = full_cache.parts.entry(part).or_default();
let final_response = match part.entry(result.to_string()) {
Entry::Occupied(mut entry) => {
let Response {
submission_time,
response,
} = entry.get();
match response {
Ok(()) => return Ok(()),
Err(ErrorSerializable::Incorrect) => return Err(Error::Incorrect),
Err(ErrorSerializable::RateLimit(time)) => {
let remaining_seconds = get_remaining_time(submission_time, time);
match remaining_seconds {
Some(remaining_seconds) => {
return Err(Error::RateLimit(format!("{remaining_seconds}s")))
}
None => {
let response = post_fn(result);
let translated = match &response {
Ok(()) => Ok(()),
Err(e) => Err(ErrorSerializable::from(e)),
};
entry.insert(Response {
submission_time: Utc::now(),
response: translated,
});
response
}
}
}
Err(_err) => {
let response = post_fn(result);
let translated = match &response {
Ok(()) => Ok(()),
Err(e) => Err(ErrorSerializable::from(e)),
};
entry.insert(Response {
submission_time: Utc::now(),
response: translated,
});
response
}
}
}
Entry::Vacant(entry) => {
let response = post_fn(result);
let translated = match &response {
Ok(()) => Ok(()),
Err(e) => Err(ErrorSerializable::from(e)),
};
entry.insert(Response {
submission_time: Utc::now(),
response: translated,
});
response
}
};
if let Ok(cache_file) = std::fs::File::options()
.truncate(true)
.create(true)
.write(true)
.open(cache_path)
{
let _ = serde_json::to_writer(cache_file, &full_cache);
}
final_response
}
else {
post_fn(result)
}
}