use std::{
fs,
path::{
Path,
PathBuf,
},
};
use downloader::{
Download,
Downloader,
};
use crate::{
data::cache::{
BUNSEN_CACHE_CONFIG,
path_utils,
},
errors::{
BunsenError,
BunsenResult,
},
};
pub const BUNSEN_CACHE_DIR: &str = "BUNSEN_CACHE_DIR";
pub const BUNSEN_DATA_DIR: &str = "BUNSEN_DATA_DIR";
#[derive(Clone, Default, Debug)]
pub struct BunsenDiskCacheOptions {
pub cache_dir: Option<PathBuf>,
pub data_dir: Option<PathBuf>,
pub downloader: Option<fn() -> Downloader>,
}
impl BunsenDiskCacheOptions {
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 BunsenDiskCache {
cache_dir: PathBuf,
data_dir: PathBuf,
downloader: Downloader,
}
impl Default for BunsenDiskCache {
fn default() -> Self {
Self::new(BunsenDiskCacheOptions::default()).unwrap()
}
}
impl BunsenDiskCache {
pub fn new(options: BunsenDiskCacheOptions) -> BunsenResult<Self> {
let cache_dir = BUNSEN_CACHE_CONFIG
.resolve_cache_dir(options.cache_dir)
.ok_or(BunsenError::ResourceNotFound(
"failed to resolve cache directory".to_string(),
))?;
let data_dir = BUNSEN_CACHE_CONFIG
.resolve_data_dir(options.data_dir)
.ok_or(BunsenError::ResourceNotFound(
"failed to resolve data directory".to_string(),
))?;
let downloader = match options.downloader {
Some(builder) => builder(),
None => Downloader::builder()
.build()
.map_err(BunsenError::external)?,
};
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
}
fn _load_resource<P, C, S>(
&mut self,
root: &P,
context: &[C],
urls: &[S],
download: bool,
) -> BunsenResult<PathBuf>
where
P: AsRef<Path>,
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 = path_utils::extend_path(root, context, &file_name);
dl.file_name = path.clone();
if path.exists() {
return Ok(path);
}
if !download {
return Err(BunsenError::ResourceNotFound(format!(
"cached file not found: {}",
path.display()
)));
}
fs::create_dir_all(path.parent().unwrap()).map_err(BunsenError::external)?;
self.downloader
.download(&[dl])
.map_err(BunsenError::external)?;
Ok(path)
}
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 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)
}
pub fn load_cached_path<C, S>(
&mut self,
context: &[C],
urls: &[S],
download: bool,
) -> BunsenResult<PathBuf>
where
C: AsRef<Path>,
S: AsRef<str>,
{
let root = self.cache_dir.clone();
self._load_resource(&root, context, urls, download)
}
pub fn load_data_path<C, S>(
&mut self,
context: &[C],
urls: &[S],
download: bool,
) -> BunsenResult<PathBuf>
where
C: AsRef<Path>,
S: AsRef<str>,
{
let root = self.cache_dir.clone();
self._load_resource(&root, context, urls, download)
}
}
#[cfg(test)]
mod tests {
use std::{
env,
path::PathBuf,
};
use serial_test::serial;
use crate::data::cache::{
BUNSEN_CACHE_CONFIG,
BUNSEN_CACHE_DIR,
BUNSEN_DATA_DIR,
BunsenDiskCache,
BunsenDiskCacheOptions,
};
#[test]
#[serial]
fn test_resolve_dirs() {
let orig_cache_dir = env::var(BUNSEN_CACHE_DIR);
let orig_data_dir = env::var(BUNSEN_CACHE_DIR);
let pds = BUNSEN_CACHE_CONFIG
.project_dirs()
.expect("failed to get project dirs");
let user_cache_dir = PathBuf::from("/tmp/bunsen/cache");
let user_data_dir = PathBuf::from("/tmp/bunsen/data");
let env_cache_dir = PathBuf::from("/tmp/bunsen/env_cache");
let env_data_dir = PathBuf::from("/tmp/bunsen/env_data");
unsafe {
env::remove_var(BUNSEN_CACHE_DIR);
env::remove_var(BUNSEN_DATA_DIR);
}
let cache = BunsenDiskCache::new(
BunsenDiskCacheOptions::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 = BunsenDiskCache::new(BunsenDiskCacheOptions::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(BUNSEN_CACHE_DIR, env_cache_dir.to_str().unwrap());
env::set_var(BUNSEN_DATA_DIR, env_data_dir.to_str().unwrap());
}
let cache = BunsenDiskCache::new(
BunsenDiskCacheOptions::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 = BunsenDiskCache::new(BunsenDiskCacheOptions::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(BUNSEN_CACHE_DIR, original) },
Err(_) => unsafe { env::remove_var(BUNSEN_CACHE_DIR) },
}
match orig_data_dir {
Ok(original) => unsafe { env::set_var(BUNSEN_DATA_DIR, original) },
Err(_) => unsafe { env::remove_var(BUNSEN_DATA_DIR) },
}
}
#[test]
fn test_data_path() {
let cache = BunsenDiskCache::new(BunsenDiskCacheOptions::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 = BunsenDiskCache::new(BunsenDiskCacheOptions::default()).unwrap();
let path = cache.cache_path(&["prefix"], "file.txt");
assert_eq!(path, cache.cache_dir.join("prefix").join("file.txt"));
}
}