image_cache.rs

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