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