netcdf_reader/classic/
mod.rs1pub mod data;
7pub mod header;
8pub(crate) mod storage;
9pub mod types;
10pub mod variable;
11
12use std::fs::File;
13use std::path::Path;
14
15use memmap2::Mmap;
16
17use crate::error::Result;
18use crate::types::NcGroup;
19use crate::NcFormat;
20
21use storage::ClassicStorage;
22
23pub struct ClassicFile {
25 pub(crate) format: NcFormat,
26 pub(crate) root_group: NcGroup,
27 pub(crate) storage: ClassicStorage,
28 pub(crate) numrecs: u64,
29}
30
31impl ClassicFile {
32 pub fn open(path: &Path, format: NcFormat) -> Result<Self> {
34 let file = File::open(path)?;
35 let mmap = unsafe { Mmap::map(&file)? };
37 let header = header::parse_header(&mmap, format)?;
38 reject_unsupported_classic_features(&header)?;
39 let storage = ClassicStorage::from_mmap(mmap);
40
41 let root_group = NcGroup {
42 name: "/".to_string(),
43 dimensions: header.dimensions,
44 variables: header.variables,
45 attributes: header.global_attributes,
46 groups: Vec::new(), };
48
49 Ok(ClassicFile {
50 format,
51 root_group,
52 storage,
53 numrecs: header.numrecs,
54 })
55 }
56
57 pub fn from_bytes(bytes: &[u8], format: NcFormat) -> Result<Self> {
59 let header = header::parse_header(bytes, format)?;
60 reject_unsupported_classic_features(&header)?;
61 let storage = ClassicStorage::from_bytes(bytes.to_vec());
62
63 let root_group = NcGroup {
64 name: "/".to_string(),
65 dimensions: header.dimensions,
66 variables: header.variables,
67 attributes: header.global_attributes,
68 groups: Vec::new(),
69 };
70
71 Ok(ClassicFile {
72 format,
73 root_group,
74 storage,
75 numrecs: header.numrecs,
76 })
77 }
78
79 pub fn from_mmap(mmap: Mmap, format: NcFormat) -> Result<Self> {
81 let header = header::parse_header(&mmap, format)?;
82 reject_unsupported_classic_features(&header)?;
83 let storage = ClassicStorage::from_mmap(mmap);
84
85 let root_group = NcGroup {
86 name: "/".to_string(),
87 dimensions: header.dimensions,
88 variables: header.variables,
89 attributes: header.global_attributes,
90 groups: Vec::new(),
91 };
92
93 Ok(ClassicFile {
94 format,
95 root_group,
96 storage,
97 numrecs: header.numrecs,
98 })
99 }
100
101 #[cfg(feature = "netcdf4")]
103 pub fn from_storage(
104 storage: hdf5_reader::storage::DynStorage,
105 format: NcFormat,
106 ) -> Result<Self> {
107 let storage = ClassicStorage::from_range(storage);
108 let header = parse_header_from_storage(&storage, format)?;
109 reject_unsupported_classic_features(&header)?;
110
111 let root_group = NcGroup {
112 name: "/".to_string(),
113 dimensions: header.dimensions,
114 variables: header.variables,
115 attributes: header.global_attributes,
116 groups: Vec::new(),
117 };
118
119 Ok(ClassicFile {
120 format,
121 root_group,
122 storage,
123 numrecs: header.numrecs,
124 })
125 }
126
127 pub fn format(&self) -> NcFormat {
129 self.format
130 }
131
132 pub fn root_group(&self) -> &NcGroup {
134 &self.root_group
135 }
136
137 pub fn numrecs(&self) -> u64 {
139 self.numrecs
140 }
141}
142
143fn reject_unsupported_classic_features(header: &header::ClassicHeader) -> Result<()> {
144 let has_subfiling_marker = header
145 .global_attributes
146 .iter()
147 .any(|attr| is_subfiling_attribute_name(&attr.name))
148 || header.variables.iter().any(|var| {
149 var.attributes
150 .iter()
151 .any(|attr| is_subfiling_attribute_name(&attr.name))
152 });
153
154 if has_subfiling_marker {
155 return Err(crate::Error::UnsupportedFeature(
156 "PnetCDF subfiling datasets require a virtual multi-file storage adapter".to_string(),
157 ));
158 }
159
160 Ok(())
161}
162
163fn is_subfiling_attribute_name(name: &str) -> bool {
164 let lower = name.to_ascii_lowercase();
165 lower.starts_with("_pnetcdf_subfiling") || lower.starts_with("subfiling")
166}
167
168#[cfg(feature = "netcdf4")]
169fn parse_header_from_storage(
170 storage: &ClassicStorage,
171 format: NcFormat,
172) -> Result<header::ClassicHeader> {
173 let mut len = storage.initial_header_len();
174
175 loop {
176 let prefix = storage.read_header_prefix(len)?;
177 match header::parse_header(prefix.as_ref(), format) {
178 Ok(header) => return Ok(header),
179 Err(crate::Error::UnexpectedEof { .. }) if (prefix.len() as u64) < storage.len() => {
180 let current = prefix.len().max(1);
181 len = current.saturating_mul(2);
182 }
183 Err(err) => return Err(err),
184 }
185 }
186}