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 group_names(&self) -> Result<Vec<String>> {
219 let inner = borrow_inner(&self.file_inner);
220 let all_names = match &*inner {
221 H5FileInner::Reader(reader) => reader
222 .dataset_names()
223 .iter()
224 .map(|s| s.to_string())
225 .collect::<Vec<_>>(),
226 H5FileInner::Writer(writer) => writer
227 .dataset_names()
228 .iter()
229 .map(|s| s.to_string())
230 .collect::<Vec<_>>(),
231 H5FileInner::Closed => return Ok(vec![]),
232 };
233
234 let prefix = if self.name == "/" {
235 String::new()
236 } else {
237 format!("{}/", self.name.trim_start_matches('/'))
238 };
239
240 let mut groups = std::collections::BTreeSet::new();
241 for name in &all_names {
242 let stripped = if prefix.is_empty() {
243 name.as_str()
244 } else if let Some(rest) = name.strip_prefix(&prefix) {
245 rest
246 } else {
247 continue;
248 };
249 if let Some(pos) = stripped.find('/') {
251 groups.insert(stripped[..pos].to_string());
252 }
253 }
254 Ok(groups.into_iter().collect())
255 }
256}