type Result<T> = std::result::Result<T, MeasurementError>;
use super::*;
use crate::{
ps::CPSSettings,
tools::{get_current_timestamp, h5::*},
*,
};
use hdf5_metno::{Error, File};
use itertools::Itertools;
use ndarray::ArrayView3;
use snafu::prelude::*;
use std::hash::{DefaultHasher, Hash, Hasher};
const CACHED_CPS_GROUPNAME: &str = "cached_cps";
const CACHED_CPS_DATASETNAME_BASE: &str = "cached_cps/cps_";
const MAX_NUM_CACHED_CPS: usize = 99;
fn get_dset_name_for_index(index: usize) -> String {
format!("{}{:02}", CACHED_CPS_DATASETNAME_BASE, index)
}
fn read_current_cps_meta(file: &File) -> Vec<(usize, String, Flt, u64)> {
let mut res = Vec::with_capacity(MAX_NUM_CACHED_CPS);
for i in 0..MAX_NUM_CACHED_CPS {
let dset_name = get_dset_name_for_index(i);
if let Ok(dataset) = file.dataset(&dset_name)
&& let Ok(timestamp) = read_h5_attr_scalar(&dataset, "timestamp")
&& let Ok(hash) = read_h5_attr_scalar(&dataset, "settings_hash")
{
res.push((i, dset_name, timestamp, hash));
}
}
res
}
pub fn store_CPS(file: &File, settings: &CPSSettings, cps: &Array3<Cflt>) -> Result<()> {
let file_members = file
.member_names()
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "reading file members",
})?;
if !file_members
.iter()
.map(|a| a.as_str())
.any(|m| m == CACHED_CPS_GROUPNAME)
{
file.create_group(CACHED_CPS_GROUPNAME)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "creating cached CPS group in file",
})?;
}
let mut hasher = DefaultHasher::new();
settings.hash(&mut hasher);
let settings_hash = hasher.finish();
let settings_json = serde_json::to_string(settings).expect("Failed to serialize CPS settings");
let current_timestamp = get_current_timestamp();
let current_meta = read_current_cps_meta(file);
let target_index = if current_meta.len() == MAX_NUM_CACHED_CPS {
let mut oldest_index = 0;
let mut oldest_timestamp = current_timestamp;
for (i, (_, _, timestamp, _)) in current_meta.iter().enumerate() {
if *timestamp < oldest_timestamp {
oldest_timestamp = *timestamp;
oldest_index = i;
}
}
oldest_index
} else if let Some(i) = current_meta
.iter()
.map(|a| a.3)
.position(|a| a == settings_hash)
{
i
} else {
current_meta.len()
};
let dset_name = get_dset_name_for_index(target_index);
if current_meta.iter().map(|a| &a.1).contains(&dset_name) {
file.unlink(&dset_name)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "unlinking old CPS dataset",
})?;
}
let dataset = file
.new_dataset_builder()
.with_data(cps)
.create(dset_name.as_str())
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "creating new CPS dataset",
})?;
write_h5_attr_scalar(&dataset, "settings_hash", settings_hash)?;
write_h5_attr_string(&dataset, "settings_json", &settings_json)?;
write_h5_attr_scalar(&dataset, "timestamp", current_timestamp)?;
Ok(())
}
pub fn load_CPS(file: &File, settings: &CPSSettings) -> Result<Option<ndarray::Array3<Cflt>>> {
let mut hasher = DefaultHasher::new();
settings.hash(&mut hasher);
let settings_hash = hasher.finish();
let current_meta = read_current_cps_meta(file);
for (index, dset_name, _timestamp, hash) in current_meta.into_iter() {
if hash == settings_hash {
let dset =
file.dataset(&dset_name)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "reading CPS dataset",
})?;
let settings_json = read_h5_attr_string(&dset, "settings_json")?;
let this_settings: CPSSettings = serde_json::from_str(&settings_json)
.map_err(JSONError::from)
.context(ParseMetaSnafu {
field: "settings_json",
})?;
if this_settings != *settings {
continue;
}
let dset_name = get_dset_name_for_index(index);
let dataset =
file.dataset(&dset_name)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "reading CPS dataset",
})?;
let cps_data: ndarray::Array3<Cflt> =
dataset
.read()
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "reading CPS data",
})?;
return Ok(Some(cps_data));
}
}
Ok(None)
}
pub fn clear_all_cached_cps(file: &File) -> Result<()> {
file.unlink(CACHED_CPS_GROUPNAME)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "unlinking cached CPS group",
})?;
Ok(())
}
#[expect(dead_code)]
pub fn remove_cached_cps(file: &File, settings: &CPSSettings) -> Result<bool> {
let mut hasher = DefaultHasher::new();
settings.hash(&mut hasher);
let settings_hash = hasher.finish();
let current_meta = read_current_cps_meta(file);
for (_, dset_name, _, hash) in ¤t_meta {
if *hash == settings_hash {
let dset =
file.dataset(dset_name)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "reading CPS dataset",
})?;
let settings_json = read_h5_attr_string(&dset, "settings_json")?;
let this_settings: CPSSettings = serde_json::from_str(&settings_json)
.map_err(JSONError::from)
.context(ParseMetaSnafu {
field: "settings_json",
})?;
if *settings == this_settings {
file.unlink(dset_name)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "unlinking CPS dataset",
})?;
}
return Ok(true);
}
}
Ok(false)
}