use std::{
env,
fs::{self, File},
io::{BufReader, Cursor},
path::{Path, PathBuf},
time::Instant,
};
use crate::{
error::{HResult, HrdfError},
models::Version,
storage::DataStorage,
};
use bincode::config;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use url::Url;
use zip::ZipArchive;
#[derive(Debug, Serialize, Deserialize)]
pub struct Hrdf {
data_storage: DataStorage,
}
impl Hrdf {
pub async fn new(
version: Version,
url_or_path: &str,
force_rebuild_cache: bool,
cache_prefix: Option<String>,
) -> HResult<Self> {
let now = Instant::now();
let unique_filename = format!("{:x}", Sha256::digest(url_or_path.as_bytes()));
let cache_path = PathBuf::from(&cache_prefix.unwrap_or(String::from("./")))
.join(format!("{unique_filename}.cache"));
let hrdf = if cache_path.exists() && !force_rebuild_cache {
log::info!("Loading HRDF data from cache ({cache_path:?})...");
Self::load_from_cache(&cache_path).ok()
} else {
None
};
let hrdf = if let Some(hrdf) = hrdf {
hrdf
} else {
let compressed_data_path = if Url::parse(url_or_path).is_ok() {
let compressed_data_path = env::temp_dir().join(format!("{unique_filename}.zip"));
if !compressed_data_path.exists() {
log::info!("Downloading HRDF data to {compressed_data_path:?}...");
let response = reqwest::get(url_or_path).await?.error_for_status()?;
let mut file = std::fs::File::create(&compressed_data_path)?;
let mut content = Cursor::new(response.bytes().await?);
std::io::copy(&mut content, &mut file)?;
}
compressed_data_path
} else {
PathBuf::from(url_or_path)
};
let decompressed_data_path = env::temp_dir().join(unique_filename);
if !decompressed_data_path.exists() {
log::info!("Unzipping HRDF archive into {decompressed_data_path:?}...");
let file = File::open(&compressed_data_path)?;
let mut archive = ZipArchive::new(BufReader::new(file))?;
archive.extract(&decompressed_data_path)?;
}
log::info!("Parsing HRDF data from {decompressed_data_path:?}...");
let hrdf = Self {
data_storage: DataStorage::new(version, &decompressed_data_path)?,
};
log::info!("Building cache...");
hrdf.build_cache(&cache_path)?;
hrdf
};
let elapsed = now.elapsed();
log::info!("HRDF data loaded in {elapsed:.2?}!");
Ok(hrdf)
}
pub async fn try_from_date(
date: NaiveDate,
force_rebuild_cache: bool,
cache_prefix: Option<String>,
) -> HResult<Self> {
let url = Version::try_url(date)?;
let version = Version::try_from(date)?;
log::info!("Loading Hrdf Version ({version}) and Date ({date}) from url: {url}.");
Self::new(version, &url, force_rebuild_cache, cache_prefix).await
}
pub async fn try_from_year(
year: i32,
force_rebuild_cache: bool,
cache_prefix: Option<String>,
) -> HResult<Self> {
let date = NaiveDate::from_ymd_opt(year, 1, 1).ok_or_else(|| HrdfError::InvalidYear)?;
Self::try_from_date(date, force_rebuild_cache, cache_prefix).await
}
pub fn data_storage(&self) -> &DataStorage {
&self.data_storage
}
pub fn build_cache(&self, path: &Path) -> HResult<()> {
let data = bincode::serde::encode_to_vec(self, config::standard())?;
fs::write(path, data)?;
Ok(())
}
pub fn load_from_cache(path: &Path) -> HResult<Self> {
let data = fs::read(path)?;
let (hrdf, _) = bincode::serde::decode_from_slice(&data, config::standard())?;
Ok(hrdf)
}
}