use std::{
fs,
path::{
Path,
PathBuf,
},
};
use downloader::{
Download,
Downloader,
};
use crate::{
WORDCHIPPER_CACHE_CONFIG,
path_utils,
};
#[derive(Clone, Default, Debug)]
pub struct WordchipperDiskCacheOptions {
pub cache_dir: Option<PathBuf>,
pub data_dir: Option<PathBuf>,
pub downloader: Option<fn() -> Downloader>,
}
impl WordchipperDiskCacheOptions {
pub fn with_cache_dir<P: AsRef<Path>>(
mut self,
cache_dir: Option<P>,
) -> Self {
self.cache_dir = cache_dir.map(|p| p.as_ref().to_path_buf());
self
}
pub fn with_data_dir<P: AsRef<Path>>(
mut self,
data_dir: Option<P>,
) -> Self {
self.data_dir = data_dir.map(|p| p.as_ref().to_path_buf());
self
}
pub fn with_downloader(
mut self,
downloader: Option<fn() -> Downloader>,
) -> Self {
self.downloader = downloader;
self
}
}
pub struct WordchipperDiskCache {
cache_dir: PathBuf,
data_dir: PathBuf,
downloader: Downloader,
}
impl Default for WordchipperDiskCache {
fn default() -> Self {
Self::new(WordchipperDiskCacheOptions::default()).unwrap()
}
}
impl WordchipperDiskCache {
pub fn new(options: WordchipperDiskCacheOptions) -> Result<Self, Box<dyn std::error::Error>> {
let cache_dir = WORDCHIPPER_CACHE_CONFIG
.resolve_cache_dir(options.cache_dir)
.ok_or("failed to resolve cache directory")?;
let data_dir = WORDCHIPPER_CACHE_CONFIG
.resolve_data_dir(options.data_dir)
.ok_or("failed to resolve data directory")?;
let downloader = match options.downloader {
Some(builder) => builder(),
None => Downloader::builder().build()?,
};
Ok(Self {
cache_dir,
data_dir,
downloader,
})
}
pub fn cache_dir(&self) -> &Path {
&self.cache_dir
}
pub fn data_dir(&self) -> &Path {
&self.data_dir
}
pub fn downloader(&self) -> &Downloader {
&self.downloader
}
pub fn cache_path<C, F>(
&self,
context: &[C],
file: F,
) -> PathBuf
where
C: AsRef<Path>,
F: AsRef<Path>,
{
path_utils::extend_path(&self.cache_dir, context, file)
}
pub fn load_cached_path<C, S>(
&mut self,
context: &[C],
urls: &[S],
download: bool,
) -> Result<PathBuf, Box<dyn std::error::Error>>
where
C: AsRef<Path>,
S: AsRef<str>,
{
let urls: Vec<_> = urls.iter().map(|s| s.as_ref()).collect();
let mut dl = Download::new_mirrored(&urls);
let file_name = dl.file_name.clone();
let path = self.cache_path(context, &file_name);
dl.file_name = path.clone();
if path.exists() {
return Ok(path);
}
if !download {
return Err(format!("cached file not found: {}", path.display()).into());
}
fs::create_dir_all(path.parent().unwrap())?;
self.downloader.download(&[dl])?;
Ok(path)
}
pub fn data_path<C, F>(
&self,
context: &[C],
file: F,
) -> PathBuf
where
C: AsRef<Path>,
F: AsRef<Path>,
{
path_utils::extend_path(&self.data_dir, context, file)
}
}
#[cfg(test)]
mod tests {
use std::{
env,
path::PathBuf,
};
use serial_test::serial;
use crate::{
WORDCHIPPER_CACHE_CONFIG,
WORDCHIPPER_CACHE_DIR,
WORDCHIPPER_DATA_DIR,
disk_cache::{
WordchipperDiskCache,
WordchipperDiskCacheOptions,
},
};
#[test]
#[serial]
fn test_resolve_dirs() {
let orig_cache_dir = env::var(WORDCHIPPER_CACHE_DIR);
let orig_data_dir = env::var(WORDCHIPPER_CACHE_DIR);
let pds = WORDCHIPPER_CACHE_CONFIG
.project_dirs()
.expect("failed to get project dirs");
let user_cache_dir = PathBuf::from("/tmp/wordchipper/cache");
let user_data_dir = PathBuf::from("/tmp/wordchipper/data");
let env_cache_dir = PathBuf::from("/tmp/wordchipper/env_cache");
let env_data_dir = PathBuf::from("/tmp/wordchipper/env_data");
unsafe {
env::remove_var(WORDCHIPPER_CACHE_DIR);
env::remove_var(WORDCHIPPER_DATA_DIR);
}
let cache = WordchipperDiskCache::new(
WordchipperDiskCacheOptions::default()
.with_cache_dir(Some(user_cache_dir.clone()))
.with_data_dir(Some(user_data_dir.clone())),
)
.unwrap();
assert_eq!(&cache.cache_dir(), &user_cache_dir);
assert_eq!(&cache.data_dir(), &user_data_dir);
let cache = WordchipperDiskCache::new(WordchipperDiskCacheOptions::default()).unwrap();
assert_eq!(&cache.cache_dir(), &pds.cache_dir().to_path_buf());
assert_eq!(&cache.data_dir(), &pds.data_dir().to_path_buf());
unsafe {
env::set_var(WORDCHIPPER_CACHE_DIR, env_cache_dir.to_str().unwrap());
env::set_var(WORDCHIPPER_DATA_DIR, env_data_dir.to_str().unwrap());
}
let cache = WordchipperDiskCache::new(
WordchipperDiskCacheOptions::default()
.with_cache_dir(Some(user_cache_dir.clone()))
.with_data_dir(Some(user_data_dir.clone())),
)
.unwrap();
assert_eq!(&cache.cache_dir(), &user_cache_dir);
assert_eq!(&cache.data_dir(), &user_data_dir);
let cache = WordchipperDiskCache::new(WordchipperDiskCacheOptions::default()).unwrap();
assert_eq!(&cache.cache_dir(), &env_cache_dir);
assert_eq!(&cache.data_dir(), &env_data_dir);
match orig_cache_dir {
Ok(original) => unsafe { env::set_var(WORDCHIPPER_CACHE_DIR, original) },
Err(_) => unsafe { env::remove_var(WORDCHIPPER_CACHE_DIR) },
}
match orig_data_dir {
Ok(original) => unsafe { env::set_var(WORDCHIPPER_DATA_DIR, original) },
Err(_) => unsafe { env::remove_var(WORDCHIPPER_DATA_DIR) },
}
}
#[test]
fn test_data_path() {
let cache = WordchipperDiskCache::new(WordchipperDiskCacheOptions::default()).unwrap();
let path = cache.data_path(&["prefix"], "file.txt");
assert_eq!(path, cache.data_dir.join("prefix").join("file.txt"));
}
#[test]
fn test_cache_path() {
let cache = WordchipperDiskCache::new(WordchipperDiskCacheOptions::default()).unwrap();
let path = cache.cache_path(&["prefix"], "file.txt");
assert_eq!(path, cache.cache_dir.join("prefix").join("file.txt"));
}
}