1use crate::dataset::DatasetBuilder;
21use crate::error::{Hdf5Error, Result};
22use crate::file::{borrow_inner, borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
23use crate::types::H5Type;
24
25pub struct H5Group {
30 file_inner: SharedInner,
31 name: String,
33}
34
35impl H5Group {
36 pub(crate) fn new(file_inner: SharedInner, name: String) -> Self {
38 Self { file_inner, name }
39 }
40
41 pub fn name(&self) -> &str {
43 &self.name
44 }
45
46 pub fn new_dataset<T: H5Type>(&self) -> DatasetBuilder<T> {
51 DatasetBuilder::new_in_group(clone_inner(&self.file_inner), self.name.clone())
52 }
53
54 pub fn create_group(&self, name: &str) -> Result<H5Group> {
58 let full_name = if self.name == "/" {
59 format!("/{}", name)
60 } else {
61 format!("{}/{}", self.name, name)
62 };
63
64 let mut inner = borrow_inner_mut(&self.file_inner);
65 match &mut *inner {
66 H5FileInner::Writer(writer) => {
67 writer.create_group(&self.name, name)?;
68 }
69 H5FileInner::Reader(_) => {
70 return Err(Hdf5Error::InvalidState(
71 "cannot create groups in read mode".into(),
72 ));
73 }
74 H5FileInner::Closed => {
75 return Err(Hdf5Error::InvalidState("file is closed".into()));
76 }
77 }
78 drop(inner);
79
80 Ok(H5Group {
81 file_inner: clone_inner(&self.file_inner),
82 name: full_name,
83 })
84 }
85
86 pub fn group(&self, name: &str) -> Result<H5Group> {
88 let full_name = if self.name == "/" {
89 format!("/{}", name)
90 } else {
91 format!("{}/{}", self.name, name)
92 };
93
94 let inner = borrow_inner(&self.file_inner);
96 if let H5FileInner::Reader(reader) = &*inner {
97 let prefix = if full_name == "/" {
98 String::new()
99 } else {
100 format!("{}/", full_name.trim_start_matches('/'))
101 };
102 let has_children = reader
103 .dataset_names()
104 .iter()
105 .any(|n| n.starts_with(&prefix));
106 if !has_children {
107 return Err(Hdf5Error::NotFound(full_name));
108 }
109 }
110 drop(inner);
111
112 Ok(H5Group {
113 file_inner: clone_inner(&self.file_inner),
114 name: full_name,
115 })
116 }
117
118 pub fn dataset_names(&self) -> Result<Vec<String>> {
120 let inner = borrow_inner(&self.file_inner);
121 let all_names = match &*inner {
122 H5FileInner::Reader(reader) => reader
123 .dataset_names()
124 .iter()
125 .map(|s| s.to_string())
126 .collect::<Vec<_>>(),
127 H5FileInner::Writer(writer) => writer
128 .dataset_names()
129 .iter()
130 .map(|s| s.to_string())
131 .collect::<Vec<_>>(),
132 H5FileInner::Closed => return Ok(vec![]),
133 };
134
135 let prefix = if self.name == "/" {
136 String::new()
137 } else {
138 format!("{}/", self.name.trim_start_matches('/'))
139 };
140
141 let mut result = Vec::new();
142 for name in &all_names {
143 let stripped = if prefix.is_empty() {
144 name.as_str()
145 } else if let Some(rest) = name.strip_prefix(&prefix) {
146 rest
147 } else {
148 continue;
149 };
150 if !stripped.contains('/') {
152 result.push(stripped.to_string());
153 }
154 }
155 Ok(result)
156 }
157
158 pub fn write_vlen_strings(&self, name: &str, strings: &[&str]) -> Result<()> {
160 let full_name = if self.name == "/" {
161 name.to_string()
162 } else {
163 let trimmed = self.name.trim_start_matches('/');
164 format!("{}/{}", trimmed, name)
165 };
166
167 let mut inner = borrow_inner_mut(&self.file_inner);
168 match &mut *inner {
169 H5FileInner::Writer(writer) => {
170 let idx = writer.create_vlen_string_dataset(&full_name, strings)?;
171 if self.name != "/" {
172 writer.assign_dataset_to_group(&self.name, idx)?;
173 }
174 Ok(())
175 }
176 H5FileInner::Reader(_) => {
177 Err(Hdf5Error::InvalidState("cannot write in read mode".into()))
178 }
179 H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
180 }
181 }
182
183 pub fn group_names(&self) -> Result<Vec<String>> {
185 let inner = borrow_inner(&self.file_inner);
186 let all_names = match &*inner {
187 H5FileInner::Reader(reader) => reader
188 .dataset_names()
189 .iter()
190 .map(|s| s.to_string())
191 .collect::<Vec<_>>(),
192 H5FileInner::Writer(writer) => writer
193 .dataset_names()
194 .iter()
195 .map(|s| s.to_string())
196 .collect::<Vec<_>>(),
197 H5FileInner::Closed => return Ok(vec![]),
198 };
199
200 let prefix = if self.name == "/" {
201 String::new()
202 } else {
203 format!("{}/", self.name.trim_start_matches('/'))
204 };
205
206 let mut groups = std::collections::BTreeSet::new();
207 for name in &all_names {
208 let stripped = if prefix.is_empty() {
209 name.as_str()
210 } else if let Some(rest) = name.strip_prefix(&prefix) {
211 rest
212 } else {
213 continue;
214 };
215 if let Some(pos) = stripped.find('/') {
217 groups.insert(stripped[..pos].to_string());
218 }
219 }
220 Ok(groups.into_iter().collect())
221 }
222}