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::filter::FilterPipeline;
24use crate::types::H5Type;
25
26pub struct H5Group {
31 file_inner: SharedInner,
32 name: String,
34}
35
36impl H5Group {
37 pub(crate) fn new(file_inner: SharedInner, name: String) -> Self {
39 Self { file_inner, name }
40 }
41
42 pub fn name(&self) -> &str {
44 &self.name
45 }
46
47 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
52 DatasetBuilder::new_in_group(clone_inner(&self.file_inner), self.name.clone())
53 }
54
55 pub fn create_group(&self, name: &str) -> Result<H5Group> {
59 let full_name = if self.name == "/" {
60 format!("/{}", name)
61 } else {
62 format!("{}/{}", self.name, name)
63 };
64
65 let mut inner = borrow_inner_mut(&self.file_inner);
66 match &mut *inner {
67 H5FileInner::Writer(writer) => {
68 writer.create_group(&self.name, name)?;
69 }
70 H5FileInner::Reader(_) => {
71 return Err(Hdf5Error::InvalidState(
72 "cannot create groups in read mode".into(),
73 ));
74 }
75 H5FileInner::Closed => {
76 return Err(Hdf5Error::InvalidState("file is closed".into()));
77 }
78 }
79 drop(inner);
80
81 Ok(H5Group {
82 file_inner: clone_inner(&self.file_inner),
83 name: full_name,
84 })
85 }
86
87 pub fn group(&self, name: &str) -> Result<H5Group> {
89 let full_name = if self.name == "/" {
90 format!("/{}", name)
91 } else {
92 format!("{}/{}", self.name, name)
93 };
94
95 let inner = borrow_inner(&self.file_inner);
97 if let H5FileInner::Reader(reader) = &*inner {
98 let prefix = if full_name == "/" {
99 String::new()
100 } else {
101 format!("{}/", full_name.trim_start_matches('/'))
102 };
103 let has_children = reader
104 .dataset_names()
105 .iter()
106 .any(|n| n.starts_with(&prefix));
107 if !has_children {
108 return Err(Hdf5Error::NotFound(full_name));
109 }
110 }
111 drop(inner);
112
113 Ok(H5Group {
114 file_inner: clone_inner(&self.file_inner),
115 name: full_name,
116 })
117 }
118
119 pub fn dataset_names(&self) -> Result<Vec<String>> {
121 let inner = borrow_inner(&self.file_inner);
122 let all_names = match &*inner {
123 H5FileInner::Reader(reader) => reader
124 .dataset_names()
125 .iter()
126 .map(|s| s.to_string())
127 .collect::<Vec<_>>(),
128 H5FileInner::Writer(writer) => writer
129 .dataset_names()
130 .iter()
131 .map(|s| s.to_string())
132 .collect::<Vec<_>>(),
133 H5FileInner::Closed => return Ok(vec![]),
134 };
135
136 let prefix = if self.name == "/" {
137 String::new()
138 } else {
139 format!("{}/", self.name.trim_start_matches('/'))
140 };
141
142 let mut result = Vec::new();
143 for name in &all_names {
144 let stripped = if prefix.is_empty() {
145 name.as_str()
146 } else if let Some(rest) = name.strip_prefix(&prefix) {
147 rest
148 } else {
149 continue;
150 };
151 if !stripped.contains('/') {
153 result.push(stripped.to_string());
154 }
155 }
156 Ok(result)
157 }
158
159 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
161 let full_name = if self.name == "/" {
162 name.to_string()
163 } else {
164 let trimmed = self.name.trim_start_matches('/');
165 format!("{}/{}", trimmed, name)
166 };
167
168 let mut inner = borrow_inner_mut(&self.file_inner);
169 match &mut *inner {
170 H5FileInner::Writer(writer) => {
171 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
172 if self.name != "/" {
173 writer.assign_dataset_to_group(&self.name, idx)?;
174 }
175 Ok(())
176 }
177 H5FileInner::Reader(_) => {
178 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
179 }
180 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
181 }
182 }
183
184 pub fn write_vlen_strings_compressed(
186 &self,
187 name: &str,
188 strings: &[&str],
189 chunk_size: usize,
190 pipeline: FilterPipeline,
191 ) -> Result<()> {
192 let full_name = if self.name == "/" {
193 name.to_string()
194 } else {
195 let trimmed = self.name.trim_start_matches('/');
196 format!("{}/{}", trimmed, name)
197 };
198
199 let mut inner = borrow_inner_mut(&self.file_inner);
200 match &mut *inner {
201 H5FileInner::Writer(writer) => {
202 let idx = writer.create_vlen_string_dataset_compressed(
203 &full_name, strings, chunk_size, pipeline,
204 )?;
205 if self.name != "/" {
206 writer.assign_dataset_to_group(&self.name, idx)?;
207 }
208 Ok(())
209 }
210 H5FileInner::Reader(_) => {
211 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
212 }
213 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
214 }
215 }
216
217 pub fn create_appendable_vlen_dataset(
219 &self,
220 name: &str,
221 chunk_size: usize,
222 pipeline: Option<FilterPipeline>,
223 ) -> Result<()> {
224 let full_name = if self.name == "/" {
225 name.to_string()
226 } else {
227 let trimmed = self.name.trim_start_matches('/');
228 format!("{}/{}", trimmed, name)
229 };
230
231 let mut inner = borrow_inner_mut(&self.file_inner);
232 match &mut *inner {
233 H5FileInner::Writer(writer) => {
234 let idx = writer
235 .create_appendable_vlen_string_dataset(&full_name, chunk_size, pipeline)?;
236 if self.name != "/" {
237 writer.assign_dataset_to_group(&self.name, idx)?;
238 }
239 Ok(())
240 }
241 H5FileInner::Reader(_) => {
242 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
243 }
244 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
245 }
246 }
247
248 pub fn append_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
250 let full_name = if self.name == "/" {
251 name.to_string()
252 } else {
253 let trimmed = self.name.trim_start_matches('/');
254 format!("{}/{}", trimmed, name)
255 };
256
257 let mut inner = borrow_inner_mut(&self.file_inner);
258 match &mut *inner {
259 H5FileInner::Writer(writer) => {
260 let ds_index = writer
261 .dataset_index(&full_name)
262 .ok_or_else(|| Hdf5Error::NotFound(full_name.clone()))?;
263 writer.append_vlen_strings(ds_index, strings)?;
264 Ok(())
265 }
266 H5FileInner::Reader(_) => {
267 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
268 }
269 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
270 }
271 }
272
273 pub fn group_names(&self) -> Result<Vec<String>> {
275 let inner = borrow_inner(&self.file_inner);
276 let all_names = match &*inner {
277 H5FileInner::Reader(reader) => reader
278 .dataset_names()
279 .iter()
280 .map(|s| s.to_string())
281 .collect::<Vec<_>>(),
282 H5FileInner::Writer(writer) => writer
283 .dataset_names()
284 .iter()
285 .map(|s| s.to_string())
286 .collect::<Vec<_>>(),
287 H5FileInner::Closed => return Ok(vec![]),
288 };
289
290 let prefix = if self.name == "/" {
291 String::new()
292 } else {
293 format!("{}/", self.name.trim_start_matches('/'))
294 };
295
296 let mut groups = std::collections::BTreeSet::new();
297 for name in &all_names {
298 let stripped = if prefix.is_empty() {
299 name.as_str()
300 } else if let Some(rest) = name.strip_prefix(&prefix) {
301 rest
302 } else {
303 continue;
304 };
305 if let Some(pos) = stripped.find('/') {
307 groups.insert(stripped[..pos].to_string());
308 }
309 }
310 Ok(groups.into_iter().collect())
311 }
312}