image_cache.rs

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