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<()> {
521 let es = T::element_size();
522 let raw =
524 unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
525 let dims = [values.len() as u64];
526 self.add_attr(AttributeMessage::array_numeric(
527 name,
528 T::hdf5_type(),
529 &dims,
530 raw.to_vec(),
531 ))
532 }
533
534 pub fn set_attr_string_array(&self, name: &str, values: &[&str]) -> Result<()> {
538 let mut inner = borrow_inner_mut(&self.file_inner);
542 match &mut *inner {
543 H5FileInner::Writer(writer) => {
544 let attr = writer.vlen_string_array_attribute(name, values)?;
545 if self.name == "/" {
546 writer.add_root_attribute(attr);
547 } else {
548 writer.add_group_attribute(&self.name, attr)?;
549 }
550 Ok(())
551 }
552 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
553 "cannot write attributes in read mode".into(),
554 )),
555 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
556 }
557 }
558
559 fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
562 let inner = borrow_inner(&self.file_inner);
563 match &*inner {
564 H5FileInner::Writer(writer) => {
565 if self.name == "/" {
566 writer.add_root_attribute(attr);
567 } else {
568 writer.add_group_attribute(&self.name, attr)?;
569 }
570 Ok(())
571 }
572 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
573 "cannot write attributes in read mode".into(),
574 )),
575 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
576 }
577 }
578
579 pub fn attr_names(&self) -> Result<Vec<String>> {
581 let inner = borrow_inner(&self.file_inner);
582 match &*inner {
583 H5FileInner::Reader(reader) => {
584 if self.name == "/" {
585 Ok(reader.root_attr_names())
586 } else {
587 Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
588 }
589 }
590 _ => Err(Hdf5Error::InvalidState(
591 "attr_names is only available in read mode".into(),
592 )),
593 }
594 }
595
596 pub fn attr_string(&self, name: &str) -> Result<String> {
598 let mut inner = borrow_inner_mut(&self.file_inner);
599 match &mut *inner {
600 H5FileInner::Reader(reader) => {
601 let attr = if self.name == "/" {
602 reader.root_attr(name)
603 } else {
604 reader.group_attr(self.name.trim_start_matches('/'), name)
605 }
606 .ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
607 .clone();
608 Ok(reader.attr_string_value(&attr)?)
609 }
610 _ => Err(Hdf5Error::InvalidState(
611 "attr_string is only available in read mode".into(),
612 )),
613 }
614 }
615}