use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, PoisonError};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tracing::{debug, info};
use super::GeoIpDownloadError;
const STAMP_EXT: &str = "retry";
pub(super) const FLOOR_SECS: u64 = 60 * 60;
pub(super) const CEILING_SECS: u64 = 24 * 60 * 60;
static ANNOUNCED: Mutex<Option<HashSet<(PathBuf, u64)>>> = Mutex::new(None);
pub(super) fn stamp_path(dest: &Path) -> PathBuf {
let mut name = dest.as_os_str().to_os_string();
name.push(".");
name.push(STAMP_EXT);
PathBuf::from(name)
}
fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs())
}
pub(super) fn wait_for(dest: &Path, kind: &'static str) -> Option<Duration> {
let stamp = stamp_path(dest);
let until: u64 = fs::read_to_string(&stamp).ok()?.trim().parse().ok()?;
let now = now_secs();
let until = until.min(now + CEILING_SECS);
let remaining = until.checked_sub(now).filter(|left| *left > 0)?;
let first = ANNOUNCED
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get_or_insert_default()
.insert((stamp, until));
if first {
info!(
kind,
dest = %dest.display(),
wait_secs = remaining,
"GeoIP provider asked us to wait, serving the copy on disk until then"
);
}
Some(Duration::from_secs(remaining))
}
pub(super) fn begin(dest: &Path, error: &GeoIpDownloadError) {
let wait = match error {
GeoIpDownloadError::RateLimited {
retry_after_secs, ..
} => retry_after_secs.unwrap_or(FLOOR_SECS),
other if other.outcome() == "refused" => FLOOR_SECS,
_ => return,
};
let until = now_secs() + wait.min(CEILING_SECS);
if let Err(e) = fs::write(stamp_path(dest), until.to_string()) {
debug!(dest = %dest.display(), error = %e, "could not record the cool-down");
}
}
pub(super) fn clear(dest: &Path) {
let _ = fs::remove_file(stamp_path(dest));
}