use super::super::errors::*;
use {
kutil::std::error::*,
rand::{distr::*, *},
std::{collections::*, env::*, fs::*, path::*, sync::*},
tracing::info,
};
const RANDOM_NAME_LENGTH: usize = 32;
pub type PathBufRef = Arc<Mutex<PathBuf>>;
type PathBufRefMap = LazyLock<Mutex<HashMap<String, PathBufRef>>>;
#[derive(Debug)]
pub struct UrlCache {
pub base_directory: PathBuf,
pub files: PathBufRefMap,
pub directories: PathBufRefMap,
}
impl UrlCache {
pub fn new(base_directory: Option<PathBuf>) -> Self {
let base_directory = base_directory.unwrap_or_else(|| Self::default_base_directory());
Self {
base_directory,
files: LazyLock::new(|| HashMap::default().into()),
directories: LazyLock::new(|| HashMap::default().into()),
}
}
pub fn default_base_directory() -> PathBuf {
temp_dir().join("read-url")
}
pub fn reset(&self) -> Result<(), UrlError> {
let mut errors = Vec::default();
let mut files = self.files.lock()?;
for path in files.values() {
let path = path.lock()?;
info!("deleting file: {}", path.display());
let path = path.as_path();
if let Err(error) = remove_file(path) {
errors.push(error.with_path(path));
}
}
files.clear();
let mut directories = self.directories.lock()?;
for path in directories.values() {
let path = path.lock()?;
info!("deleting directory: {}", path.display());
let path = path.as_path();
if let Err(error) = remove_dir_all(path) {
errors.push(error.with_path(path));
}
}
directories.clear();
if errors.is_empty() { Ok(()) } else { Err(UrlError::IoMany(errors)) }
}
pub fn file(&self, key: &str, prefix: &str) -> Result<(PathBufRef, bool), UrlError> {
let key = key.to_string();
let mut files = self.files.lock()?;
match files.get(&key) {
Some(path) => {
info!("existing file: {}", path.clone().lock()?.display());
Ok((path.clone(), true))
}
None => {
let path = self.new_path(prefix)?;
info!("new file: {}", path.display());
let path = Arc::new(Mutex::new(path));
files.insert(key, path.clone());
Ok((path, false))
}
}
}
pub fn directory(&self, key: &str, prefix: &str) -> Result<(PathBufRef, bool), UrlError> {
let key = key.to_string();
let mut directories = self.directories.lock()?;
match directories.get(&key) {
Some(path) => {
info!("existing directory: {}", path.clone().lock()?.display());
Ok((path.clone(), true))
}
None => {
let path = self.new_path(prefix)?;
info!("new directory: {}", path.display());
let path = Arc::new(Mutex::new(path));
directories.insert(key, path.clone());
Ok((path, false))
}
}
}
pub(crate) fn new_path(&self, prefix: &str) -> Result<PathBuf, UrlError> {
create_dir_all(&self.base_directory).with_path(&self.base_directory)?;
let distribution = Uniform::new_inclusive('a', 'z').expect("Uniform::new_inclusive");
let path: String = rng().sample_iter(distribution).take(RANDOM_NAME_LENGTH).collect();
let path = prefix.to_string() + &path;
Ok(self.base_directory.join(path))
}
}
impl Drop for UrlCache {
fn drop(&mut self) {
_ = self.reset();
}
}