nexrad_data/aws/realtime/
chunk_identifier.rs1use crate::aws::realtime::{ChunkType, VolumeIndex};
2use chrono::{DateTime, Utc};
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub struct ChunkIdentifier {
8 site: String,
9 volume: VolumeIndex,
10 name: String,
11 date_time: Option<DateTime<Utc>>,
12}
13
14impl ChunkIdentifier {
15 pub fn new(
17 site: String,
18 volume: VolumeIndex,
19 name: String,
20 date_time: Option<DateTime<Utc>>,
21 ) -> Self {
22 Self {
23 site,
24 volume,
25 name,
26 date_time,
27 }
28 }
29
30 pub fn with_sequence(&self, sequence: usize) -> Self {
33 let name = format!(
34 "{}-{:03}-{}",
35 self.name_prefix(),
36 sequence,
37 match sequence {
38 1 => "S",
39 55 => "E",
40 _ => "I",
41 }
42 );
43
44 Self {
45 site: self.site.clone(),
46 volume: self.volume,
47 name,
48 date_time: None,
49 }
50 }
51
52 pub fn site(&self) -> &str {
54 &self.site
55 }
56
57 pub fn volume(&self) -> &VolumeIndex {
59 &self.volume
60 }
61
62 pub fn name(&self) -> &str {
64 &self.name
65 }
66
67 pub fn name_prefix(&self) -> &str {
69 &self.name[..15]
70 }
71
72 pub fn sequence(&self) -> Option<usize> {
74 self.name.split('-').nth(2).and_then(|s| s.parse().ok())
75 }
76
77 pub fn chunk_type(&self) -> Option<ChunkType> {
79 match self.name.chars().last() {
80 Some('S') => Some(ChunkType::Start),
81 Some('I') => Some(ChunkType::Intermediate),
82 Some('E') => Some(ChunkType::End),
83 _ => None,
84 }
85 }
86
87 pub fn date_time(&self) -> Option<DateTime<Utc>> {
89 self.date_time
90 }
91
92 pub fn next_chunk(&self) -> Option<NextChunk> {
94 let sequence = self.sequence()?;
95 let mut chunk_type;
96
97 if sequence < 55 {
98 let next_sequence = sequence + 1;
99
100 chunk_type = ChunkType::Intermediate;
101 if next_sequence == 55 {
102 chunk_type = ChunkType::End;
103 }
104
105 let name = format!(
106 "{}-{:03}-{}",
107 self.name_prefix(),
108 next_sequence,
109 match chunk_type {
110 ChunkType::Start => "S",
111 ChunkType::Intermediate => "I",
112 ChunkType::End => "E",
113 }
114 );
115
116 let next_chunk = ChunkIdentifier::new(self.site().to_string(), self.volume, name, None);
117 return Some(NextChunk::Sequence(next_chunk));
118 }
119
120 let mut volume = self.volume.as_number() + 1;
121 if volume > 999 {
122 volume = 1;
123 }
124
125 Some(NextChunk::Volume(VolumeIndex::new(volume)))
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub enum NextChunk {
132 Sequence(ChunkIdentifier),
138
139 Volume(VolumeIndex),
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 use chrono::TimeZone;
149
150 #[test]
151 fn test_chunk_identifier() {
152 let site = "KTLX";
153 let volume = 50;
154 let name = "20240813-123330-014-I";
155 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
156
157 let chunk = ChunkIdentifier::new(
158 site.to_string(),
159 VolumeIndex::new(volume),
160 name.to_string(),
161 Some(date_time),
162 );
163
164 assert_eq!(chunk.site(), site);
165 assert_eq!(chunk.volume().as_number(), 50);
166 assert_eq!(chunk.name(), name);
167 assert_eq!(chunk.name_prefix(), "20240813-123330");
168 assert_eq!(chunk.chunk_type(), Some(ChunkType::Intermediate));
169 assert_eq!(chunk.sequence(), Some(14));
170 assert_eq!(chunk.date_time(), Some(date_time));
171 }
172
173 #[test]
174 fn test_next_chunk_start() {
175 let site = "KTLX";
176 let volume = 50;
177 let name = "20240813-123330-001-S";
178 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
179
180 let chunk = ChunkIdentifier::new(
181 site.to_string(),
182 VolumeIndex::new(volume),
183 name.to_string(),
184 Some(date_time),
185 );
186
187 let next_chunk = chunk.next_chunk().expect("Expected next chunk");
188 match next_chunk {
189 NextChunk::Sequence(next_chunk) => {
190 assert_eq!(next_chunk.site(), site);
191 assert_eq!(next_chunk.volume().as_number(), 50);
192 assert_eq!(next_chunk.name(), "20240813-123330-002-I");
193 assert_eq!(next_chunk.name_prefix(), "20240813-123330");
194 assert_eq!(next_chunk.sequence(), Some(2));
195 assert_eq!(next_chunk.chunk_type(), Some(ChunkType::Intermediate));
196 assert_eq!(next_chunk.date_time(), None);
197 }
198 _ => panic!("Expected sequence"),
199 }
200 }
201
202 #[test]
203 fn test_next_chunk_intermediate() {
204 let site = "KTLX";
205 let volume = 999;
206 let name = "20240813-123330-014-I";
207 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
208
209 let chunk = ChunkIdentifier::new(
210 site.to_string(),
211 VolumeIndex::new(volume),
212 name.to_string(),
213 Some(date_time),
214 );
215
216 let next_chunk = chunk.next_chunk().expect("Expected next chunk");
217 match next_chunk {
218 NextChunk::Sequence(next_chunk) => {
219 assert_eq!(next_chunk.site(), site);
220 assert_eq!(next_chunk.volume().as_number(), 999);
221 assert_eq!(next_chunk.name(), "20240813-123330-015-I");
222 assert_eq!(next_chunk.name_prefix(), "20240813-123330");
223 assert_eq!(next_chunk.sequence(), Some(15));
224 assert_eq!(next_chunk.chunk_type(), Some(ChunkType::Intermediate));
225 assert_eq!(next_chunk.date_time(), None);
226 }
227 _ => panic!("Expected sequence"),
228 }
229 }
230
231 #[test]
232 fn test_next_chunk_end() {
233 let site = "KTLX";
234 let volume = 50;
235 let name = "20240813-123330-055-E";
236 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
237
238 let chunk = ChunkIdentifier::new(
239 site.to_string(),
240 VolumeIndex::new(volume),
241 name.to_string(),
242 Some(date_time),
243 );
244
245 let next_chunk = chunk.next_chunk().expect("Expected next chunk");
246 match next_chunk {
247 NextChunk::Volume(next_volume) => {
248 assert_eq!(next_volume.as_number(), 51);
249 }
250 _ => panic!("Expected volume"),
251 }
252 }
253
254 #[test]
255 fn test_next_chunk_last_volume() {
256 let site = "KTLX";
257 let volume = 999;
258 let name = "20240813-123330-055-E";
259 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
260
261 let chunk = ChunkIdentifier::new(
262 site.to_string(),
263 VolumeIndex::new(volume),
264 name.to_string(),
265 Some(date_time),
266 );
267
268 let next_chunk = chunk.next_chunk().expect("Expected next chunk");
269 match next_chunk {
270 NextChunk::Volume(next_volume) => {
271 assert_eq!(next_volume.as_number(), 1);
272 }
273 _ => panic!("Expected volume"),
274 }
275 }
276
277 #[test]
278 fn test_chunk_from_sequence() {
279 let site = "KTLX";
280 let volume = 50;
281 let name = "20240813-123330-014-I";
282 let date_time = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
283
284 let chunk = ChunkIdentifier::new(
285 site.to_string(),
286 VolumeIndex::new(volume),
287 name.to_string(),
288 Some(date_time),
289 );
290
291 let next_chunk = chunk.with_sequence(15);
292 assert_eq!(next_chunk.site(), site);
293 assert_eq!(next_chunk.volume().as_number(), 50);
294 assert_eq!(next_chunk.name(), "20240813-123330-015-I");
295 assert_eq!(next_chunk.name_prefix(), "20240813-123330");
296 assert_eq!(next_chunk.sequence(), Some(15));
297 assert_eq!(next_chunk.chunk_type(), Some(ChunkType::Intermediate));
298 assert_eq!(next_chunk.date_time(), None);
299
300 assert_eq!(chunk.with_sequence(1).chunk_type(), Some(ChunkType::Start));
301 assert_eq!(chunk.with_sequence(55).chunk_type(), Some(ChunkType::End));
302 }
303}