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