1use std::borrow::Borrow;
2use std::collections::HashMap;
3use std::error::Error;
4use std::fmt::Debug;
5use std::str::FromStr;
6
7pub use crate::errors::DezoomerError;
8
9pub use super::Vec2d;
10use super::ZoomError;
11use std::fmt;
12use crate::dezoomer::PageContents::Success;
13
14pub enum PageContents {
15 Unknown,
16 Success(Vec<u8>),
17 Error(ZoomError),
18}
19
20impl From<Result<Vec<u8>, ZoomError>> for PageContents {
21 fn from(res: Result<Vec<u8>, ZoomError>) -> Self {
22 res.map(Self::Success).unwrap_or_else(Self::Error)
23 }
24}
25
26impl std::fmt::Debug for PageContents {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 Self::Unknown => f.write_str("<not yet available>"),
30 Success(contents) => f.write_str(&String::from_utf8_lossy(contents)),
31 PageContents::Error(e) => write!(f, "{}", e)
32 }
33 }
34}
35
36pub struct DezoomerInput {
37 pub uri: String,
38 pub contents: PageContents,
39}
40
41pub struct DezoomerInputWithContents<'a> {
42 pub uri: &'a str,
43 pub contents: &'a [u8],
44}
45
46impl DezoomerInput {
47 pub fn with_contents(&self) -> Result<DezoomerInputWithContents, DezoomerError> {
48 match &self.contents {
49 PageContents::Unknown => Err(DezoomerError::NeedsData {
50 uri: self.uri.clone(),
51 }),
52 Success(contents) => Ok(DezoomerInputWithContents {
53 uri: &self.uri,
54 contents,
55 }),
56 PageContents::Error(e) => {
57 Err(DezoomerError::DownloadError{msg: e.to_string()})
58 }
59 }
60 }
61}
62
63pub type ZoomLevel = Box<dyn TileProvider + Sync>;
65
66pub type ZoomLevels = Vec<ZoomLevel>;
68
69pub trait IntoZoomLevels {
70 fn into_zoom_levels(self) -> ZoomLevels;
71}
72
73impl<I, Z> IntoZoomLevels for I
74where
75 I: Iterator<Item = Z>,
76 Z: TileProvider + Sync + 'static,
77{
78 fn into_zoom_levels(self) -> ZoomLevels {
79 self.map(|x| Box::new(x) as ZoomLevel).collect()
80 }
81}
82
83pub trait Dezoomer {
85 fn name(&self) -> &'static str;
87
88 fn zoom_levels(&mut self, data: &DezoomerInput) -> Result<ZoomLevels, DezoomerError>;
90 fn assert(&self, c: bool) -> Result<(), DezoomerError> {
91 if c {
92 Ok(())
93 } else {
94 Err(self.wrong_dezoomer())
95 }
96 }
97 fn wrong_dezoomer(&self) -> DezoomerError {
98 DezoomerError::WrongDezoomer { name: self.name() }
99 }
100}
101
102#[derive(Clone, Copy)]
103pub struct TileFetchResult {
104 pub count: u64,
105 pub successes: u64,
106 pub tile_size: Option<Vec2d>,
107}
108
109impl TileFetchResult {
110 pub fn is_success(&self) -> bool {
111 self.tile_size
112 .filter(|&Vec2d { x, y }| x > 0 && y > 0)
113 .is_some()
114 && self.successes > 0
115 }
116}
117
118type PostProcessResult = Result<Vec<u8>, Box<dyn Error + Send>>;
119#[derive(Clone, Copy)]
122pub enum PostProcessFn {
123 Fn(fn(&TileReference, Vec<u8>) -> PostProcessResult),
124 None,
125}
126
127pub trait TileProvider: Debug {
129 fn next_tiles(&mut self, previous: Option<TileFetchResult>) -> Vec<TileReference>;
132
133 fn post_process_fn(&self) -> PostProcessFn {
135 PostProcessFn::None
136 }
137
138 fn name(&self) -> String {
140 format!("{:?}", self)
141 }
142
143 fn title(&self) -> Option<String> { None }
145
146 fn size_hint(&self) -> Option<Vec2d> {
148 None
149 }
150
151 fn http_headers(&self) -> HashMap<String, String> {
153 HashMap::new()
154 }
155}
156
157pub struct ZoomLevelIter<'a> {
159 zoom_level: &'a mut ZoomLevel,
160 previous: Option<TileFetchResult>,
161 waiting_results: bool,
162}
163
164impl<'a> ZoomLevelIter<'a> {
165 pub fn new(zoom_level: &'a mut ZoomLevel) -> Self {
166 ZoomLevelIter { zoom_level, previous: None, waiting_results: false }
167 }
168 pub fn next_tile_references(&mut self) -> Option<Vec<TileReference>> {
169 assert!(!self.waiting_results);
170 self.waiting_results = true;
171 let tiles = self.zoom_level.next_tiles(self.previous);
172 if tiles.is_empty() { None } else { Some(tiles) }
173 }
174 pub fn set_fetch_result(&mut self, result: TileFetchResult) {
175 assert!(self.waiting_results);
176 self.waiting_results = false;
177 self.previous = Some(result)
178 }
179 pub fn size_hint(&self) -> Option<Vec2d> {
180 self.zoom_level.size_hint()
181 }
182}
183
184pub fn single_level<T: TileProvider + Sync + 'static>(
186 level: T,
187) -> Result<ZoomLevels, DezoomerError> {
188 Ok(vec![Box::new(level)])
189}
190
191pub trait TilesRect: Debug {
192 fn size(&self) -> Vec2d;
193 fn tile_size(&self) -> Vec2d;
194 fn tile_url(&self, pos: Vec2d) -> String;
195 fn title(&self) -> Option<String> { None }
196 fn tile_ref(&self, pos: Vec2d) -> TileReference {
197 TileReference {
198 url: self.tile_url(pos),
199 position: self.tile_size() * pos,
200 }
201 }
202 fn post_process_fn(&self) -> PostProcessFn {
203 PostProcessFn::None
204 }
205
206 fn tile_count(&self) -> u32 {
207 let Vec2d { x, y } = self.size().ceil_div(self.tile_size());
208 x * y
209 }
210}
211
212impl<T: TilesRect> TileProvider for T {
213 fn next_tiles(&mut self, previous: Option<TileFetchResult>) -> Vec<TileReference> {
214 if previous.is_some() {
217 return vec![];
218 }
219
220 let tile_size = self.tile_size();
221 let Vec2d { x: w, y: h } = self.size().ceil_div(tile_size);
222 let this: &T = self.borrow(); (0..h)
224 .flat_map(move |y| (0..w).map(move |x| this.tile_ref(Vec2d { x, y })))
225 .collect()
226 }
227
228 fn post_process_fn(&self) -> PostProcessFn {
229 TilesRect::post_process_fn(self)
230 }
231
232 fn name(&self) -> String {
233 let Vec2d { x, y } = self.size();
234 format!(
235 "{:?} ({:>5} x {:>5} pixels, {:>5} tiles)",
236 self,
237 x,
238 y,
239 self.tile_count()
240 )
241 }
242
243 fn title(&self) -> Option<String> { TilesRect::title(self) }
244
245 fn size_hint(&self) -> Option<Vec2d> {
246 Some(self.size())
247 }
248
249 fn http_headers(&self) -> HashMap<String, String> {
250 let mut headers = HashMap::new();
251 headers.insert("Referer".into(), self.tile_url(Vec2d::default()));
253 headers
254 }
255}
256
257#[derive(Debug, PartialEq, Eq, Hash, Clone)]
258pub struct TileReference {
259 pub url: String,
260 pub position: Vec2d,
261}
262
263impl FromStr for TileReference {
264 type Err = ZoomError;
265
266 fn from_str(tile_str: &str) -> Result<Self, Self::Err> {
267 let mut parts = tile_str.split(' ');
268 let make_error = || ZoomError::MalformedTileStr {
269 tile_str: String::from(tile_str),
270 };
271
272 if let (Some(x), Some(y), Some(url)) = (parts.next(), parts.next(), parts.next()) {
273 let x: u32 = x.parse().map_err(|_| make_error())?;
274 let y: u32 = y.parse().map_err(|_| make_error())?;
275 Ok(TileReference {
276 url: String::from(url),
277 position: Vec2d { x, y },
278 })
279 } else {
280 Err(make_error())
281 }
282 }
283}
284
285impl fmt::Display for TileReference {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 f.write_str(&self.url)
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[derive(Debug)]
296 struct FakeLvl;
297
298 impl TilesRect for FakeLvl {
299 fn size(&self) -> Vec2d {
300 Vec2d { x: 100, y: 100 }
301 }
302
303 fn tile_size(&self) -> Vec2d {
304 Vec2d { x: 60, y: 60 }
305 }
306
307 fn tile_url(&self, pos: Vec2d) -> String {
308 format!("{},{}", pos.x, pos.y)
309 }
310 }
311
312 #[test]
313 fn assert_tiles() {
314 let mut lvl: ZoomLevel = Box::new(FakeLvl {});
315 let mut all_tiles = vec![];
316 let mut zoom_level_iter = ZoomLevelIter::new(&mut lvl);
317 while let Some(tiles) = zoom_level_iter.next_tile_references() {
318 all_tiles.extend(tiles);
319 zoom_level_iter.set_fetch_result(TileFetchResult {
320 count: 0,
321 successes: 0,
322 tile_size: None,
323 });
324 };
325 assert_eq!(
326 all_tiles,
327 vec![
328 TileReference {
329 url: "0,0".into(),
330 position: Vec2d { x: 0, y: 0 },
331 },
332 TileReference {
333 url: "1,0".into(),
334 position: Vec2d { x: 60, y: 0 },
335 },
336 TileReference {
337 url: "0,1".into(),
338 position: Vec2d { x: 0, y: 60 },
339 },
340 TileReference {
341 url: "1,1".into(),
342 position: Vec2d { x: 60, y: 60 },
343 }
344 ]
345 );
346 }
347}