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