1use crate::dataset::{DatasetBuilder, H5Dataset};
21use crate::error::{Hdf5Error, Result};
22use crate::file::{borrow_inner, borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
23use crate::format::messages::attribute::AttributeMessage;
24use crate::format::messages::filter::FilterPipeline;
25use crate::types::H5Type;
26
27pub struct H5Group {
32 file_inner: SharedInner,
33 name: String,
35}
36
37impl H5Group {
38 pub(crate) fn new(file_inner: SharedInner, name: String) -> Self {
40 Self { file_inner, name }
41 }
42
43 pub fn name(&self) -> &str {
45 &self.name
46 }
47
48 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
53 DatasetBuilder::new_in_group(clone_inner(&self.file_inner), self.name.clone())
54 }
55
56 pub fn create_group(&self, name: &str) -> Result<H5Group> {
60 let full_name = if self.name == "/" {
61 format!("/{}", name)
62 } else {
63 format!("{}/{}", self.name, name)
64 };
65
66 let inner = borrow_inner(&self.file_inner);
67 match &*inner {
68 H5FileInner::Writer(writer) => {
69 writer.create_group(&self.name, name)?;
70 }
71 H5FileInner::Reader(_) => {
72 return Err(Hdf5Error::InvalidState(
73 "cannot create groups in read mode".into(),
74 ));
75 }
76 H5FileInner::Closed => {
77 return Err(Hdf5Error::InvalidState("file is closed".into()));
78 }
79 }
80 drop(inner);
81
82 Ok(H5Group {
83 file_inner: clone_inner(&self.file_inner),
84 name: full_name,
85 })
86 }
87
88 pub fn link(&self, link_name: &str, target_path: &str) -> Result<()> {
108 let inner = borrow_inner(&self.file_inner);
109 match &*inner {
110 H5FileInner::Writer(writer) => {
111 writer.create_hard_link(&self.name, link_name, target_path)?;
112 Ok(())
113 }
114 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
115 "cannot create hard links in read mode".into(),
116 )),
117 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
118 }
119 }
120
121 pub fn group(&self, name: &str) -> Result<H5Group> {
123 let full_name = if self.name == "/" {
124 format!("/{}", name)
125 } else {
126 format!("{}/{}", self.name, name)
127 };
128
129 let inner = borrow_inner(&self.file_inner);
134 let full_name = match &*inner {
135 H5FileInner::Reader(reader) => {
136 let group_path = full_name.trim_start_matches('/');
137 if !reader.has_group(group_path) {
138 return Err(Hdf5Error::NotFound(full_name));
139 }
140 full_name
141 }
142 H5FileInner::Writer(writer) => writer.canonical_group_path(&full_name),
146 H5FileInner::Closed => full_name,
147 };
148 drop(inner);
149
150 Ok(H5Group {
151 file_inner: clone_inner(&self.file_inner),
152 name: full_name,
153 })
154 }
155
156 pub fn dataset_names(&self) -> Result<Vec<String>> {
158 let inner = borrow_inner(&self.file_inner);
159 let all_names = match &*inner {
160 H5FileInner::Reader(reader) => reader
161 .dataset_names()
162 .iter()
163 .map(|s| s.to_string())
164 .collect::<Vec<_>>(),
165 H5FileInner::Writer(writer) => writer
166 .dataset_names()
167 .iter()
168 .map(|s| s.to_string())
169 .collect::<Vec<_>>(),
170 H5FileInner::Closed => return Ok(vec![]),
171 };
172
173 let prefix = if self.name == "/" {
174 String::new()
175 } else {
176 format!("{}/", self.name.trim_start_matches('/'))
177 };
178
179 let mut result = Vec::new();
180 for name in &all_names {
181 let stripped = if prefix.is_empty() {
182 name.as_str()
183 } else if let Some(rest) = name.strip_prefix(&prefix) {
184 rest
185 } else {
186 continue;
187 };
188 if !stripped.contains('/') {
190 result.push(stripped.to_string());
191 }
192 }
193 Ok(result)
194 }
195
196 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
202 let full_name = if self.name == "/" {
203 name.to_string()
204 } else {
205 let trimmed = self.name.trim_start_matches('/');
206 format!("{}/{}", trimmed, name)
207 };
208
209 let inner = borrow_inner(&self.file_inner);
210 match &*inner {
211 H5FileInner::Writer(writer) => {
212 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
213 if self.name != "/" {
214 writer.assign_dataset_to_group(&self.name, idx)?;
215 }
216 let (shape, element_size, chunked, btree2, fixed_array) =
217 writer.dataset_handle_parts(idx);
218 Ok(H5Dataset::new_writer(
219 clone_inner(&self.file_inner),
220 idx,
221 shape,
222 element_size,
223 chunked,
224 btree2,
225 fixed_array,
226 ))
227 }
228 H5FileInner::Reader(_) => {
229 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
230 }
231 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
232 }
233 }
234
235 pub fn write_vlen_bytes(&self, name: &str, items: &[&[u8]]) -> Result<H5Dataset> {
243 let full_name = if self.name == "/" {
244 name.to_string()
245 } else {
246 let trimmed = self.name.trim_start_matches('/');
247 format!("{}/{}", trimmed, name)
248 };
249
250 let inner = borrow_inner(&self.file_inner);
251 match &*inner {
252 H5FileInner::Writer(writer) => {
253 let idx = writer.create_vlen_bytes_dataset(&full_name, items)?;
254 if self.name != "/" {
255 writer.assign_dataset_to_group(&self.name, idx)?;
256 }
257 let (shape, element_size, chunked, btree2, fixed_array) =
258 writer.dataset_handle_parts(idx);
259 Ok(H5Dataset::new_writer(
260 clone_inner(&self.file_inner),
261 idx,
262 shape,
263 element_size,
264 chunked,
265 btree2,
266 fixed_array,
267 ))
268 }
269 H5FileInner::Reader(_) => {
270 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
271 }
272 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
273 }
274 }
275
276 pub fn write_vlen_strings_compressed(
281 &self,
282 name: &str,
283 strings: &[&str],
284 chunk_size: usize,
285 pipeline: FilterPipeline,
286 ) -> Result<H5Dataset> {
287 let full_name = if self.name == "/" {
288 name.to_string()
289 } else {
290 let trimmed = self.name.trim_start_matches('/');
291 format!("{}/{}", trimmed, name)
292 };
293
294 let inner = borrow_inner(&self.file_inner);
295 match &*inner {
296 H5FileInner::Writer(writer) => {
297 let idx = writer.create_vlen_string_dataset_compressed(
298 &full_name, strings, chunk_size, pipeline,
299 )?;
300 if self.name != "/" {
301 writer.assign_dataset_to_group(&self.name, idx)?;
302 }
303 let (shape, element_size, chunked, btree2, fixed_array) =
304 writer.dataset_handle_parts(idx);
305 Ok(H5Dataset::new_writer(
306 clone_inner(&self.file_inner),
307 idx,
308 shape,
309 element_size,
310 chunked,
311 btree2,
312 fixed_array,
313 ))
314 }
315 H5FileInner::Reader(_) => {
316 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
317 }
318 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
319 }
320 }
321
322 pub fn create_appendable_vlen_dataset(
328 &self,
329 name: &str,
330 chunk_size: usize,
331 pipeline: Option<FilterPipeline>,
332 ) -> Result<H5Dataset> {
333 let full_name = if self.name == "/" {
334 name.to_string()
335 } else {
336 let trimmed = self.name.trim_start_matches('/');
337 format!("{}/{}", trimmed, name)
338 };
339
340 let inner = borrow_inner(&self.file_inner);
341 match &*inner {
342 H5FileInner::Writer(writer) => {
343 let idx = writer
344 .create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
345 if self.name != "/" {
346 writer.assign_dataset_to_group(&self.name, idx)?;
347 }
348 let (shape, element_size, chunked, btree2, fixed_array) =
349 writer.dataset_handle_parts(idx);
350 Ok(H5Dataset::new_writer(
351 clone_inner(&self.file_inner),
352 idx,
353 shape,
354 element_size,
355 chunked,
356 btree2,
357 fixed_array,
358 ))
359 }
360 H5FileInner::Reader(_) => {
361 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
362 }
363 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
364 }
365 }
366
367 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
369 let full_name = if self.name == "/" {
370 name.to_string()
371 } else {
372 let trimmed = self.name.trim_start_matches('/');
373 format!("{}/{}", trimmed, name)
374 };
375
376 let inner = borrow_inner(&self.file_inner);
377 match &*inner {
378 H5FileInner::Writer(writer) => {
379 let ds_index = writer
380 .dataset_index(&full_name)
381 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
382 writer.append_vlen_strings(ds_index, strings)?;
383 Ok(())
384 }
385 H5FileInner::Reader(_) => {
386 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
387 }
388 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
389 }
390 }
391
392 pub fn dataset_writer(&self, name: &str) -> Result<H5Dataset> {
399 let full_name = if self.name == "/" {
400 name.to_string()
401 } else {
402 let trimmed = self.name.trim_start_matches('/');
403 format!("{}/{}", trimmed, name)
404 };
405
406 let inner = borrow_inner(&self.file_inner);
407 match &*inner {
408 H5FileInner::Writer(writer) => {
409 let index = writer
410 .dataset_index(&full_name)
411 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
412 let (shape, element_size, chunked, btree2, fixed_array) =
413 writer.dataset_handle_parts(index);
414 Ok(H5Dataset::new_writer(
415 clone_inner(&self.file_inner),
416 index,
417 shape,
418 element_size,
419 chunked,
420 btree2,
421 fixed_array,
422 ))
423 }
424 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
425 "cannot open a dataset_writer in read mode; use dataset() instead".into(),
426 )),
427 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
428 }
429 }
430
431 pub fn group_names(&self) -> Result<Vec<String>> {
433 let prefix = if self.name == "/" {
434 String::new()
435 } else {
436 format!("{}/", self.name.trim_start_matches('/'))
437 };
438
439 let mut groups = std::collections::BTreeSet::new();
440 let inner = borrow_inner(&self.file_inner);
441 match &*inner {
442 H5FileInner::Reader(reader) => {
446 for path in reader.group_paths() {
447 let stripped = if prefix.is_empty() {
448 path.as_str()
449 } else if let Some(rest) = path.strip_prefix(&prefix) {
450 rest
451 } else {
452 continue;
453 };
454 if stripped.is_empty() {
455 continue;
456 }
457 let child = match stripped.find('/') {
459 Some(pos) => &stripped[..pos],
460 None => stripped,
461 };
462 groups.insert(child.to_string());
463 }
464 }
465 H5FileInner::Writer(writer) => {
467 for name in writer.dataset_names() {
468 let stripped = if prefix.is_empty() {
469 name.as_str()
470 } else if let Some(rest) = name.strip_prefix(&prefix) {
471 rest
472 } else {
473 continue;
474 };
475 if let Some(pos) = stripped.find('/') {
476 groups.insert(stripped[..pos].to_string());
477 }
478 }
479 }
480 H5FileInner::Closed => return Ok(vec![]),
481 }
482 Ok(groups.into_iter().collect())
483 }
484
485 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
492 let inner = borrow_inner(&self.file_inner);
493 match &*inner {
494 H5FileInner::Writer(writer) => {
495 writer.set_vlen_string_attribute(self.attr_target(), name, value)?;
496 Ok(())
497 }
498 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
499 "cannot write attributes in read mode".into(),
500 )),
501 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
502 }
503 }
504
505 pub fn set_attr_numeric<T: H5Type>(&self, name: &str, value: &T) -> Result<()> {
507 let es = T::element_size();
508 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
511 self.add_attr(AttributeMessage::scalar_numeric(
512 name,
513 T::hdf5_type(),
514 raw.to_vec(),
515 ))
516 }
517
518 pub fn set_attr_array_numeric<T: H5Type>(&self, name: &str, values: &[T]) -> Result<()> {
526 self.set_attr_array_numeric_nd(name, values, &[values.len()])
527 }
528
529 pub fn set_attr_array_numeric_nd<T: H5Type>(
538 &self,
539 name: &str,
540 values: &[T],
541 shape: &[usize],
542 ) -> Result<()> {
543 let n: usize = shape.iter().product();
544 if values.len() != n {
545 return Err(Hdf5Error::InvalidState(format!(
546 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
547 values.len()
548 )));
549 }
550 let es = T::element_size();
551 let raw =
553 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
554 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
555 self.add_attr(AttributeMessage::array_numeric(
556 name,
557 T::hdf5_type(),
558 &dims,
559 raw.to_vec(),
560 ))
561 }
562
563 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
569 self.set_attr_string_array_nd(name, values, &[values.len()])
570 }
571
572 pub fn set_attr_string_array_nd(
582 &self,
583 name: &str,
584 values: &[&str],
585 shape: &[usize],
586 ) -> Result<()> {
587 let n: usize = shape.iter().product();
588 if values.len() != n {
589 return Err(Hdf5Error::InvalidState(format!(
590 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
591 values.len()
592 )));
593 }
594 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
595 let mut inner = borrow_inner_mut(&self.file_inner);
599 match &mut *inner {
600 H5FileInner::Writer(writer) => {
601 writer.set_vlen_string_array_attribute(self.attr_target(), name, values, &dims)?;
602 Ok(())
603 }
604 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
605 "cannot write attributes in read mode".into(),
606 )),
607 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
608 }
609 }
610
611 fn attr_target(&self) -> crate::io::writer::AttrTarget<'_> {
614 if self.name == "/" {
615 crate::io::writer::AttrTarget::Root
616 } else {
617 crate::io::writer::AttrTarget::Group(&self.name)
618 }
619 }
620
621 fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
624 let inner = borrow_inner(&self.file_inner);
625 match &*inner {
626 H5FileInner::Writer(writer) => {
627 writer.set_attribute(self.attr_target(), attr)?;
628 Ok(())
629 }
630 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
631 "cannot write attributes in read mode".into(),
632 )),
633 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
634 }
635 }
636
637 pub fn attr_names(&self) -> Result<Vec<String>> {
639 let inner = borrow_inner(&self.file_inner);
640 match &*inner {
641 H5FileInner::Reader(reader) => {
642 if self.name == "/" {
643 Ok(reader.root_attr_names())
644 } else {
645 Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
646 }
647 }
648 _ => Err(Hdf5Error::InvalidState(
649 "attr_names is only available in read mode".into(),
650 )),
651 }
652 }
653
654 pub fn attr_string(&self, name: &str) -> Result<String> {
656 let mut inner = borrow_inner_mut(&self.file_inner);
657 match &mut *inner {
658 H5FileInner::Reader(reader) => {
659 let attr = if self.name == "/" {
660 reader.root_attr(name)
661 } else {
662 reader.group_attr(self.name.trim_start_matches('/'), name)
663 }
664 .ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
665 .clone();
666 Ok(reader.attr_string_value(&attr)?)
667 }
668 _ => Err(Hdf5Error::InvalidState(
669 "attr_string is only available in read mode".into(),
670 )),
671 }
672 }
673}