1use rocket::fs::NamedFile;
4use rocket::http::Status;
5use rocket::request::Request;
6use rocket::response::{Responder, Response};
7use rocket::tokio::fs::create_dir_all;
8use rocket_etag_if_none_match::{EtagIfNoneMatch, entity_tag::EntityTag};
9use std::borrow::Cow;
10use std::fs;
11use std::fs::File;
12use std::io;
13use std::io::Error;
14use std::io::ErrorKind;
15use std::io::Write;
16use std::os::unix::fs::MetadataExt;
17use std::path::{Path, PathBuf};
18
19use crate::config::Config;
20use crate::tile_request::TileRequest;
21use crate::tile_request::WmtsRequest;
22use crate::tile_request::XyzRequest;
23
24pub struct CachedFile {
28 named_file: Option<NamedFile>,
29 etag_value: String,
30}
31
32impl CachedFile {
33 pub async fn from_path<P: AsRef<Path>>(
34 etag_if_none_match: EtagIfNoneMatch<'_>,
35 path: P,
36 ) -> Option<CachedFile> {
37 if path.as_ref().exists() && !path.as_ref().is_dir() {
38 let etag_value = CachedFile::etag_value(path.as_ref());
39 let et = unsafe { EntityTag::new_unchecked(false, Cow::Borrowed(&etag_value)) };
40 if etag_if_none_match.strong_eq(&et) {
41 return Some(CachedFile {
42 etag_value,
43 named_file: None,
44 });
45 }
46 if let Ok(f) = CachedFile::open(path).await {
47 return Some(f);
48 }
49 }
50 None
51 }
52 async fn open<P: AsRef<Path>>(path: P) -> io::Result<CachedFile> {
53 let etag_value = CachedFile::etag_value(path.as_ref());
54 let named_file = NamedFile::open(path).await?;
55 Ok(CachedFile {
56 named_file: Some(named_file),
57 etag_value,
58 })
59 }
60
61 pub fn etag_value<P: AsRef<Path>>(path: P) -> String {
62 let metadata = fs::metadata(path.as_ref()).unwrap();
63 let content_length = metadata.len();
64 let mtime = metadata.mtime();
65 format!(r#"{}-{}"#, mtime, content_length)
66 }
67}
68impl<'r> Responder<'r, 'static> for CachedFile {
69 fn respond_to(self, req: &Request) -> rocket::response::Result<'static> {
70 if let Some(f) = self.named_file {
71 return Response::build_from(f.respond_to(req)?)
72 .raw_header("Etag", self.etag_value)
73 .ok();
74 }
75 Err(Status::NotModified)
76 }
77}
78
79pub struct Cache {
81 config: Config,
82}
83impl Cache {
84 async fn create_dir(&self, dirpath: PathBuf) -> Option<Error> {
85 if !dirpath.exists() {
86 if let Err(error) = create_dir_all(dirpath).await {
87 info!("Failed to create directory, error={}", error);
88 return Some(Error::new(ErrorKind::Other, "!"));
89 }
90 }
91 None
92 }
93 fn get_wmts_url_template(&self, alias: &str) -> Option<String> {
94 for conf in self.config.clone().wmts {
95 info!("conf.alias={}, alias={}", conf.alias, alias);
96 if conf.alias.eq(alias) {
97 return Some(conf.url);
98 }
99 }
100 info!("No template found for {}", alias);
101 None
102 }
103
104 fn get_xyz_url_template(&self, alias: &str) -> Option<String> {
105 for conf in self.config.clone().xyz {
106 info!("conf.alias={}, alias={}", conf.alias, alias);
107 if conf.alias.eq(alias) {
108 return Some(conf.url);
109 }
110 }
111 info!("No template found for {}", alias);
112 None
113 }
114
115 pub async fn get_or_download_wmts(
116 &self,
117 etag_if_none_match: EtagIfNoneMatch<'_>,
118 request: WmtsRequest,
119 ) -> std::io::Result<CachedFile> {
120 let path = request.filepath(self.config.directory.as_str());
121 if let Some(f) = CachedFile::from_path(etag_if_none_match, path.clone()).await {
122 return Ok(f);
123 }
124 let dirpath = request.dirpath(self.config.directory.as_str());
125 if let Some(error) = self.create_dir(dirpath).await {
126 return Err(error);
127 }
128 if let Some(url_template) = self.get_wmts_url_template(request.alias.as_str()) {
129 let url = url_template
131 .replace("{layer}", request.layer.as_str())
132 .replace("{style}", request.style.as_str())
133 .replace("{tilematrixset}", request.tilematrixset.as_str())
134 .replace("{Service}", request.service.as_str())
135 .replace("{Request}", request.request.as_str())
136 .replace("{Version}", request.version.as_str())
137 .replace("{Format}", request.format.as_str())
138 .replace("{TileMatrix}", request.tile_matrix.as_str())
139 .replace("{TileCol}", request.tile_col.as_str())
140 .replace("{TileRow}", request.tile_row.as_str());
141 if let Some(filepath) = path.to_str() {
142 match Self::download(url.as_str(), filepath).await {
144 Ok(_) => return CachedFile::open(path).await,
145 Err(error) => {
146 info!("Failed to download or copy, error={}", error);
147 return Err(Error::new(ErrorKind::NotFound, "!"));
148 }
149 }
150 }
151 }
152 info!("No template found for {}", request.get_alias());
153 Err(Error::new(ErrorKind::InvalidInput, "!"))
154 }
155
156 pub async fn get_or_download_xyz(
157 &self,
158 etag_if_none_match: EtagIfNoneMatch<'_>,
159 request: XyzRequest,
160 ) -> std::io::Result<CachedFile> {
161 let path = request.filepath(self.config.directory.as_str());
162 if let Some(f) = CachedFile::from_path(etag_if_none_match, path.clone()).await {
163 return Ok(f);
164 }
165
166 if path.exists() && !path.is_dir() {
167 return CachedFile::open(path).await;
168 }
169 let dirpath = request.dirpath(self.config.directory.as_str());
170 if let Some(error) = self.create_dir(dirpath).await {
171 return Err(error);
172 }
173 if let Some(url_template) = self.get_xyz_url_template(request.alias.as_str()) {
174 let url = url_template
176 .replace("{a}", request.a.as_str())
177 .replace("{x}", request.x.as_str())
178 .replace("{y}", request.y.as_str())
179 .replace("{z}", request.z.as_str());
180 if let Some(filepath) = path.to_str() {
181 match Self::download(url.as_str(), filepath).await {
183 Ok(_) => return CachedFile::open(path).await,
184 Err(error) => {
185 info!("Failed to download or copy, error={}", error);
186 return Err(Error::new(ErrorKind::NotFound, "!"));
187 }
188 }
189 }
190 }
191 info!("No template found for {}", request.alias);
192 Err(Error::new(ErrorKind::InvalidInput, "!"))
193 }
194
195 async fn download(url: &str, filepath: &str) -> Result<(), String> {
196 info!("download {}", url);
197 match reqwest::get(url).await {
199 Err(download_error) => Err(download_error.to_string()),
200 Ok(response) => {
201 let file_creation = File::create(filepath);
203 if file_creation.is_err() {
204 return Err(format!(
205 "Failed to create the file, error={}",
206 file_creation.err().unwrap()
207 ));
208 }
209 let mut file = file_creation.ok().unwrap();
210 let file_reading = response.bytes().await;
212 if file_reading.is_err() {
213 return Err(format!(
214 "Failed to read, error={}",
215 file_reading.err().unwrap()
216 ));
217 }
218 let content = file_reading.ok().unwrap();
219 info!("Writing {}", filepath);
220 file.write_all(&content)
221 .map_err(|e| panic!("Failed to copy, error={}", e))
222 }
223 }
224 }
225 pub fn new(app_config: Config) -> Self {
226 Cache {
227 config: app_config.clone(),
228 }
229 }
230}