1use crate::radar::{
2 Radar, RadarData, RadarId, RadarImageFeature, RadarImageFeatureLayer, RadarImageLegend,
3 RadarLegendType, RadarType,
4};
5use anyhow::{anyhow, Result};
6use chrono::Duration;
7use std::str::FromStr;
8use std::{io::Cursor, thread::sleep};
9use strum::IntoEnumIterator;
10use suppaftp::list::File;
11use suppaftp::FtpStream;
12use tracing::{debug, error};
13
14pub struct FtpClient {
15 ftp_stream: Option<FtpStream>,
17 root_url: String,
18}
19
20impl FtpClient {
21 pub fn new() -> Result<Self> {
22 let root_url = "ftp.bom.gov.au:21".to_string();
23 Ok(FtpClient {
24 ftp_stream: None,
25 root_url,
26 })
27 }
28
29 fn stream(&mut self) -> Result<&mut FtpStream> {
30 if self.ftp_stream.is_none() {
31 let mut stream = FtpStream::connect(&self.root_url)?;
32 stream.login("anonymous", "guest")?;
33 self.ftp_stream = Some(stream);
34 }
35 Ok(self.ftp_stream.as_mut().unwrap())
36 }
37
38 fn get_buf(&mut self, path: &str) -> Result<Cursor<Vec<u8>>> {
39 debug!("Downloading {}{path}", self.root_url);
40 Ok(self.stream()?.retr_as_buffer(path)?)
41 }
42
43 pub fn keepalive(&mut self) -> Result<()> {
44 if self.ftp_stream.is_some() {
45 self.stream()?.noop()?;
46 }
47 Ok(())
48 }
49
50 pub fn list_files(&mut self, path: &str) -> Result<impl Iterator<Item = File>> {
51 let url = format!("{}{}", self.root_url, path);
52 debug!("Listing directory {url}");
53 let mut attempts = 0;
54 let listing = loop {
55 match self.stream()?.list(Some(path)) {
56 Ok(l) => break l,
57 Err(e) => {
58 error!("Error listing directory {url}. {e} Retry in 5 seconds");
59 attempts += 1;
60 if attempts > 5 {
61 return Err(anyhow!(
62 "Failed to list directory {url} after {attempts} attempts. {e}"
63 ));
64 }
65 sleep(Duration::seconds(5).to_std().unwrap());
66 continue;
67 }
68 };
69 };
70 Ok(listing.into_iter().map(|s| File::from_str(&s).unwrap()))
71 }
72
73 pub fn get_radar_data(&mut self) -> Result<Vec<RadarData>> {
74 let buf = self.get_buf("/anon/home/adfd/spatial/IDR00007.dbf")?;
75 let mut reader = dbase::Reader::new(buf)?;
76 Ok(reader.read_as::<RadarData>()?)
77 }
78
79 pub fn get_public_radars(&mut self) -> Result<impl Iterator<Item = Radar>> {
80 Ok(self
81 .get_radar_data()?
82 .into_iter()
83 .filter(|r| r.status == "Public")
84 .map(|r| r.into()))
85 }
86
87 pub fn get_radar_legends(&mut self) -> Result<Vec<RadarImageLegend>> {
88 let mut legends = Vec::with_capacity(3);
89 for t in RadarLegendType::iter() {
90 let path = format!("/anon/gen/radar_transparencies/IDR.legend.{}.png", t.id());
91 legends.push(RadarImageLegend {
92 r#type: t,
93 png_buf: self.get_buf(&path)?.into_inner(),
94 })
95 }
96 Ok(legends)
97 }
98
99 pub fn get_radar_feature_layers(
100 &mut self,
101 id: RadarId,
102 size: RadarType,
103 ) -> Result<Vec<RadarImageFeatureLayer>> {
104 let mut layers = Vec::new();
105 for feature in RadarImageFeature::iter() {
106 let layer = self.get_radar_feature_layer(id, size, feature)?;
107 layers.push(layer);
108 }
109 Ok(layers)
110 }
111
112 pub fn get_radar_feature_layer(
113 &mut self,
114 id: RadarId,
115 size: RadarType,
116 feature: RadarImageFeature,
117 ) -> Result<RadarImageFeatureLayer> {
118 let filename = format!("IDR{id:02}{}.{feature}.png", size.id());
119 let path = format!("/anon/gen/radar_transparencies/{filename}");
120 let png_buf = self.get_buf(&path)?.into_inner();
121 Ok(RadarImageFeatureLayer {
122 feature,
123 size,
124 radar_id: id,
125 png_buf,
126 filename,
127 })
128 }
129
130 pub fn list_radar_data_layers(&mut self) -> Result<impl Iterator<Item = File>> {
131 Ok(self
132 .list_files("/anon/gen/radar")?
133 .filter(move |f| f.name().ends_with(".png")))
134 }
135
136 pub fn get_radar_data_png(&mut self, filename: &str) -> Result<Vec<u8>> {
137 let buf = self.get_buf(&format!("/anon/gen/radar/{}", filename))?;
138 Ok(buf.into_inner())
139 }
140}