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 group(&self, name: &str) -> Result<H5Group> {
90 let full_name = if self.name == "/" {
91 format!("/{}", name)
92 } else {
93 format!("{}/{}", self.name, name)
94 };
95
96 let inner = borrow_inner(&self.file_inner);
101 if let H5FileInner::Reader(reader) = &*inner {
102 let group_path = full_name.trim_start_matches('/');
103 if !reader.has_group(group_path) {
104 return Err(Hdf5Error::NotFound(full_name));
105 }
106 }
107 drop(inner);
108
109 Ok(H5Group {
110 file_inner: clone_inner(&self.file_inner),
111 name: full_name,
112 })
113 }
114
115 pub fn dataset_names(&self) -> Result<Vec<String>> {
117 let inner = borrow_inner(&self.file_inner);
118 let all_names = match &*inner {
119 H5FileInner::Reader(reader) => reader
120 .dataset_names()
121 .iter()
122 .map(|s| s.to_string())
123 .collect::<Vec<_>>(),
124 H5FileInner::Writer(writer) => writer
125 .dataset_names()
126 .iter()
127 .map(|s| s.to_string())
128 .collect::<Vec<_>>(),
129 H5FileInner::Closed => return Ok(vec![]),
130 };
131
132 let prefix = if self.name == "/" {
133 String::new()
134 } else {
135 format!("{}/", self.name.trim_start_matches('/'))
136 };
137
138 let mut result = Vec::new();
139 for name in &all_names {
140 let stripped = if prefix.is_empty() {
141 name.as_str()
142 } else if let Some(rest) = name.strip_prefix(&prefix) {
143 rest
144 } else {
145 continue;
146 };
147 if !stripped.contains('/') {
149 result.push(stripped.to_string());
150 }
151 }
152 Ok(result)
153 }
154
155 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
157 let full_name = if self.name == "/" {
158 name.to_string()
159 } else {
160 let trimmed = self.name.trim_start_matches('/');
161 format!("{}/{}", trimmed, name)
162 };
163
164 let mut inner = borrow_inner_mut(&self.file_inner);
165 match &mut *inner {
166 H5FileInner::Writer(writer) => {
167 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
168 if self.name != "/" {
169 writer.assign_dataset_to_group(&self.name, idx)?;
170 }
171 Ok(())
172 }
173 H5FileInner::Reader(_) => {
174 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
175 }
176 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
177 }
178 }
179
180 pub fn write_vlen_strings_compressed(
182 &self,
183 name: &str,
184 strings: &[&str],
185 chunk_size: usize,
186 pipeline: FilterPipeline,
187 ) -> Result<()> {
188 let full_name = if self.name == "/" {
189 name.to_string()
190 } else {
191 let trimmed = self.name.trim_start_matches('/');
192 format!("{}/{}", trimmed, name)
193 };
194
195 let mut inner = borrow_inner_mut(&self.file_inner);
196 match &mut *inner {
197 H5FileInner::Writer(writer) => {
198 let idx = writer.create_vlen_string_dataset_compressed(
199 &full_name, strings, chunk_size, pipeline,
200 )?;
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 create_appendable_vlen_dataset(
215 &self,
216 name: &str,
217 chunk_size: usize,
218 pipeline: Option<FilterPipeline>,
219 ) -> Result<()> {
220 let full_name = if self.name == "/" {
221 name.to_string()
222 } else {
223 let trimmed = self.name.trim_start_matches('/');
224 format!("{}/{}", trimmed, name)
225 };
226
227 let mut inner = borrow_inner_mut(&self.file_inner);
228 match &mut *inner {
229 H5FileInner::Writer(writer) => {
230 let idx = writer
231 .create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
232 if self.name != "/" {
233 writer.assign_dataset_to_group(&self.name, idx)?;
234 }
235 Ok(())
236 }
237 H5FileInner::Reader(_) => {
238 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
239 }
240 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
241 }
242 }
243
244 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
246 let full_name = if self.name == "/" {
247 name.to_string()
248 } else {
249 let trimmed = self.name.trim_start_matches('/');
250 format!("{}/{}", trimmed, name)
251 };
252
253 let mut inner = borrow_inner_mut(&self.file_inner);
254 match &mut *inner {
255 H5FileInner::Writer(writer) => {
256 let ds_index = writer
257 .dataset_index(&full_name)
258 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
259 writer.append_vlen_strings(ds_index, strings)?;
260 Ok(())
261 }
262 H5FileInner::Reader(_) => {
263 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
264 }
265 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
266 }
267 }
268
269 pub fn group_names(&self) -> Result<Vec<String>> {
271 let prefix = if self.name == "/" {
272 String::new()
273 } else {
274 format!("{}/", self.name.trim_start_matches('/'))
275 };
276
277 let mut groups = std::collections::BTreeSet::new();
278 let inner = borrow_inner(&self.file_inner);
279 match &*inner {
280 H5FileInner::Reader(reader) => {
284 for path in reader.group_paths() {
285 let stripped = if prefix.is_empty() {
286 path.as_str()
287 } else if let Some(rest) = path.strip_prefix(&prefix) {
288 rest
289 } else {
290 continue;
291 };
292 if stripped.is_empty() {
293 continue;
294 }
295 let child = match stripped.find('/') {
297 Some(pos) => &stripped[..pos],
298 None => stripped,
299 };
300 groups.insert(child.to_string());
301 }
302 }
303 H5FileInner::Writer(writer) => {
305 for name in writer.dataset_names() {
306 let stripped = if prefix.is_empty() {
307 name
308 } else if let Some(rest) = name.strip_prefix(&prefix) {
309 rest
310 } else {
311 continue;
312 };
313 if let Some(pos) = stripped.find('/') {
314 groups.insert(stripped[..pos].to_string());
315 }
316 }
317 }
318 H5FileInner::Closed => return Ok(vec![]),
319 }
320 Ok(groups.into_iter().collect())
321 }
322
323 pub fn set_attr_string(&self, name: &str, value: &str) -> Result<()> {
328 self.add_attr(AttributeMessage::scalar_string(name, value))
329 }
330
331 pub fn set_attr_numeric<T: H5Type>(&self, name: &str, value: &T) -> Result<()> {
333 let es = T::element_size();
334 let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
337 self.add_attr(AttributeMessage::scalar_numeric(
338 name,
339 T::hdf5_type(),
340 raw.to_vec(),
341 ))
342 }
343
344 fn add_attr(&self, attr: AttributeMessage) -> Result<()> {
347 let mut inner = borrow_inner_mut(&self.file_inner);
348 match &mut *inner {
349 H5FileInner::Writer(writer) => {
350 if self.name == "/" {
351 writer.add_root_attribute(attr);
352 } else {
353 writer.add_group_attribute(&self.name, attr)?;
354 }
355 Ok(())
356 }
357 H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
358 "cannot write attributes in read mode".into(),
359 )),
360 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
361 }
362 }
363
364 pub fn attr_names(&self) -> Result<Vec<String>> {
366 let inner = borrow_inner(&self.file_inner);
367 match &*inner {
368 H5FileInner::Reader(reader) => {
369 if self.name == "/" {
370 Ok(reader.root_attr_names())
371 } else {
372 Ok(reader.group_attr_names(self.name.trim_start_matches('/')))
373 }
374 }
375 _ => Err(Hdf5Error::InvalidState(
376 "attr_names is only available in read mode".into(),
377 )),
378 }
379 }
380
381 pub fn attr_string(&self, name: &str) -> Result<String> {
383 let mut inner = borrow_inner_mut(&self.file_inner);
384 match &mut *inner {
385 H5FileInner::Reader(reader) => {
386 let attr = if self.name == "/" {
387 reader.root_attr(name)
388 } else {
389 reader.group_attr(self.name.trim_start_matches('/'), name)
390 }
391 .ok_or_else(|| Hdf5Error::NotFound(name.to_string()))?
392 .clone();
393 Ok(reader.attr_string_value(&attr)?)
394 }
395 _ => Err(Hdf5Error::InvalidState(
396 "attr_string is only available in read mode".into(),
397 )),
398 }
399 }
400}