1use bliss_traits::net::{AbortSignal, Body, Bytes, NetHandler, NetProvider, NetWaker, Request};
7use data_url::DataUrl;
8use std::{marker::PhantomData, pin::Pin, sync::Arc, task::Poll};
9use tokio::runtime::Handle;
10
11#[cfg(feature = "cache")]
12use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions};
13
14const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0";
15
16#[cfg(feature = "cache")]
17type Client = reqwest_middleware::ClientWithMiddleware;
18#[cfg(not(feature = "cache"))]
19type Client = reqwest::Client;
20
21#[cfg(feature = "cache")]
22type RequestBuilder = reqwest_middleware::RequestBuilder;
23#[cfg(not(feature = "cache"))]
24type RequestBuilder = reqwest::RequestBuilder;
25
26#[cfg(feature = "cache")]
27fn get_cache_path() -> std::path::PathBuf {
28 use directories::ProjectDirs;
29 let path = ProjectDirs::from("com", "DioxusLabs", "Bliss")
30 .expect("Failed to find cache directory")
31 .cache_dir()
32 .to_owned();
33 println!("Using cache dir {}", path.display());
34 path
35}
36
37pub struct Provider {
38 rt: Handle,
39 client: Client,
40 waker: Arc<dyn NetWaker>,
41}
42impl Provider {
43 pub fn new(waker: Option<Arc<dyn NetWaker>>) -> Self {
44 let builder = reqwest::Client::builder();
45 #[cfg(feature = "cookies")]
46 let builder = builder.cookie_store(true);
47 let client = builder.build().unwrap();
48
49 #[cfg(feature = "cache")]
50 let client = reqwest_middleware::ClientBuilder::new(client)
51 .with(Cache(HttpCache {
52 mode: CacheMode::Default,
53 manager: CACacheManager::new(get_cache_path(), true),
54 options: HttpCacheOptions::default(),
55 }))
56 .build();
57
58 let waker = waker.unwrap_or(Arc::new(DummyNetWaker));
59 Self {
60 rt: Handle::current(),
61 client,
62 waker,
63 }
64 }
65 pub fn shared(waker: Option<Arc<dyn NetWaker>>) -> Arc<dyn NetProvider> {
66 Arc::new(Self::new(waker))
67 }
68 pub fn is_empty(&self) -> bool {
69 Arc::strong_count(&self.waker) == 1
70 }
71 pub fn count(&self) -> usize {
72 Arc::strong_count(&self.waker) - 1
73 }
74}
75impl Provider {
76 async fn fetch_inner(
77 client: Client,
78 request: Request,
79 ) -> Result<(String, Bytes), ProviderError> {
80 Ok(match request.url.scheme() {
81 "data" => {
82 let data_url = DataUrl::process(request.url.as_str())?;
83 let decoded = data_url.decode_to_vec()?;
84 (request.url.to_string(), Bytes::from(decoded.0))
85 }
86 "file" => {
87 let file_content = std::fs::read(request.url.path())?;
88 (request.url.to_string(), Bytes::from(file_content))
89 }
90 _ => {
91 let response = client
92 .request(request.method, request.url)
93 .headers(request.headers)
94 .header("Content-Type", request.content_type.as_str())
95 .header("User-Agent", USER_AGENT)
96 .apply_body(request.body, request.content_type.as_str())
97 .await
98 .send()
99 .await?;
100
101 (response.url().to_string(), response.bytes().await?)
102 }
103 })
104 }
105
106 #[allow(clippy::type_complexity)]
107 pub fn fetch_with_callback(
108 &self,
109 request: Request,
110 callback: Box<dyn FnOnce(Result<(String, Bytes), ProviderError>) + Send + Sync + 'static>,
111 ) {
112 #[cfg(feature = "debug_log")]
113 let url = request.url.to_string();
114
115 let client = self.client.clone();
116 self.rt.spawn(async move {
117 let result = Self::fetch_inner(client, request).await;
118
119 #[cfg(feature = "debug_log")]
120 if let Err(e) = &result {
121 eprintln!("Error fetching {url}: {e:?}");
122 } else {
123 println!("Success {url}");
124 }
125
126 callback(result);
127 });
128 }
129
130 pub async fn fetch_async(&self, request: Request) -> Result<(String, Bytes), ProviderError> {
131 #[cfg(feature = "debug_log")]
132 let url = request.url.to_string();
133
134 let client = self.client.clone();
135 let result = Self::fetch_inner(client, request).await;
136
137 #[cfg(feature = "debug_log")]
138 if let Err(e) = &result {
139 eprintln!("Error fetching {url}: {e:?}");
140 } else {
141 println!("Success {url}");
142 }
143
144 result
145 }
146}
147
148impl NetProvider for Provider {
149 fn fetch(&self, doc_id: usize, mut request: Request, handler: Box<dyn NetHandler>) {
150 let client = self.client.clone();
151
152 #[cfg(feature = "debug_log")]
153 println!("Fetching {}", &request.url);
154
155 let waker = self.waker.clone();
156 self.rt.spawn(async move {
157 #[cfg(feature = "debug_log")]
158 let url = request.url.to_string();
159
160 let signal = request.signal.take();
161 let result = if let Some(signal) = signal {
162 AbortFetch::new(
163 signal,
164 Box::pin(async move { Self::fetch_inner(client, request).await }),
165 )
166 .await
167 } else {
168 Self::fetch_inner(client, request).await
169 };
170
171 waker.wake(doc_id);
173
174 match result {
175 Ok((response_url, bytes)) => {
176 handler.bytes(response_url, bytes);
177 #[cfg(feature = "debug_log")]
178 println!("Success {url}");
179 }
180 Err(e) => {
181 #[cfg(feature = "debug_log")]
182 eprintln!("Error fetching {url}: {e:?}");
183 #[cfg(not(feature = "debug_log"))]
184 let _ = e;
185 }
186 };
187 });
188 }
189}
190
191struct AbortFetch<F, T> {
193 signal: AbortSignal,
194 future: F,
195 _rt: PhantomData<T>,
196}
197
198impl<F, T> AbortFetch<F, T> {
199 fn new(signal: AbortSignal, future: F) -> Self {
200 Self {
201 signal,
202 future,
203 _rt: PhantomData,
204 }
205 }
206}
207
208impl<F, T> Future for AbortFetch<F, T>
209where
210 F: Future + Unpin + Send + 'static,
211 F::Output: Send + Into<Result<T, ProviderError>> + 'static,
212 T: Unpin,
213{
214 type Output = Result<T, ProviderError>;
215
216 fn poll(
217 mut self: std::pin::Pin<&mut Self>,
218 cx: &mut std::task::Context<'_>,
219 ) -> std::task::Poll<Self::Output> {
220 if self.signal.aborted() {
221 return Poll::Ready(Err(ProviderError::Abort));
222 }
223
224 match Pin::new(&mut self.future).poll(cx) {
225 Poll::Ready(output) => Poll::Ready(output.into()),
226 Poll::Pending => Poll::Pending,
227 }
228 }
229}
230
231#[derive(Debug)]
232pub enum ProviderError {
233 Abort,
234 Io(std::io::Error),
235 DataUrl(data_url::DataUrlError),
236 DataUrlBase64(data_url::forgiving_base64::InvalidBase64),
237 ReqwestError(reqwest::Error),
238 #[cfg(feature = "cache")]
239 ReqwestMiddlewareError(reqwest_middleware::Error),
240}
241
242impl From<std::io::Error> for ProviderError {
243 fn from(value: std::io::Error) -> Self {
244 Self::Io(value)
245 }
246}
247
248impl From<data_url::DataUrlError> for ProviderError {
249 fn from(value: data_url::DataUrlError) -> Self {
250 Self::DataUrl(value)
251 }
252}
253
254impl From<data_url::forgiving_base64::InvalidBase64> for ProviderError {
255 fn from(value: data_url::forgiving_base64::InvalidBase64) -> Self {
256 Self::DataUrlBase64(value)
257 }
258}
259
260impl From<reqwest::Error> for ProviderError {
261 fn from(value: reqwest::Error) -> Self {
262 Self::ReqwestError(value)
263 }
264}
265
266#[cfg(feature = "cache")]
267impl From<reqwest_middleware::Error> for ProviderError {
268 fn from(value: reqwest_middleware::Error) -> Self {
269 Self::ReqwestMiddlewareError(value)
270 }
271}
272
273trait ReqwestExt {
274 async fn apply_body(self, body: Body, content_type: &str) -> Self;
275}
276impl ReqwestExt for RequestBuilder {
277 async fn apply_body(self, body: Body, content_type: &str) -> Self {
278 match body {
279 Body::Bytes(bytes) => self.body(bytes),
280 Body::Form(form_data) => match content_type {
281 "application/x-www-form-urlencoded" => self.form(&form_data),
282 #[cfg(feature = "multipart")]
283 "multipart/form-data" => {
284 use bliss_traits::net::Entry;
285 use bliss_traits::net::EntryValue;
286 let mut form_data = form_data;
287 let mut form = reqwest::multipart::Form::new();
288 for Entry { name, value } in form_data.0.drain(..) {
289 form = match value {
290 EntryValue::String(value) => form.text(name, value),
291 EntryValue::File(path_buf) => form
292 .file(name, path_buf)
293 .await
294 .expect("Couldn't read form file from disk"),
295 EntryValue::EmptyFile => form.part(
296 name,
297 reqwest::multipart::Part::bytes(&[])
298 .mime_str("application/octet-stream")
299 .unwrap(),
300 ),
301 };
302 }
303 self.multipart(form)
304 }
305 _ => self,
306 },
307 Body::Empty => self,
308 }
309 }
310}
311
312struct DummyNetWaker;
313impl NetWaker for DummyNetWaker {
314 fn wake(&self, _client_id: usize) {}
315}