use crate::dataset::DatasetBuilder;
use crate::error::{Hdf5Error, Result};
use crate::file::{borrow_inner, borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
use crate::format::messages::attribute::AttributeMessage;
use crate::format::messages::filter::FilterPipeline;
use crate::types::H5Type;
pub struct H5Group {
file_inner: SharedInner,
name: String,
}
impl H5Group {
pub(crate) fn new(file_inner: SharedInner, name: String) -> Self {
Self { file_inner, name }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
DatasetBuilder::new_in_group(clone_inner(&self.file_inner), self.name.clone())
}
pub fn create_group(&self, name: &str) -> Result<H5Group> {
let full_name = if self.name == "/" {
format!("/{}", name)
} else {
format!("{}/{}", self.name, name)
};
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
writer.create_group(&self.name, name)?;
}
H5FileInner::Reader(_) => {
return Err(Hdf5Error::InvalidState(
"cannot create groups in read mode".into(),
));
}
H5FileInner::Closed => {
return Err(Hdf5Error::InvalidState("file is closed".into()));
}
}
drop(inner);
Ok(H5Group {
file_inner: clone_inner(&self.file_inner),
name: full_name,
})
}
pub fn group(&self, name: &str) -> Result<H5Group> {
let full_name = if self.name == "/" {
format!("/{}", name)
} else {
format!("{}/{}", self.name, name)
};
let inner = borrow_inner(&self.file_inner);
if let H5FileInner::Reader(reader) = &*inner {
let group_path = full_name.trim_start_matches('/');
if !reader.has_group(group_path) {
return Err(Hdf5Error::NotFound(full_name));
}
}
drop(inner);
Ok(H5Group {
file_inner: clone_inner(&self.file_inner),
name: full_name,
})
}
pub fn dataset_names(&self) -> Result<Vec<String>> {
let inner = borrow_inner(&self.file_inner);
let all_names = match &*inner {
H5FileInner::Reader(reader) => reader
.dataset_names()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
H5FileInner::Writer(writer) => writer
.dataset_names()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
H5FileInner::Closed => return Ok(vec![]),
};
let prefix = if self.name == "/" {
String::new()
} else {
format!("{}/", self.name.trim_start_matches('/'))
};
let mut result = Vec::new();
for name in &all_names {
let stripped = if prefix.is_empty() {
name.as_str()
} else if let Some(rest) = name.strip_prefix(&prefix) {
rest
} else {
continue;
};
if !stripped.contains('/') {
result.push(stripped.to_string());
}
}
Ok(result)
}
pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
let full_name = if self.name == "/" {
name.to_string()
} else {
let trimmed = self.name.trim_start_matches('/');
format!("{}/{}", trimmed, name)
};
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
if self.name != "/" {
writer.assign_dataset_to_group(&self.name, idx)?;
}
Ok(())
}
H5FileInner::Reader(_) => {
Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
}
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn write_vlen_strings_compressed(
&self,
name: &str,
strings: &[&str],
chunk_size: usize,
pipeline: FilterPipeline,
) -> Result<()> {
let full_name = if self.name == "/" {
name.to_string()
} else {
let trimmed = self.name.trim_start_matches('/');
format!("{}/{}", trimmed, name)
};
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
let idx = writer.create_vlen_string_dataset_compressed(
&full_name, strings, chunk_size, pipeline,
)?;
if self.name != "/" {
writer.assign_dataset_to_group(&self.name, idx)?;
}
Ok(())
}
H5FileInner::Reader(_) => {
Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
}
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn create_appendable_vlen_dataset(
&self,
name: &str,
chunk_size: usize,
pipeline: Option<FilterPipeline>,
) -> Result<()> {
let full_name = if self.name == "/" {
name.to_string()
} else {
let trimmed = self.name.trim_start_matches('/');
format!("{}/{}", trimmed, name)
};
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
let idx = writer
.create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
if self.name != "/" {
writer.assign_dataset_to_group(&self.name, idx)?;
}
Ok(())
}
H5FileInner::Reader(_) => {
Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
}
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
let full_name = if self.name == "/" {
name.to_string()
} else {
let trimmed = self.name.trim_start_matches('/');
format!("{}/{}", trimmed, name)
};
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
let ds_index = writer
.dataset_index(&full_name)
.ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
writer.append_vlen_strings(ds_index, strings)?;
Ok(())
}
H5FileInner::Reader(_) => {
Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
}
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn group_names(&self) -> Result<Vec<String>> {
let prefix = if self.name == "/" {
String::new()
} else {
format!("{}/", self.name.trim_start_matches('/'))
};
let mut groups = std::collections::BTreeSet::new();
let inner = borrow_inner(&self.file_inner);
match &*inner {
H5FileInner::Reader(reader) => {
for path in reader.group_paths() {
let stripped = if prefix.is_empty() {
path.as_str()
} else if let Some(rest) = path.strip_prefix(&prefix) {
rest
} else {
continue;
};
if stripped.is_empty() {
continue;
}
let child = match stripped.find('/') {
Some(pos) => &stripped[..pos],
None => stripped,
};
groups.insert(child.to_string());
}
}
H5FileInner::Writer(writer) => {
for name in writer.dataset_names() {
let stripped = if prefix.is_empty() {
name
} else if let Some(rest) = name.strip_prefix(&prefix) {
rest
} else {
continue;
};
if let Some(pos) = stripped.find('/') {
groups.insert(stripped[..pos].to_string());
}
}
}
H5FileInner::Closed => return Ok(vec![]),
}
Ok(groups.into_iter().collect())
}
pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
self.add_attr(AttributeMessage::scalar_string(name, value))
}
pub fn set_attr_numeric<T: H5Type>(&self, name: &str, value: &T) -> Result<()> {
let es = T::element_size();
let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
self.add_attr(AttributeMessage::scalar_numeric(
name,
T::hdf5_type(),
raw.to_vec(),
))
}
fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
if self.name == "/" {
writer.add_root_attribute(attr);
} else {
writer.add_group_attribute(&self.name, attr)?;
}
Ok(())
}
H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
"cannot write attributes in read mode".into(),
)),
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn attr_names(&self) -> Result<Vec<String>> {
let inner = borrow_inner(&self.file_inner);
match &*inner {
H5FileInner::Reader(reader) => {
if self.name == "/" {
Ok(reader.root_attr_names())
} else {
Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
}
}
_ => Err(Hdf5Error::InvalidState(
"attr_names is only available in read mode".into(),
)),
}
}
pub fn attr_string(&self, name: &str) -> Result<String> {
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Reader(reader) => {
let attr = if self.name == "/" {
reader.root_attr(name)
} else {
reader.group_attr(self.name.trim_start_matches('/'), name)
}
.ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
.clone();
Ok(reader.attr_string_value(&attr)?)
}
_ => Err(Hdf5Error::InvalidState(
"attr_string is only available in read mode".into(),
)),
}
}
}