use crate::loadable_device::LoadableDevice as _;
use crate::samplic::{
Curve, DeviceDescription, HasParameterType, Icon, LoadableDevice,
Measurement, MeasurementMetadata, MeasurementResults,
ParameterDescription, ProfileDescription, ProfileParameterDescription,
Sample, SampleMetadata, Sampling, StorableDevice, Translation,
UnitDescription,
};
use crate::{
error, file_load_json as load_json, file_load_string as load_string,
LoadPathPattern, Result,
};
use indexmap::IndexSet;
use snafu::{OptionExt, ResultExt};
use std::path::PathBuf;
use uuid::Uuid;
#[derive(Debug)]
pub struct Device {
inner: crate::filesystem::Device,
}
impl Device {
pub fn new(inner: crate::filesystem::Device) -> Self {
Device { inner }
}
pub fn system_dir(&self) -> PathBuf {
self.inner.system_dir()
}
pub fn samplic_dir(&self) -> PathBuf {
self.inner.samplic_dir()
}
pub fn data_dir(&self) -> PathBuf {
self.inner.data_dir()
}
pub fn config_dir(&self) -> PathBuf {
self.inner.config_dir()
}
pub fn units_dir(&self) -> PathBuf {
self.samplic_dir().join("unit")
}
pub fn unit_file(&self, unit_id: &str) -> PathBuf {
self.units_dir().join(unit_id).join("unit.json")
}
pub fn translations_dir(&self) -> PathBuf {
self.samplic_dir().join("translation")
}
pub fn translation_file(&self, translation_id: &str) -> PathBuf {
self.translations_dir()
.join(translation_id)
.join("translation.json")
}
pub fn sample_index_file(&self) -> PathBuf {
self.data_dir().join("index.json")
}
pub fn samples_dir(&self) -> PathBuf {
self.data_dir().join("sample")
}
pub fn sample_dir(&self, sample_id: Uuid) -> PathBuf {
self.samples_dir()
.join(sample_id.to_hyphenated().to_string())
}
pub fn sample_metadata_dir(&self, sample_id: Uuid) -> PathBuf {
self.sample_dir(sample_id).join("metadata")
}
pub fn sample_metadata_file(&self, sample_id: Uuid) -> PathBuf {
self.sample_metadata_dir(sample_id).join("metadata.json")
}
pub fn measurements_dir(&self, sample_id: Uuid) -> PathBuf {
self.sample_dir(sample_id).join("measurement")
}
pub fn measurement_dir(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurements_dir(sample_id)
.join(format!("{}", measurement_index))
}
pub fn measurement_metadata_dir(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_dir(sample_id, measurement_index)
.join("metadata")
}
pub fn measurement_metadata_file(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_metadata_dir(sample_id, measurement_index)
.join("metadata.json")
}
pub fn curves_dir(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_dir(sample_id, measurement_index)
.join("curve")
}
pub fn curve_dir(
&self,
sample_id: Uuid,
measurement_index: usize,
curve_id: &str,
) -> PathBuf {
self.curves_dir(sample_id, measurement_index).join(curve_id)
}
pub fn curve_file(
&self,
sample_id: Uuid,
measurement_index: usize,
curve_id: &str,
) -> PathBuf {
self.curve_dir(sample_id, measurement_index, curve_id)
.join("curve.json")
}
pub fn results_dir(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_dir(sample_id, measurement_index)
.join("results")
}
pub fn results_file(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.results_dir(sample_id, measurement_index)
.join("results.json")
}
pub fn description_dir(&self) -> PathBuf {
self.inner.description_dir()
}
pub fn samplings_dir(&self) -> PathBuf {
self.description_dir().join("sampling")
}
pub fn sampling_file(&self, sampling_id: &str) -> PathBuf {
self.samplings_dir()
.join(sampling_id)
.join("samplic")
.join("sampling.json")
}
pub fn device_dir(&self) -> PathBuf {
self.inner.device_dir()
}
pub fn device_samplic_dir(&self) -> PathBuf {
self.device_dir().join("samplic")
}
pub fn device_description_file(&self) -> PathBuf {
self.device_samplic_dir().join("device.json")
}
pub fn system_profiles_dir(&self) -> PathBuf {
self.description_dir().join("profile")
}
pub fn system_profile_dir(&self, profile_id: Uuid) -> PathBuf {
self.system_profiles_dir()
.join(profile_id.to_hyphenated().to_string())
}
pub fn system_profile_file(&self, profile_id: Uuid) -> PathBuf {
self.system_profile_dir(profile_id)
.join("samplic")
.join("profile.json")
}
pub fn user_profiles_dir(&self) -> PathBuf {
self.config_dir().join("profile")
}
pub fn user_profile_dir(&self, profile_id: Uuid) -> PathBuf {
self.user_profiles_dir()
.join(profile_id.to_hyphenated().to_string())
}
pub fn user_profile_file(&self, profile_id: Uuid) -> PathBuf {
self.user_profile_dir(profile_id)
.join("samplic")
.join("profile.json")
}
pub fn images_dir(&self) -> PathBuf {
self.system_dir().join("image")
}
pub fn icons_dir(&self) -> PathBuf {
self.images_dir().join("icon")
}
pub fn icon_file(&self, icon_id: &str) -> PathBuf {
self.icons_dir().join(icon_id).join("icon.svg")
}
}
impl LoadableDevice for Device {
fn load_unit_index(&self) -> Result<IndexSet<String>> {
IndexSet::load_path_pattern_from_dir(
self.units_dir(),
"*/unit.json",
)
}
fn load_unit(&self, unit_id: &str) -> Result<UnitDescription> {
load_json(self.unit_file(unit_id))
}
fn load_translation_index(&self) -> Result<IndexSet<String>> {
IndexSet::load_path_pattern_from_dir(
self.translations_dir(),
"*/translation.json",
)
}
fn load_translation(
&self,
translation_id: &str,
) -> Result<Translation> {
load_json(self.translation_file(translation_id))
}
fn load_sample_metadata(
&self,
sample_id: Uuid,
) -> Result<SampleMetadata> {
load_json(self.sample_metadata_file(sample_id))
}
fn load_measurement_metadata(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> Result<MeasurementMetadata> {
load_json(
self.measurement_metadata_file(sample_id, measurement_index),
)
}
fn load_curve(
&self,
sample_id: Uuid,
measurement_index: usize,
curve_id: &str,
) -> Result<Curve> {
load_json(self.curve_file(sample_id, measurement_index, curve_id))
}
fn load_curve_index(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> Result<IndexSet<String>> {
IndexSet::load_path_pattern_from_dir(
self.curves_dir(sample_id, measurement_index),
"*/curve.json",
)
}
fn load_results(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> Result<MeasurementResults> {
load_json(self.results_file(sample_id, measurement_index))
}
fn load_sampling_index(&self) -> Result<IndexSet<String>> {
IndexSet::load_path_pattern_from_dir(
self.samplings_dir(),
"*/samplic/sampling.json",
)
}
fn load_measurement_index(
&self,
sample_id: Uuid,
) -> Result<IndexSet<usize>> {
let mut index = IndexSet::load_path_pattern_from_dir(
self.measurements_dir(sample_id),
"*",
)?;
index.sort();
Ok(index)
}
fn load_sample_index(&self) -> Result<IndexSet<Uuid>> {
load_json(self.sample_index_file())
}
fn load_device_description(&self) -> Result<DeviceDescription> {
load_json(self.device_description_file())
}
fn load_system_profile(
&self,
profile_id: Uuid,
) -> Result<ProfileDescription> {
load_json(self.system_profile_file(profile_id))
}
fn load_system_profile_index(&self) -> Result<IndexSet<Uuid>> {
load_json(self.inner.ommui().system_profiles_file())
}
fn load_user_profile(
&self,
profile_id: Uuid,
) -> Result<ProfileDescription> {
load_json(self.user_profile_file(profile_id))
}
fn load_user_profile_index(&self) -> Result<IndexSet<Uuid>> {
IndexSet::load_path_pattern_from_dir(self.user_profiles_dir(), "*")
}
fn load_sampling(&self, sampling_id: &str) -> Result<Sampling> {
load_json(self.sampling_file(sampling_id))
}
fn load_icon(&self, icon_id: &str) -> Result<Icon> {
load_string(self.icon_file(icon_id))
}
fn load_icon_index(&self) -> Result<IndexSet<String>> {
IndexSet::load_path_pattern_from_dir(
self.icons_dir(),
"*/icon.svg",
)
}
}
impl StorableDevice for Device {
fn store_user_profile(
&mut self,
profile_id: Uuid,
profile: &ProfileDescription,
) -> Result<()> {
let sampling = self.load_sampling(&profile.sampling_id)?;
let parent = self.load_profile(profile.parent)?;
ensure!(
parent.sampling_id == profile.sampling_id,
error::ProfileParentDifferentSamplingId {
parent_profile: profile.parent,
parent_sampling_id: parent.sampling_id.to_string(),
sampling_id: profile.sampling_id.to_string(),
}
);
ensure!(
profile.parameters.len() == sampling.parameters.len(),
error::ProfileParameterCountMismatchToSampling {
profile_parameter_count: profile.parameters.len(),
sampling_parameter_count: sampling.parameters.len(),
sampling_id: profile.sampling_id.to_string(),
}
);
for (k, v) in profile.parameters.iter() {
let description = sampling.parameters.get(k).context(
error::SamplingParameterNotFound {
parameter_id: k.to_string(),
sampling_id: profile.sampling_id.to_string(),
},
)?;
use self::ParameterDescription as D;
use self::ProfileParameterDescription as P;
match (description, &v) {
(D::Numeric(d), P::Numeric(p)) => {
ensure!(
p.value >= d.minimum && p.value <= d.maximum,
error::ProfileNumericParameterValueOutOfRange {
sampling_id: profile.sampling_id.to_string(),
parameter_id: k.to_string(),
minimum: d.minimum,
maximum: d.maximum,
value: p.value
}
);
ensure!(
d.unit
.representations
.contains(&p.representation.to_string()),
error::ParameterRepresentationNotAllowed {
sampling_id: profile.sampling_id.to_string(),
parameter_id: k.to_string(),
representation: p.representation.to_string(),
allowed_representations: d
.unit
.representations
.clone()
}
);
}
(D::Boolean(_d), P::Boolean(_p)) => {}
(D::Enumeration(d), P::Enumeration(p)) => ensure!(
d.enumeration_values.contains_key(&p.value),
error::EnumerationValueNotFound {
sampling_id: profile.sampling_id.to_string(),
parameter_id: k.to_string(),
enumeration_value: p.value.to_string(),
allowed_enumeration_values: d
.enumeration_values
.keys()
.map(ToString::to_string)
.collect::<Vec<String>>()
}
),
(d, p) => {
error::ProfileParameterTypeMismatch {
parameter_id: k.to_string(),
sampling_id: profile.sampling_id.to_string(),
required: d.parameter_type(),
found: p.parameter_type(),
}
.fail()?;
}
}
}
for p in profile.results.iter() {
let d = sampling.results.get(&p.result_id).context(
error::SamplingResultNotFound {
sampling_id: profile.sampling_id.to_string(),
result_id: p.result_id.to_string(),
},
)?;
ensure!(
d.unit.representations.contains(&p.representation),
error::ResultRepresentationNotAllowed {
result_id: p.result_id.to_string(),
representation: p.representation.to_string(),
sampling_id: profile.sampling_id.to_string(),
allowed_representations: d
.unit
.representations
.clone()
}
);
}
for pc in profile.curves.iter() {
let d = sampling.curves.get(&pc.curve_id).context(
error::SamplingCurveNotFound {
sampling_id: profile.sampling_id.to_string(),
curve_id: pc.curve_id.to_string(),
},
)?;
use crate::samplic::CoordinateSystemDescription as D;
use crate::samplic::ProfileCoordinateSystem as P;
match (&pc.axes, &d.coordinate_system) {
(P::Cartesian2d(p), D::Cartesian2d(d)) => {
{
let unit = &d.x.unit;
let representation = &p.x.representation;
let axis = "x".to_string();
ensure!(
unit.representations.contains(representation),
error::AxisUnitRepresentationNotAllowed {
sampling_id: profile
.sampling_id
.to_string(),
curve_id: pc.curve_id.to_string(),
axis,
representation: representation.to_string(),
allowed_representations: unit
.representations
.clone()
}
);
}
{
let unit = &d.y.unit;
let representation = &p.y.representation;
let axis = "y".to_string();
ensure!(
unit.representations.contains(representation),
error::AxisUnitRepresentationNotAllowed {
sampling_id: profile
.sampling_id
.to_string(),
curve_id: pc.curve_id.to_string(),
axis,
representation: representation.to_string(),
allowed_representations: unit
.representations
.clone()
}
);
}
}
(P::Polar(p), D::Polar(d)) => {
{
let unit = &d.angle.unit;
let representation = &p.angle.representation;
let axis = "angle".to_string();
ensure!(
unit.representations.contains(representation),
error::AxisUnitRepresentationNotAllowed {
sampling_id: profile
.sampling_id
.to_string(),
curve_id: pc.curve_id.to_string(),
axis,
representation: representation.to_string(),
allowed_representations: unit
.representations
.clone()
}
);
}
{
let unit = &d.radius.unit;
let representation = &p.radius.representation;
let axis = "radius".to_string();
ensure!(
unit.representations.contains(representation),
error::AxisUnitRepresentationNotAllowed {
sampling_id: profile
.sampling_id
.to_string(),
curve_id: pc.curve_id.to_string(),
axis,
representation: representation.to_string(),
allowed_representations: unit
.representations
.clone()
}
);
}
}
(p, d) => {
use crate::samplic::HasCoordinateSystem;
error::CoordinateSystemMismatch {
sampling_id: profile.sampling_id.to_string(),
curve_id: pc.curve_id.to_string(),
found: p.coordinate_system(),
expected: d.coordinate_system(),
}
.fail()?;
}
}
}
let profile_dir = self.user_profile_dir(profile_id);
let profile_file = self.user_profile_file(profile_id);
std::fs::create_dir_all(profile_dir).context(error::Io)?;
let writer =
std::fs::File::create(profile_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, profile)
.context(error::Serde)?;
Ok(())
}
fn store_measurement_metadata(
&mut self,
sample_id: Uuid,
measurement_index: usize,
metadata: &MeasurementMetadata,
) -> Result<()> {
let metadata_dir =
self.measurement_metadata_dir(sample_id, measurement_index);
let metadata_file =
self.measurement_metadata_file(sample_id, measurement_index);
std::fs::create_dir_all(metadata_dir).context(error::Io)?;
let writer =
std::fs::File::create(metadata_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, metadata)
.context(error::Serde)?;
Ok(())
}
fn store_results(
&mut self,
sample_id: Uuid,
measurement_index: usize,
results: &MeasurementResults,
) -> Result<()> {
let results_dir = self.results_dir(sample_id, measurement_index);
let results_file = self.results_file(sample_id, measurement_index);
std::fs::create_dir_all(results_dir).context(error::Io)?;
let writer =
std::fs::File::create(results_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, results)
.context(error::Serde)?;
Ok(())
}
fn store_curve(
&mut self,
sample_id: Uuid,
measurement_index: usize,
curve_id: &str,
curve: &Curve,
) -> Result<()> {
let curve_dir =
self.curve_dir(sample_id, measurement_index, curve_id);
let curve_file =
self.curve_file(sample_id, measurement_index, curve_id);
std::fs::create_dir_all(curve_dir).context(error::Io)?;
let writer =
std::fs::File::create(curve_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, curve)
.context(error::Serde)?;
Ok(())
}
fn store_measurement(
&mut self,
sample_id: Uuid,
measurement_index: usize,
measurement: &Measurement,
) -> Result<()> {
self.store_measurement_metadata(
sample_id,
measurement_index,
&measurement.metadata,
)?;
self.store_results(
sample_id,
measurement_index,
&measurement.results,
)?;
for (curve_id, curve) in measurement.curves.iter() {
self.store_curve(
sample_id,
measurement_index,
curve_id,
curve,
)?;
}
Ok(())
}
fn store_sample_metadata(
&mut self,
sample_id: Uuid,
metadata: &SampleMetadata,
) -> Result<()> {
let metadata_dir = self.sample_metadata_dir(sample_id);
let metadata_file = self.sample_metadata_file(sample_id);
std::fs::create_dir_all(metadata_dir).context(error::Io)?;
let writer =
std::fs::File::create(metadata_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, metadata)
.context(error::Serde)?;
Ok(())
}
fn store_sample(
&mut self,
sample_id: Uuid,
sample: &Sample,
) -> Result<()> {
self.store_sample_metadata(sample_id, &sample.metadata)?;
for (measurement_index, measurement) in sample.measurements.iter()
{
self.store_measurement(
sample_id,
*measurement_index,
measurement,
)?;
}
Ok(())
}
fn store_sample_index(
&mut self,
index: &IndexSet<Uuid>,
) -> Result<()> {
let index_dir = self.data_dir();
let index_file = self.sample_index_file();
std::fs::create_dir_all(index_dir).context(error::Io)?;
let writer =
std::fs::File::create(index_file).context(error::Io)?;
serde_json::to_writer_pretty(writer, index)
.context(error::Serde)?;
Ok(())
}
}