use datasize::{data_size, DataSize};
use minicbor::{Decode, Encode};
use sealed::sealed;
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use std::path::PathBuf;
use crate::annotation::{Annotation, AnnotationHandle};
use crate::annotationdataset::{AnnotationDataSet, AnnotationDataSetHandle};
use crate::annotationstore::AnnotationStore;
use crate::config::Configurable;
use crate::error::StamError;
use crate::file::*;
use crate::json::{FromJson, ToJson};
use crate::resources::TextResource;
use crate::resources::TextResourceHandle;
use crate::store::*;
use crate::types::*;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, DataSize, Encode, Decode)]
#[cbor(transparent)]
pub struct AnnotationSubStoreHandle(#[n(0)] u16);
#[sealed]
impl Handle for AnnotationSubStoreHandle {
fn new(intid: usize) -> Self {
Self(intid as u16)
}
fn as_usize(&self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Encode, Decode, Default, PartialEq, Clone, DataSize)]
pub struct AnnotationSubStore {
#[n(0)]
intid: Option<AnnotationSubStoreHandle>,
#[n(1)]
pub(crate) id: Option<String>,
#[n(2)]
pub(crate) filename: Option<PathBuf>,
#[n(3)]
pub(crate) parents: Vec<Option<AnnotationSubStoreHandle>>,
#[n(4)]
pub(crate) annotations: Vec<AnnotationHandle>,
#[n(5)]
pub(crate) annotationsets: Vec<AnnotationDataSetHandle>,
#[n(6)]
pub(crate) resources: Vec<TextResourceHandle>,
}
impl AnnotationSubStore {
pub fn id(&self) -> Option<&str> {
self.id.as_deref()
}
pub fn filename(&self) -> Option<&PathBuf> {
self.filename.as_ref()
}
pub fn meminfo(&self) -> usize {
return data_size(self);
}
pub fn annotations_len(&self) -> usize {
self.annotations.len()
}
pub fn resources_len(&self) -> usize {
self.resources.len()
}
pub fn datasets_len(&self) -> usize {
self.annotationsets.len()
}
pub fn parents(&self) -> &Vec<Option<AnnotationSubStoreHandle>> {
&self.parents
}
pub fn with_parent(mut self, index: Option<AnnotationSubStoreHandle>) -> Self {
self.add_parent(index);
self
}
pub fn add_parent(&mut self, index: Option<AnnotationSubStoreHandle>) {
self.parents.push(index);
}
pub fn with_parents(mut self, parents: Vec<Option<AnnotationSubStoreHandle>>) -> Self {
self.parents = parents;
self
}
pub fn with_filename(mut self, filename: &str) -> Self {
self.filename = Some(filename.into());
self
}
}
#[sealed]
impl TypeInfo for AnnotationSubStore {
fn typeinfo() -> Type {
Type::AnnotationSubStore
}
}
impl AnnotationSubStore {}
impl<'a> Request<AnnotationSubStore> for AnnotationSubStoreHandle {
fn to_handle<'store, S>(&self, _store: &'store S) -> Option<AnnotationSubStoreHandle>
where
S: StoreFor<AnnotationSubStore>,
{
Some(*self)
}
}
#[sealed]
impl Storable for AnnotationSubStore {
type HandleType = AnnotationSubStoreHandle;
type StoreHandleType = ();
type FullHandleType = Self::HandleType;
type StoreType = AnnotationStore;
fn id(&self) -> Option<&str> {
self.id.as_deref()
}
fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
fn handle(&self) -> Option<Self::HandleType> {
self.intid
}
fn with_handle(mut self, handle: AnnotationSubStoreHandle) -> Self {
self.intid = Some(handle);
self
}
fn carries_id() -> bool {
true
}
fn fullhandle(
_storehandle: Self::StoreHandleType,
handle: Self::HandleType,
) -> Self::FullHandleType {
handle
}
fn merge(&mut self, _other: Self) -> Result<(), StamError> {
Ok(())
}
fn unbind(mut self) -> Self {
self.intid = None;
self
}
}
impl AnnotationStore {
pub fn add_substore(&mut self, filename: &str) -> Result<AnnotationSubStoreHandle, StamError> {
if !self.substores.is_empty() {
let foundpath = Some(get_filepath(filename, self.config.workdir())?);
let mut foundsubstore = None;
for substore in <Self as StoreFor<AnnotationSubStore>>::iter(self) {
if substore.filename == foundpath || substore.filename == Some(filename.into()) {
foundsubstore = Some(substore.handle().expect("substore must have handle"));
break;
}
}
if let Some(foundsubstore) = foundsubstore {
let parent_handle = self.config.current_substore_path.last().copied();
let substore: &mut AnnotationSubStore = self.get_mut(foundsubstore)?;
substore.add_parent(parent_handle);
return Ok(foundsubstore);
}
}
let parent_handle = self.config.current_substore_path.last().copied();
let handle = self.insert(AnnotationSubStore::default().with_parent(parent_handle))?; debug(self.config(), || {
format!(
"AnnotationStore.add_substore: adding substore filename={:?}, parent={:?}",
filename, parent_handle
)
});
self.push_current_substore(handle);
self.merge_json_file(filename)?;
self.pop_current_substore();
Ok(handle)
}
pub fn add_new_substore(
&mut self,
id: impl Into<String>,
filename: &str,
) -> Result<AnnotationSubStoreHandle, StamError> {
if !self.substores.is_empty() {
let foundpath = Some(get_filepath(filename, self.config.workdir())?);
let mut foundsubstore = None;
for substore in <Self as StoreFor<AnnotationSubStore>>::iter(self) {
if substore.filename == foundpath || substore.filename == Some(filename.into()) {
foundsubstore = Some(substore.handle().expect("substore must have handle"));
break;
}
}
if let Some(foundsubstore) = foundsubstore {
let parent_handle = self.config.current_substore_path.last().copied();
let substore: &mut AnnotationSubStore = self.get_mut(foundsubstore)?;
substore.add_parent(parent_handle);
return Ok(foundsubstore);
}
}
let parent_handle = self.config.current_substore_path.last().copied();
let handle = self.insert(
AnnotationSubStore::default()
.with_id(id)
.with_filename(filename)
.with_parent(parent_handle),
)?; debug(self.config(), || {
format!(
"AnnotationStore.add_substore: adding substore filename={:?}, parent={:?}",
filename, parent_handle
)
});
Ok(handle)
}
fn push_current_substore(&mut self, index: AnnotationSubStoreHandle) {
self.config.current_substore_path.push(index);
}
fn pop_current_substore(&mut self) -> bool {
self.config.current_substore_path.pop().is_some()
}
}
pub trait AssociateSubStore<T>
where
T: Storable,
{
fn associate_substore(
&mut self,
item: impl Request<T>,
substore: impl Request<AnnotationSubStore>,
) -> Result<(), StamError>;
}
impl AssociateSubStore<Annotation> for AnnotationStore {
fn associate_substore(
&mut self,
item: impl Request<Annotation>,
substore: impl Request<AnnotationSubStore>,
) -> Result<(), StamError> {
if let Some(handle) = item.to_handle(self) {
if let Some(substore_handle) = self.annotation_substore_map.get(handle) {
let substore = self.get_mut(substore_handle)?;
if let Some(pos) = substore.annotations.iter().position(|x| *x == handle) {
substore.annotations.remove(pos);
}
self.annotation_substore_map.remove_all(handle);
}
let substore = self.get_mut(substore)?;
let substore_handle = substore.handle().expect("substore must have handle");
substore.annotations.push(handle);
self.annotation_substore_map.insert(handle, substore_handle);
Ok(())
} else {
Err(StamError::NotFoundError(
Type::Annotation,
item.requested_id_owned()
.unwrap_or("(ID not known at this point)".into()),
))
}
}
}
impl AssociateSubStore<TextResource> for AnnotationStore {
fn associate_substore(
&mut self,
item: impl Request<TextResource>,
substore: impl Request<AnnotationSubStore>,
) -> Result<(), StamError> {
if let Some(handle) = item.to_handle(self) {
let resource = self.get(handle)?;
if resource.filename().is_some() {
if let Some(substore_handles) = self.resource_substore_map.get(handle) {
let substore_handles: Vec<_> = substore_handles.clone();
for substore_handle in substore_handles {
let substore = self.get_mut(substore_handle)?;
if let Some(pos) = substore.resources.iter().position(|x| *x == handle) {
substore.resources.remove(pos);
}
}
self.resource_substore_map.remove_all(handle);
}
}
let substore = self.get_mut(substore)?;
let substore_handle = substore.handle().expect("substore must have handle");
if !substore.resources.contains(&handle) {
substore.resources.push(handle);
}
self.resource_substore_map.insert(handle, substore_handle);
Ok(())
} else {
Err(StamError::NotFoundError(
Type::Annotation,
item.requested_id_owned()
.unwrap_or("(ID not known at this point)".into()),
))
}
}
}
impl AssociateSubStore<AnnotationDataSet> for AnnotationStore {
fn associate_substore(
&mut self,
item: impl Request<AnnotationDataSet>,
substore: impl Request<AnnotationSubStore>,
) -> Result<(), StamError> {
if let Some(handle) = item.to_handle(self) {
let dataset = self.get(handle)?;
if dataset.filename().is_some() {
if let Some(substore_handles) = self.dataset_substore_map.get(handle) {
let substore_handles: Vec<_> = substore_handles.clone();
for substore_handle in substore_handles {
let substore = self.get_mut(substore_handle)?;
if let Some(pos) = substore.annotationsets.iter().position(|x| *x == handle)
{
substore.annotationsets.remove(pos);
}
}
self.dataset_substore_map.remove_all(handle);
}
}
let substore = self.get_mut(substore)?;
let substore_handle = substore.handle().expect("substore must have handle");
if !substore.annotationsets.contains(&handle) {
substore.annotationsets.push(handle);
}
self.dataset_substore_map.insert(handle, substore_handle);
Ok(())
} else {
Err(StamError::NotFoundError(
Type::Annotation,
item.requested_id_owned()
.unwrap_or("(ID not known at this point)".into()),
))
}
}
}
impl<'store> Serialize for ResultItem<'store, AnnotationSubStore> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("AnnotationStore", 3)?;
state.serialize_field("@type", "AnnotationStore")?;
if let Some(id) = self.id() {
state.serialize_field("@id", id)?;
}
let substores: Vec<_> = self.substores().collect();
let substore_handle = self.handle();
if !substores.is_empty() {
if substores.len() == 1 {
if let Some(substore) =
<AnnotationStore as StoreFor<AnnotationSubStore>>::iter(self.store())
.filter(|substore| substore.parents.contains(&Some(substore_handle)))
.next()
{
state.serialize_field(
"@include",
substore.filename().ok_or(serde::ser::Error::custom(
"substore must have filename or can not be serialised",
))?,
)?;
}
} else {
let substores_filenames: Vec<_> =
<AnnotationStore as StoreFor<AnnotationSubStore>>::iter(self.store())
.filter(|substore| substore.parents.contains(&Some(substore_handle)))
.filter_map(|substore| substore.filename())
.collect();
state.serialize_field("@include", &substores_filenames)?;
}
}
let wrappedstore: WrappedStore<TextResource, AnnotationStore> =
self.store().wrap_store(Some(substore_handle));
state.serialize_field("resources", &wrappedstore)?;
let wrappedstore: WrappedStore<AnnotationDataSet, AnnotationStore> =
self.store().wrap_store(Some(substore_handle));
state.serialize_field("annotationsets", &wrappedstore)?;
let wrappedstore: WrappedStore<Annotation, AnnotationStore> =
self.store().wrap_store(Some(substore_handle));
state.serialize_field("annotations", &wrappedstore)?;
state.end()
}
}
impl<'store> ResultItem<'store, AnnotationSubStore> {
pub fn save(&self) -> Result<(), StamError> {
let new_config = self.store().new_config();
debug(self.store().config(), || {
format!(
"AnnotationSubStore.save: filename={:?}, workdir={:?}",
self.as_ref().filename(),
new_config.workdir()
)
});
if let Some(filename) = self.as_ref().filename.as_ref() {
match self.store().config().dataformat {
DataFormat::Json { .. } => {
self.to_json_file(
filename.to_str().expect("filename must be valid UTF-8"),
&new_config,
) }
_ => Err(StamError::SerializationError(
"Only JSON serialisation is supported for substores".to_owned(),
)),
}
} else {
Err(StamError::SerializationError(
"No filename associated with the store".to_owned(),
))
}
}
}
impl<'store> ToJson for ResultItem<'store, AnnotationSubStore> {}