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                                match uri {
 72                                    SharedUrl::File(uri) => {
 73                                        let image = image::open(uri.as_ref())?.into_bgra8();
 74                                        Ok(Arc::new(ImageData::new(image)))
 75                                    }
 76                                    SharedUrl::Network(uri) => {
 77                                        let mut response =
 78                                            client.get(uri.as_ref(), ().into(), true).await?;
 79                                        let mut body = Vec::new();
 80                                        response.body_mut().read_to_end(&mut body).await?;
 81
 82                                        if !response.status().is_success() {
 83                                            return Err(Error::BadStatus {
 84                                                status: response.status(),
 85                                                body: String::from_utf8_lossy(&body).into_owned(),
 86                                            });
 87                                        }
 88
 89                                        let format = image::guess_format(&body)?;
 90                                        let image =
 91                                            image::load_from_memory_with_format(&body, format)?
 92                                                .into_bgra8();
 93                                        Ok(Arc::new(ImageData::new(image)))
 94                                    }
 95                                }
 96                            }
 97                        }
 98                        .map_err({
 99                            let uri = uri.clone();
100                            move |error| {
101                                log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
102                                error
103                            }
104                        }),
105                    )
106                    .shared();
107
108                images.insert(uri, future.clone());
109                future
110            }
111        }
112    }
113}