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 if let H5FileInner::Reader(reader) = &*inner {
135 let group_path = full_name.trim_start_matches('/');
136 if !reader.has_group(group_path) {
137 return Err(Hdf5Error::NotFound(full_name));
138 }
139 }
140 drop(inner);
141
142 Ok(H5Group {
143 file_inner: clone_inner(&self.file_inner),
144 name: full_name,
145 })
146 }
147
148 pub fn dataset_names(&self) -> Result<Vec<String>> {
150 let inner = borrow_inner(&self.file_inner);
151 let all_names = match &*inner {
152 H5FileInner::Reader(reader) => reader
153 .dataset_names()
154 .iter()
155 .map(|s| s.to_string())
156 .collect::<Vec<_>>(),
157 H5FileInner::Writer(writer) => writer
158 .dataset_names()
159 .iter()
160 .map(|s| s.to_string())
161 .collect::<Vec<_>>(),
162 H5FileInner::Closed => return Ok(vec![]),
163 };
164
165 let prefix = if self.name == "/" {
166 String::new()
167 } else {
168 format!("{}/", self.name.trim_start_matches('/'))
169 };
170
171 let mut result = Vec::new();
172 for name in &all_names {
173 let stripped = if prefix.is_empty() {
174 name.as_str()
175 } else if let Some(rest) = name.strip_prefix(&prefix) {
176 rest
177 } else {
178 continue;
179 };
180 if !stripped.contains('/') {
182 result.push(stripped.to_string());
183 }
184 }
185 Ok(result)
186 }
187
188 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<H5Dataset> {
194 let full_name = if self.name == "/" {
195 name.to_string()
196 } else {
197 let trimmed = self.name.trim_start_matches('/');
198 format!("{}/{}", trimmed, name)
199 };
200
201 let inner = borrow_inner(&self.file_inner);
202 match &*inner {
203 H5FileInner::Writer(writer) => {
204 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
205 if self.name != "/" {
206 writer.assign_dataset_to_group(&self.name, idx)?;
207 }
208 let (shape, element_size, chunked, btree2, fixed_array) =
209 writer.dataset_handle_parts(idx);
210 Ok(H5Dataset::new_writer(
211 clone_inner(&self.file_inner),
212 idx,
213 shape,
214 element_size,
215 chunked,
216 btree2,
217 fixed_array,
218 ))
219 }
220 H5FileInner::Reader(_) => {
221 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
222 }
223 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
224 }
225 }
226
227 pub fn write_vlen_bytes(&self, name: &str, items: &[&[u8]]) -> Result<H5Dataset> {
235 let full_name = if self.name == "/" {
236 name.to_string()
237 } else {
238 let trimmed = self.name.trim_start_matches('/');
239 format!("{}/{}", trimmed, name)
240 };
241
242 let inner = borrow_inner(&self.file_inner);
243 match &*inner {
244 H5FileInner::Writer(writer) => {
245 let idx = writer.create_vlen_bytes_dataset(&full_name, items)?;
246 if self.name != "/" {
247 writer.assign_dataset_to_group(&self.name, idx)?;
248 }
249 let (shape, element_size, chunked, btree2, fixed_array) =
250 writer.dataset_handle_parts(idx);
251 Ok(H5Dataset::new_writer(
252 clone_inner(&self.file_inner),
253 idx,
254 shape,
255 element_size,
256 chunked,
257 btree2,
258 fixed_array,
259 ))
260 }
261 H5FileInner::Reader(_) => {
262 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
263 }
264 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
265 }
266 }
267
268 pub fn write_vlen_strings_compressed(
273 &self,
274 name: &str,
275 strings: &[&str],
276 chunk_size: usize,
277 pipeline: FilterPipeline,
278 ) -> Result<H5Dataset> {
279 let full_name = if self.name == "/" {
280 name.to_string()
281 } else {
282 let trimmed = self.name.trim_start_matches('/');
283 format!("{}/{}", trimmed, name)
284 };
285
286 let inner = borrow_inner(&self.file_inner);
287 match &*inner {
288 H5FileInner::Writer(writer) => {
289 let idx = writer.create_vlen_string_dataset_compressed(
290 &full_name, strings, chunk_size, pipeline,
291 )?;
292 if self.name != "/" {
293 writer.assign_dataset_to_group(&self.name, idx)?;
294 }
295 let (shape, element_size, chunked, btree2, fixed_array) =
296 writer.dataset_handle_parts(idx);
297 Ok(H5Dataset::new_writer(
298 clone_inner(&self.file_inner),
299 idx,
300 shape,
301 element_size,
302 chunked,
303 btree2,
304 fixed_array,
305 ))
306 }
307 H5FileInner::Reader(_) => {
308 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
309 }
310 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
311 }
312 }
313
314 pub fn create_appendable_vlen_dataset(
320 &self,
321 name: &str,
322 chunk_size: usize,
323 pipeline: Option<FilterPipeline>,
324 ) -> Result<H5Dataset> {
325 let full_name = if self.name == "/" {
326 name.to_string()
327 } else {
328 let trimmed = self.name.trim_start_matches('/');
329 format!("{}/{}", trimmed, name)
330 };
331
332 let inner = borrow_inner(&self.file_inner);
333 match &*inner {
334 H5FileInner::Writer(writer) => {
335 let idx = writer
336 .create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
337 if self.name != "/" {
338 writer.assign_dataset_to_group(&self.name, idx)?;
339 }
340 let (shape, element_size, chunked, btree2, fixed_array) =
341 writer.dataset_handle_parts(idx);
342 Ok(H5Dataset::new_writer(
343 clone_inner(&self.file_inner),
344 idx,
345 shape,
346 element_size,
347 chunked,
348 btree2,
349 fixed_array,
350 ))
351 }
352 H5FileInner::Reader(_) => {
353 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
354 }
355 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
356 }
357 }
358
359 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
361 let full_name = if self.name == "/" {
362 name.to_string()
363 } else {
364 let trimmed = self.name.trim_start_matches('/');
365 format!("{}/{}", trimmed, name)
366 };
367
368 let inner = borrow_inner(&self.file_inner);
369 match &*inner {
370 H5FileInner::Writer(writer) => {
371 let ds_index = writer
372 .dataset_index(&full_name)
373 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
374 writer.append_vlen_strings(ds_index, strings)?;
375 Ok(())
376 }
377 H5FileInner::Reader(_) => {
378 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
379 }
380 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
381 }
382 }
383
384 pub fn dataset_writer(&self, name: &str) -> Result<H5Dataset> {
391 let full_name = if self.name == "/" {
392 name.to_string()
393 } else {
394 let trimmed = self.name.trim_start_matches('/');
395 format!("{}/{}", trimmed, name)
396 };
397
398 let inner = borrow_inner(&self.file_inner);
399 match &*inner {
400 H5FileInner::Writer(writer) => {
401 let index = writer
402 .dataset_index(&full_name)
403 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
404 let (shape, element_size, chunked, btree2, fixed_array) =
405 writer.dataset_handle_parts(index);
406 Ok(H5Dataset::new_writer(
407 clone_inner(&self.file_inner),
408 index,
409 shape,
410 element_size,
411 chunked,
412 btree2,
413 fixed_array,
414 ))
415 }
416 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
417 "cannot open a dataset_writer in read mode; use dataset() instead".into(),
418 )),
419 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
420 }
421 }
422
423 pub fn group_names(&self) -> Result<Vec<String>> {
425 let prefix = if self.name == "/" {
426 String::new()
427 } else {
428 format!("{}/", self.name.trim_start_matches('/'))
429 };
430
431 let mut groups = std::collections::BTreeSet::new();
432 let inner = borrow_inner(&self.file_inner);
433 match &*inner {
434 H5FileInner::Reader(reader) => {
438 for path in reader.group_paths() {
439 let stripped = if prefix.is_empty() {
440 path.as_str()
441 } else if let Some(rest) = path.strip_prefix(&prefix) {
442 rest
443 } else {
444 continue;
445 };
446 if stripped.is_empty() {
447 continue;
448 }
449 let child = match stripped.find('/') {
451 Some(pos) => &stripped[..pos],
452 None => stripped,
453 };
454 groups.insert(child.to_string());
455 }
456 }
457 H5FileInner::Writer(writer) => {
459 for name in writer.dataset_names() {
460 let stripped = if prefix.is_empty() {
461 name.as_str()
462 } else if let Some(rest) = name.strip_prefix(&prefix) {
463 rest
464 } else {
465 continue;
466 };
467 if let Some(pos) = stripped.find('/') {
468 groups.insert(stripped[..pos].to_string());
469 }
470 }
471 }
472 H5FileInner::Closed => return Ok(vec![]),
473 }
474 Ok(groups.into_iter().collect())
475 }
476
477 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
484 let inner = borrow_inner(&self.file_inner);
485 match &*inner {
486 H5FileInner::Writer(writer) => {
487 let attr = writer.vlen_string_attribute(name, value)?;
488 if self.name == "/" {
489 writer.add_root_attribute(attr);
490 } else {
491 writer.add_group_attribute(&self.name, attr)?;
492 }
493 Ok(())
494 }
495 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
496 "cannot write attributes in read mode".into(),
497 )),
498 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
499 }
500 }
501
502 pub fn set_attr_numeric<T: H5Type>(&self, name: &str, value: &T) -> Result<()> {
504 let es = T::element_size();
505 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
508 self.add_attr(AttributeMessage::scalar_numeric(
509 name,
510 T::hdf5_type(),
511 raw.to_vec(),
512 ))
513 }
514
515 pub fn set_attr_array_numeric<T: H5Type>(&self, name: &str, values: &[T]) -> Result<()> {
523 self.set_attr_array_numeric_nd(name, values, &[values.len()])
524 }
525
526 pub fn set_attr_array_numeric_nd<T: H5Type>(
535 &self,
536 name: &str,
537 values: &[T],
538 shape: &[usize],
539 ) -> Result<()> {
540 let n: usize = shape.iter().product();
541 if values.len() != n {
542 return Err(Hdf5Error::InvalidState(format!(
543 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
544 values.len()
545 )));
546 }
547 let es = T::element_size();
548 let raw =
550 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
551 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
552 self.add_attr(AttributeMessage::array_numeric(
553 name,
554 T::hdf5_type(),
555 &dims,
556 raw.to_vec(),
557 ))
558 }
559
560 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
566 self.set_attr_string_array_nd(name, values, &[values.len()])
567 }
568
569 pub fn set_attr_string_array_nd(
579 &self,
580 name: &str,
581 values: &[&str],
582 shape: &[usize],
583 ) -> Result<()> {
584 let n: usize = shape.iter().product();
585 if values.len() != n {
586 return Err(Hdf5Error::InvalidState(format!(
587 "attribute '{name}' shape {shape:?} needs {n} elements, got {}",
588 values.len()
589 )));
590 }
591 let dims: Vec<u64> = shape.iter().map(|&d| d as u64).collect();
592 let mut inner = borrow_inner_mut(&self.file_inner);
596 match &mut *inner {
597 H5FileInner::Writer(writer) => {
598 let attr = writer.vlen_string_array_attribute(name, values, &dims)?;
599 if self.name == "/" {
600 writer.add_root_attribute(attr);
601 } else {
602 writer.add_group_attribute(&self.name, attr)?;
603 }
604 Ok(())
605 }
606 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
607 "cannot write attributes in read mode".into(),
608 )),
609 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
610 }
611 }
612
613 fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
616 let inner = borrow_inner(&self.file_inner);
617 match &*inner {
618 H5FileInner::Writer(writer) => {
619 if self.name == "/" {
620 writer.add_root_attribute(attr);
621 } else {
622 writer.add_group_attribute(&self.name, attr)?;
623 }
624 Ok(())
625 }
626 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
627 "cannot write attributes in read mode".into(),
628 )),
629 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
630 }
631 }
632
633 pub fn attr_names(&self) -> Result<Vec<String>> {
635 let inner = borrow_inner(&self.file_inner);
636 match &*inner {
637 H5FileInner::Reader(reader) => {
638 if self.name == "/" {
639 Ok(reader.root_attr_names())
640 } else {
641 Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
642 }
643 }
644 _ => Err(Hdf5Error::InvalidState(
645 "attr_names is only available in read mode".into(),
646 )),
647 }
648 }
649
650 pub fn attr_string(&self, name: &str) -> Result<String> {
652 let mut inner = borrow_inner_mut(&self.file_inner);
653 match &mut *inner {
654 H5FileInner::Reader(reader) => {
655 let attr = if self.name == "/" {
656 reader.root_attr(name)
657 } else {
658 reader.group_attr(self.name.trim_start_matches('/'), name)
659 }
660 .ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
661 .clone();
662 Ok(reader.attr_string_value(&attr)?)
663 }
664 _ => Err(Hdf5Error::InvalidState(
665 "attr_string is only available in read mode".into(),
666 )),
667 }
668 }
669}