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