use crate::loadable_device::LoadableDevice as _;
use crate::samplic::{
Curve, DeviceDescription, HasParameterType, Icon, LoadableDevice,
MeasurementMetadata, MeasurementResults, ParameterDescription,
ProfileDescription, ProfileParameterDescription, 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_file(&self, sample_id: Uuid) -> PathBuf {
self.sample_dir(sample_id)
.join("metadata")
.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_file(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_dir(sample_id, measurement_index)
.join("metadata")
.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_file(
&self,
sample_id: Uuid,
measurement_index: usize,
curve_id: &str,
) -> PathBuf {
self.curves_dir(sample_id, measurement_index)
.join(curve_id)
.join("curve.json")
}
pub fn results_file(
&self,
sample_id: Uuid,
measurement_index: usize,
) -> PathBuf {
self.measurement_dir(sample_id, measurement_index)
.join("results")
.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>> {
IndexSet::load_path_pattern_from_dir(
self.measurements_dir(sample_id),
"*",
)
}
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(())
}
}