image_cache.rs

  1use std::sync::Arc;
  2
  3use crate::ImageData;
  4use collections::HashMap;
  5use futures::{
  6    future::{BoxFuture, Shared},
  7    AsyncReadExt, FutureExt,
  8};
  9use image::ImageError;
 10use parking_lot::Mutex;
 11use thiserror::Error;
 12use util::{
 13    arc_cow::ArcCow,
 14    defer,
 15    http::{self, HttpClient},
 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<
 48        Mutex<
 49            HashMap<
 50                ArcCow<'static, str>,
 51                Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>,
 52            >,
 53        >,
 54    >,
 55}
 56
 57impl ImageCache {
 58    pub fn new(client: Arc<dyn HttpClient>) -> Self {
 59        ImageCache {
 60            client,
 61            images: Default::default(),
 62        }
 63    }
 64
 65    pub fn get(
 66        &self,
 67        uri: ArcCow<'static, str>,
 68    ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
 69        match self.images.lock().get(uri.as_ref()) {
 70            Some(future) => future.clone(),
 71            None => {
 72                let client = self.client.clone();
 73                let images = self.images.clone();
 74                let future = {
 75                    let uri = uri.clone();
 76                    async move {
 77                        // If we error, remove the cached future. Otherwise we cancel before returning.
 78                        let remove_cached_future = defer({
 79                            let uri = uri.clone();
 80                            move || {
 81                                images.lock().remove(uri.as_ref());
 82                            }
 83                        });
 84
 85                        let mut response = client.get(uri.as_ref(), ().into(), true).await?;
 86                        let mut body = Vec::new();
 87                        response.body_mut().read_to_end(&mut body).await?;
 88
 89                        if !response.status().is_success() {
 90                            return Err(Error::BadStatus {
 91                                status: response.status(),
 92                                body: String::from_utf8_lossy(&body).into_owned(),
 93                            });
 94                        }
 95
 96                        let format = image::guess_format(&body)?;
 97                        let image =
 98                            image::load_from_memory_with_format(&body, format)?.into_bgra8();
 99
100                        remove_cached_future.cancel();
101                        Ok(ImageData::new(image))
102                    }
103                }
104                .boxed()
105                .shared();
106                self.images.lock().insert(uri.clone(), future.clone());
107                future
108            }
109        }
110    }
111}