image_cache.rs

 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}