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
 11pub use image::ImageFormat;
 12
 13#[derive(PartialEq, Eq, Hash, Clone)]
 14pub(crate) struct RenderImageParams {
 15    pub(crate) image_id: ImageId,
 16}
 17
 18#[derive(Debug, Error, Clone)]
 19pub(crate) 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(crate) struct ImageCache {
 46    client: Arc<dyn HttpClient>,
 47    images: Arc<Mutex<HashMap<UriOrPath, FetchImageTask>>>,
 48}
 49
 50#[derive(Debug, PartialEq, Eq, Hash, Clone)]
 51pub(crate) enum UriOrPath {
 52    Uri(SharedUri),
 53    Path(Arc<PathBuf>),
 54}
 55
 56impl From<SharedUri> for UriOrPath {
 57    fn from(value: SharedUri) -> Self {
 58        Self::Uri(value)
 59    }
 60}
 61
 62impl From<Arc<PathBuf>> for UriOrPath {
 63    fn from(value: Arc<PathBuf>) -> Self {
 64        Self::Path(value)
 65    }
 66}
 67
 68type FetchImageTask = Shared<Task<Result<Arc<ImageData>, Error>>>;
 69
 70impl ImageCache {
 71    pub fn new(client: Arc<dyn HttpClient>) -> Self {
 72        ImageCache {
 73            client,
 74            images: Default::default(),
 75        }
 76    }
 77
 78    pub fn get(&self, uri_or_path: impl Into<UriOrPath>, cx: &AppContext) -> FetchImageTask {
 79        let uri_or_path = uri_or_path.into();
 80        let mut images = self.images.lock();
 81
 82        match images.get(&uri_or_path) {
 83            Some(future) => future.clone(),
 84            None => {
 85                let client = self.client.clone();
 86                let future = cx
 87                    .background_executor()
 88                    .spawn(
 89                        {
 90                            let uri_or_path = uri_or_path.clone();
 91                            async move {
 92                                match uri_or_path {
 93                                    UriOrPath::Path(uri) => {
 94                                        let image = image::open(uri.as_ref())?.into_bgra8();
 95                                        Ok(Arc::new(ImageData::new(image)))
 96                                    }
 97                                    UriOrPath::Uri(uri) => {
 98                                        let mut response =
 99                                            client.get(uri.as_ref(), ().into(), true).await?;
100                                        let mut body = Vec::new();
101                                        response.body_mut().read_to_end(&mut body).await?;
102
103                                        if !response.status().is_success() {
104                                            return Err(Error::BadStatus {
105                                                status: response.status(),
106                                                body: String::from_utf8_lossy(&body).into_owned(),
107                                            });
108                                        }
109
110                                        let format = image::guess_format(&body)?;
111                                        let image =
112                                            image::load_from_memory_with_format(&body, format)?
113                                                .into_bgra8();
114                                        Ok(Arc::new(ImageData::new(image)))
115                                    }
116                                }
117                            }
118                        }
119                        .map_err({
120                            let uri_or_path = uri_or_path.clone();
121                            move |error| {
122                                log::log!(log::Level::Error, "{:?} {:?}", &uri_or_path, &error);
123                                error
124                            }
125                        }),
126                    )
127                    .shared();
128
129                images.insert(uri_or_path, future.clone());
130                future
131            }
132        }
133    }
134}