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