1use std::sync::Arc;
2
3use crate::ImageData;
4use collections::HashMap;
5use futures::{
6 future::{BoxFuture, Shared},
7 AsyncReadExt, FutureExt,
8};
9use image::ImageError;
10use parking_lot::Mutex;
11use thiserror::Error;
12use util::{
13 arc_cow::ArcCow,
14 http::{self, HttpClient},
15};
16
17#[derive(Debug, Error, Clone)]
18pub enum Error {
19 #[error("http error: {0}")]
20 Client(#[from] http::Error),
21 #[error("IO error: {0}")]
22 Io(Arc<std::io::Error>),
23 #[error("unexpected http status: {status}, body: {body}")]
24 BadStatus {
25 status: http::StatusCode,
26 body: String,
27 },
28 #[error("image error: {0}")]
29 Image(Arc<ImageError>),
30}
31
32impl From<std::io::Error> for Error {
33 fn from(error: std::io::Error) -> Self {
34 Error::Io(Arc::new(error))
35 }
36}
37
38impl From<ImageError> for Error {
39 fn from(error: ImageError) -> Self {
40 Error::Image(Arc::new(error))
41 }
42}
43
44pub struct ImageCache {
45 client: Arc<dyn HttpClient>,
46 images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
47}
48
49type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
50
51impl ImageCache {
52 pub fn new(client: Arc<dyn HttpClient>) -> Self {
53 ImageCache {
54 client,
55 images: Default::default(),
56 }
57 }
58
59 pub fn get(
60 &self,
61 uri: impl Into<ArcCow<'static, str>>,
62 ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
63 let uri = uri.into();
64 let mut images = self.images.lock();
65
66 match images.get(uri.as_ref()) {
67 Some(future) => future.clone(),
68 None => {
69 let client = self.client.clone();
70 let future = {
71 let uri = uri.clone();
72 async move {
73 let mut response = client.get(uri.as_ref(), ().into(), true).await?;
74 let mut body = Vec::new();
75 response.body_mut().read_to_end(&mut body).await?;
76
77 if !response.status().is_success() {
78 return Err(Error::BadStatus {
79 status: response.status(),
80 body: String::from_utf8_lossy(&body).into_owned(),
81 });
82 }
83
84 let format = image::guess_format(&body)?;
85 let image =
86 image::load_from_memory_with_format(&body, format)?.into_bgra8();
87 Ok(ImageData::new(image))
88 }
89 }
90 .boxed()
91 .shared();
92
93 images.insert(uri, future.clone());
94 future
95 }
96 }
97 }
98}