use super::cache;
use super::download;
use super::registry::{self, DatasetId, DatasetMeta};
use super::DataError;
use std::path::{Path, PathBuf};
pub struct DataManager {
data_dir: PathBuf,
}
impl DataManager {
pub fn new() -> Result<Self, DataError> {
let data_dir = cache::resolve_data_dir()?;
cache::ensure_data_dir(&data_dir)?;
Ok(Self { data_dir })
}
pub fn with_dir(dir: impl Into<PathBuf>) -> Result<Self, DataError> {
let data_dir = dir.into();
cache::ensure_data_dir(&data_dir)?;
Ok(Self { data_dir })
}
pub fn data_dir(&self) -> &Path {
&self.data_dir
}
pub fn is_available(&self, id: DatasetId) -> bool {
let Some(meta) = registry::lookup(id) else {
return false;
};
cache::is_cached(&self.data_dir, meta)
}
pub fn load_path(&self, id: DatasetId) -> Option<PathBuf> {
let meta = registry::lookup(id)?;
if cache::is_cached(&self.data_dir, meta) {
Some(cache::dataset_path(&self.data_dir, meta))
} else {
None
}
}
pub fn ensure(&self, id: DatasetId) -> Result<PathBuf, DataError> {
let meta = self.require_meta(id)?;
let path = cache::dataset_path(&self.data_dir, meta);
if cache::is_cached(&self.data_dir, meta) {
cache::verify(&path, meta)?;
return Ok(path);
}
self.download_inner(meta, &path, None)?;
Ok(path)
}
pub fn download(
&self,
id: DatasetId,
progress: Option<download::ProgressCallback>,
) -> Result<PathBuf, DataError> {
let meta = self.require_meta(id)?;
let path = cache::dataset_path(&self.data_dir, meta);
self.download_inner(meta, &path, progress)?;
Ok(path)
}
pub fn list(&self) -> Vec<(DatasetId, bool)> {
registry::DATASETS
.iter()
.map(|meta| (meta.id, cache::is_cached(&self.data_dir, meta)))
.collect()
}
fn require_meta(&self, id: DatasetId) -> Result<&'static DatasetMeta, DataError> {
registry::lookup(id).ok_or_else(|| DataError::UnknownDataset(id.as_str().to_string()))
}
fn download_inner(
&self,
meta: &DatasetMeta,
path: &Path,
progress: Option<download::ProgressCallback>,
) -> Result<(), DataError> {
download::download(meta, path, progress)?;
cache::verify(path, meta)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_dir_path(suffix: &str) -> PathBuf {
std::env::temp_dir().join(format!("siderust_mgr_{}", suffix))
}
#[test]
fn with_dir_creates_manager() {
let dir = temp_dir_path("with_dir");
let _ = std::fs::remove_dir_all(&dir);
let dm = DataManager::with_dir(&dir).unwrap();
assert_eq!(dm.data_dir(), dir.as_path());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn data_dir_returns_configured_path() {
let dir = temp_dir_path("data_dir_getter");
let dm = DataManager::with_dir(&dir).unwrap();
assert_eq!(dm.data_dir(), dir.as_path());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn is_available_returns_false_when_not_cached() {
let dir = temp_dir_path("is_avail_false");
let _ = std::fs::remove_dir_all(&dir);
let dm = DataManager::with_dir(&dir).unwrap();
assert!(!dm.is_available(DatasetId::De440));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn is_available_returns_true_when_file_present() {
let dir = temp_dir_path("is_avail_true");
let dm = DataManager::with_dir(&dir).unwrap();
let meta = registry::lookup(DatasetId::De440).unwrap();
let path = dir.join(meta.filename);
let data = vec![0u8; (meta.min_size + 100) as usize];
std::fs::write(&path, &data).unwrap();
assert!(dm.is_available(DatasetId::De440));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn load_path_returns_none_when_not_cached() {
let dir = temp_dir_path("load_path_none");
let _ = std::fs::remove_dir_all(&dir);
let dm = DataManager::with_dir(&dir).unwrap();
assert!(dm.load_path(DatasetId::De440).is_none());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn load_path_returns_some_when_cached() {
let dir = temp_dir_path("load_path_some");
let dm = DataManager::with_dir(&dir).unwrap();
let meta = registry::lookup(DatasetId::IersEop).unwrap();
let path = dir.join(meta.filename);
let data = vec![0u8; (meta.min_size + 100) as usize];
std::fs::write(&path, &data).unwrap();
let result = dm.load_path(DatasetId::IersEop);
assert!(result.is_some());
assert_eq!(result.unwrap(), path);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn list_returns_all_datasets() {
let dir = temp_dir_path("list_datasets");
let dm = DataManager::with_dir(&dir).unwrap();
let items = dm.list();
assert!(!items.is_empty());
assert!(items.iter().all(|(_, avail)| !avail));
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn ensure_returns_path_when_already_cached() {
let dir = temp_dir_path("ensure_cached");
let dm = DataManager::with_dir(&dir).unwrap();
let meta = registry::lookup(DatasetId::IersEop).unwrap();
let path = dir.join(meta.filename);
let data = vec![42u8; (meta.min_size + 100) as usize];
std::fs::write(&path, &data).unwrap();
let result = dm.ensure(DatasetId::IersEop).unwrap();
assert_eq!(result, path);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
#[cfg(feature = "runtime-data")]
fn ensure_unknown_dataset_returns_error() {
let dir = temp_dir_path("ensure_unknown");
let dm = DataManager::with_dir(&dir).unwrap();
let result = dm.ensure(DatasetId::Elp2000);
if let Err(e) = result {
let s = format!("{}", e);
assert!(
s.contains("elp2000") || s.contains("unknown") || s.contains("Unknown"),
"Unexpected error: {s}"
);
}
std::fs::remove_dir_all(&dir).ok();
}
}