image_cache.rs

  1use crate::{ImageData, ImageId, SharedUrl};
  2use collections::HashMap;
  3use futures::{
  4    future::{BoxFuture, Shared},
  5    AsyncReadExt, FutureExt, TryFutureExt,
  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<SharedUrl, 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<SharedUrl>,
 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                .map_err({
 92                    let uri = uri.clone();
 93
 94                    move |error| {
 95                        log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
 96                        error
 97                    }
 98                })
 99                .boxed()
100                .shared();
101
102                images.insert(uri, future.clone());
103                future
104            }
105        }
106    }
107}