1use crate::io::netcdf::{
2 NetCDF, Output, Reader, State, Writer,
3 format::{self, AttValue, Attribute, DimSpec},
4 nc_lock, reject_nul,
5};
6use std::{collections::HashMap, ffi::NulError, fs::File, io::Write};
7
8impl NetCDF {
9 pub fn close(&mut self) {
10 let _guard = nc_lock();
11 if let State::Write(writer) = &mut self.state
12 && let Some(mut output) = writer.output.take()
13 {
14 if let Some(threads) = writer.netcdf4 {
15 let mut file = std::io::BufWriter::new(&mut output.file);
16 let _ = format::hdf5::write(
17 &writer.dims,
18 &writer.global_attributes,
19 &output.variables,
20 std::mem::take(&mut output.data),
21 threads,
22 &mut file,
23 );
24 let _ = file.flush();
25 }
26 let _ = output.file.flush();
27 }
28 }
29 pub fn create(path: &str) -> Result<Self, NulError> {
30 Self::new(path, None)
31 }
32 pub fn create_netcdf4(path: &str, threads: usize) -> Result<Self, NulError> {
35 Self::new(path, Some(threads))
36 }
37 fn new(path: &str, netcdf4: Option<usize>) -> Result<Self, NulError> {
38 reject_nul(path)?;
39 Ok(Self {
40 state: State::Write(Writer {
41 path: path.to_string(),
42 dims: Vec::new(),
43 global_attributes: Vec::new(),
44 variables: Vec::new(),
45 netcdf4,
46 output: None,
47 }),
48 })
49 }
50 pub fn open(path: &str) -> Result<Self, NulError> {
51 reject_nul(path)?;
52 let _guard = nc_lock();
53 let bytes = std::fs::read(path).expect("failed to read netCDF file");
54 let parsed = format::parse(&bytes);
55 Ok(Self {
56 state: State::Read(Reader { bytes, parsed }),
57 })
58 }
59 pub fn dimension_length(&self, name: &str) -> Result<usize, NulError> {
60 reject_nul(name)?;
61 Ok(self
62 .lookup_dimension(name)
63 .unwrap_or_else(|| panic!("no dimension named {name}")) as usize)
64 }
65 pub fn try_dimension_length(&self, name: &str) -> Result<Option<usize>, NulError> {
66 reject_nul(name)?;
67 Ok(self.lookup_dimension(name).map(|len| len as usize))
68 }
69 fn lookup_dimension(&self, name: &str) -> Option<u64> {
70 let dims: &[DimSpec] = match &self.state {
71 State::Read(reader) => &reader.parsed.dims,
72 State::Write(writer) => &writer.dims,
73 };
74 dims.iter().find(|dim| dim.name == name).map(|dim| dim.len)
75 }
76 pub fn get_variable_attribute_text(
77 &self,
78 variable: &str,
79 attr_name: &str,
80 ) -> Result<String, NulError> {
81 reject_nul(variable)?;
82 reject_nul(attr_name)?;
83 let attributes: &[Attribute] = match &self.state {
84 State::Read(reader) => reader
85 .parsed
86 .vars
87 .iter()
88 .find(|var| var.name == variable)
89 .map(|var| var.atts.as_slice()),
90 State::Write(writer) => writer
91 .variables
92 .iter()
93 .find(|var| var.name == variable)
94 .map(|var| var.attributes.as_slice()),
95 }
96 .unwrap_or_else(|| panic!("no variable named {variable}"));
97 match attributes
98 .iter()
99 .find(|attribute| attribute.name == attr_name)
100 .map(|attribute| &attribute.value)
101 {
102 Some(AttValue::Text(text)) => Ok(text.clone()),
103 _ => panic!("no text attribute {variable}::{attr_name}"),
104 }
105 }
106 pub fn define_dimension(&mut self, name: &str, len: usize) -> Result<(), NulError> {
107 reject_nul(name)?;
108 let _guard = nc_lock();
109 self.writer_defining().dims.push(DimSpec {
110 name: name.to_string(),
111 len: len as u64,
112 });
113 Ok(())
114 }
115 pub fn end_definition(&mut self) {
116 let _guard = nc_lock();
117 let writer = match &mut self.state {
118 State::Write(writer) => writer,
119 State::Read(_) => panic!("end_definition on a NetCDF opened for reading"),
120 };
121 assert!(writer.output.is_none(), "end_definition called twice");
122 let dim_index: HashMap<&str, usize> = writer
123 .dims
124 .iter()
125 .enumerate()
126 .map(|(index, dim)| (dim.name.as_str(), index))
127 .collect();
128 let mut variables: Vec<format::VarSpec> = writer
129 .variables
130 .iter_mut()
131 .map(|build| format::VarSpec {
132 name: build.name.clone(),
133 xtype: build.xtype,
134 dimids: build
135 .dim_names
136 .iter()
137 .map(|dim| {
138 *dim_index.get(dim.as_str()).unwrap_or_else(|| {
139 panic!("variable references unknown dimension {dim}")
140 })
141 })
142 .collect(),
143 atts: std::mem::take(&mut build.attributes),
144 begin: 0,
145 vsize: 0,
146 storage: format::Storage::Classic,
147 })
148 .collect();
149 let mut file = File::create(&writer.path).expect("failed to create netCDF file");
150 if writer.netcdf4.is_none() {
151 let header = format::finalize(&writer.dims, &writer.global_attributes, &mut variables);
152 file.write_all(&header)
153 .expect("failed to write netCDF header");
154 }
155 let data = vec![Vec::new(); variables.len()];
156 writer.output = Some(Output {
157 file,
158 variables,
159 data,
160 });
161 }
162 pub fn global(&mut self) {
163 let _guard = nc_lock();
164 let title = format!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
165 self.writer_defining().global_attributes.extend([
166 Attribute {
167 name: "api_version".to_string(),
168 value: AttValue::Float(vec![8.25]),
169 },
170 Attribute {
171 name: "file_size".to_string(),
172 value: AttValue::Int(vec![1]),
173 },
174 Attribute {
175 name: "floating_point_word_size".to_string(),
176 value: AttValue::Int(vec![8]),
177 },
178 Attribute {
179 name: "version".to_string(),
180 value: AttValue::Float(vec![8.25]),
181 },
182 Attribute {
183 name: "title".to_string(),
184 value: AttValue::Text(title),
185 },
186 ]);
187 }
188 pub fn put_variable_attribute_text(
189 &mut self,
190 variable: &str,
191 attr_name: &str,
192 value: &str,
193 ) -> Result<(), NulError> {
194 reject_nul(variable)?;
195 reject_nul(attr_name)?;
196 reject_nul(value)?;
197 let _guard = nc_lock();
198 let build = self
199 .writer_defining()
200 .variables
201 .iter_mut()
202 .find(|build| build.name == variable)
203 .unwrap_or_else(|| panic!("no variable named {variable}"));
204 build.attributes.push(Attribute {
205 name: attr_name.to_string(),
206 value: AttValue::Text(value.to_string()),
207 });
208 Ok(())
209 }
210 pub(super) fn writer_defining(&mut self) -> &mut Writer {
211 match &mut self.state {
212 State::Write(writer) if writer.output.is_none() => writer,
213 State::Write(_) => panic!("operation not allowed after end_definition"),
214 State::Read(_) => panic!("write operation on a NetCDF opened for reading"),
215 }
216 }
217}