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}