1use crate::dataset::DatasetBuilder;
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 mut inner = borrow_inner_mut(&self.file_inner);
67 match &mut *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 mut inner = borrow_inner_mut(&self.file_inner);
109 match &mut *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<()> {
190 let full_name = if self.name == "/" {
191 name.to_string()
192 } else {
193 let trimmed = self.name.trim_start_matches('/');
194 format!("{}/{}", trimmed, name)
195 };
196
197 let mut inner = borrow_inner_mut(&self.file_inner);
198 match &mut *inner {
199 H5FileInner::Writer(writer) => {
200 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
201 if self.name != "/" {
202 writer.assign_dataset_to_group(&self.name, idx)?;
203 }
204 Ok(())
205 }
206 H5FileInner::Reader(_) => {
207 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
208 }
209 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
210 }
211 }
212
213 pub fn write_vlen_strings_compressed(
215 &self,
216 name: &str,
217 strings: &[&str],
218 chunk_size: usize,
219 pipeline: FilterPipeline,
220 ) -> Result<()> {
221 let full_name = if self.name == "/" {
222 name.to_string()
223 } else {
224 let trimmed = self.name.trim_start_matches('/');
225 format!("{}/{}", trimmed, name)
226 };
227
228 let mut inner = borrow_inner_mut(&self.file_inner);
229 match &mut *inner {
230 H5FileInner::Writer(writer) => {
231 let idx = writer.create_vlen_string_dataset_compressed(
232 &full_name, strings, chunk_size, pipeline,
233 )?;
234 if self.name != "/" {
235 writer.assign_dataset_to_group(&self.name, idx)?;
236 }
237 Ok(())
238 }
239 H5FileInner::Reader(_) => {
240 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
241 }
242 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
243 }
244 }
245
246 pub fn create_appendable_vlen_dataset(
248 &self,
249 name: &str,
250 chunk_size: usize,
251 pipeline: Option<FilterPipeline>,
252 ) -> Result<()> {
253 let full_name = if self.name == "/" {
254 name.to_string()
255 } else {
256 let trimmed = self.name.trim_start_matches('/');
257 format!("{}/{}", trimmed, name)
258 };
259
260 let mut inner = borrow_inner_mut(&self.file_inner);
261 match &mut *inner {
262 H5FileInner::Writer(writer) => {
263 let idx = writer
264 .create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
265 if self.name != "/" {
266 writer.assign_dataset_to_group(&self.name, idx)?;
267 }
268 Ok(())
269 }
270 H5FileInner::Reader(_) => {
271 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
272 }
273 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
274 }
275 }
276
277 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
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 mut inner = borrow_inner_mut(&self.file_inner);
287 match &mut *inner {
288 H5FileInner::Writer(writer) => {
289 let ds_index = writer
290 .dataset_index(&full_name)
291 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
292 writer.append_vlen_strings(ds_index, strings)?;
293 Ok(())
294 }
295 H5FileInner::Reader(_) => {
296 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
297 }
298 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
299 }
300 }
301
302 pub fn group_names(&self) -> Result<Vec<String>> {
304 let prefix = if self.name == "/" {
305 String::new()
306 } else {
307 format!("{}/", self.name.trim_start_matches('/'))
308 };
309
310 let mut groups = std::collections::BTreeSet::new();
311 let inner = borrow_inner(&self.file_inner);
312 match &*inner {
313 H5FileInner::Reader(reader) => {
317 for path in reader.group_paths() {
318 let stripped = if prefix.is_empty() {
319 path.as_str()
320 } else if let Some(rest) = path.strip_prefix(&prefix) {
321 rest
322 } else {
323 continue;
324 };
325 if stripped.is_empty() {
326 continue;
327 }
328 let child = match stripped.find('/') {
330 Some(pos) => &stripped[..pos],
331 None => stripped,
332 };
333 groups.insert(child.to_string());
334 }
335 }
336 H5FileInner::Writer(writer) => {
338 for name in writer.dataset_names() {
339 let stripped = if prefix.is_empty() {
340 name
341 } else if let Some(rest) = name.strip_prefix(&prefix) {
342 rest
343 } else {
344 continue;
345 };
346 if let Some(pos) = stripped.find('/') {
347 groups.insert(stripped[..pos].to_string());
348 }
349 }
350 }
351 H5FileInner::Closed => return Ok(vec![]),
352 }
353 Ok(groups.into_iter().collect())
354 }
355
356 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
361 self.add_attr(AttributeMessage::scalar_string(name, value))
362 }
363
364 pub fn set_attr_numeric<T: H5Type>(&self, name: &str, value: &T) -> Result<()> {
366 let es = T::element_size();
367 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
370 self.add_attr(AttributeMessage::scalar_numeric(
371 name,
372 T::hdf5_type(),
373 raw.to_vec(),
374 ))
375 }
376
377 fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
380 let mut inner = borrow_inner_mut(&self.file_inner);
381 match &mut *inner {
382 H5FileInner::Writer(writer) => {
383 if self.name == "/" {
384 writer.add_root_attribute(attr);
385 } else {
386 writer.add_group_attribute(&self.name, attr)?;
387 }
388 Ok(())
389 }
390 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
391 "cannot write attributes in read mode".into(),
392 )),
393 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
394 }
395 }
396
397 pub fn attr_names(&self) -> Result<Vec<String>> {
399 let inner = borrow_inner(&self.file_inner);
400 match &*inner {
401 H5FileInner::Reader(reader) => {
402 if self.name == "/" {
403 Ok(reader.root_attr_names())
404 } else {
405 Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
406 }
407 }
408 _ => Err(Hdf5Error::InvalidState(
409 "attr_names is only available in read mode".into(),
410 )),
411 }
412 }
413
414 pub fn attr_string(&self, name: &str) -> Result<String> {
416 let mut inner = borrow_inner_mut(&self.file_inner);
417 match &mut *inner {
418 H5FileInner::Reader(reader) => {
419 let attr = if self.name == "/" {
420 reader.root_attr(name)
421 } else {
422 reader.group_attr(self.name.trim_start_matches('/'), name)
423 }
424 .ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
425 .clone();
426 Ok(reader.attr_string_value(&attr)?)
427 }
428 _ => Err(Hdf5Error::InvalidState(
429 "attr_string is only available in read mode".into(),
430 )),
431 }
432 }
433}