129 lines
3.7 KiB
Rust
129 lines
3.7 KiB
Rust
use egui::{
|
|
ahash::HashMap,
|
|
load::{Bytes, BytesLoadResult, BytesLoader, BytesPoll, LoadError},
|
|
mutex::Mutex,
|
|
};
|
|
use std::{sync::Arc, task::Poll};
|
|
|
|
#[derive(Clone)]
|
|
struct File {
|
|
bytes: Arc<[u8]>,
|
|
mime: Option<String>,
|
|
}
|
|
|
|
impl File {
|
|
fn from_response(uri: &str, response: ehttp::Response) -> Result<Self, String> {
|
|
if !response.ok {
|
|
match response.text() {
|
|
Some(response_text) => {
|
|
return Err(format!(
|
|
"failed to load {uri:?}: {} {} {response_text}",
|
|
response.status, response.status_text
|
|
))
|
|
}
|
|
None => {
|
|
return Err(format!(
|
|
"failed to load {uri:?}: {} {}",
|
|
response.status, response.status_text
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
let mime = response.content_type().map(|v| v.to_owned());
|
|
let bytes = response.bytes.into();
|
|
|
|
Ok(Self { bytes, mime })
|
|
}
|
|
}
|
|
|
|
type Entry = Poll<Result<File, String>>;
|
|
|
|
#[derive(Default)]
|
|
pub struct EhttpLoader {
|
|
cache: Arc<Mutex<HashMap<String, Entry>>>,
|
|
}
|
|
|
|
impl EhttpLoader {
|
|
pub const ID: &'static str = egui::generate_loader_id!(EhttpLoader);
|
|
}
|
|
|
|
const PROTOCOLS: &[&str] = &["http://", "https://"];
|
|
|
|
fn starts_with_one_of(s: &str, prefixes: &[&str]) -> bool {
|
|
prefixes.iter().any(|prefix| s.starts_with(prefix))
|
|
}
|
|
|
|
impl BytesLoader for EhttpLoader {
|
|
fn id(&self) -> &str {
|
|
Self::ID
|
|
}
|
|
|
|
fn load(&self, ctx: &egui::Context, uri: &str) -> BytesLoadResult {
|
|
if !starts_with_one_of(uri, PROTOCOLS) {
|
|
return Err(LoadError::NotSupported);
|
|
}
|
|
|
|
let mut cache = self.cache.lock();
|
|
if let Some(entry) = cache.get(uri).cloned() {
|
|
match entry {
|
|
Poll::Ready(Ok(file)) => Ok(BytesPoll::Ready {
|
|
size: None,
|
|
bytes: Bytes::Shared(file.bytes),
|
|
mime: file.mime,
|
|
}),
|
|
Poll::Ready(Err(err)) => Err(LoadError::Loading(err)),
|
|
Poll::Pending => Ok(BytesPoll::Pending { size: None }),
|
|
}
|
|
} else {
|
|
log::trace!("started loading {uri:?}");
|
|
|
|
let uri = uri.to_owned();
|
|
cache.insert(uri.clone(), Poll::Pending);
|
|
drop(cache);
|
|
|
|
ehttp::fetch(ehttp::Request::get(uri.clone()), {
|
|
let ctx = ctx.clone();
|
|
let cache = self.cache.clone();
|
|
move |response| {
|
|
let result = match response {
|
|
Ok(response) => File::from_response(&uri, response),
|
|
Err(err) => {
|
|
// Log details; return summary
|
|
log::error!("Failed to load {uri:?}: {err}");
|
|
Err(format!("Failed to load {uri:?}"))
|
|
}
|
|
};
|
|
log::trace!("finished loading {uri:?}");
|
|
cache.lock().insert(uri, Poll::Ready(result));
|
|
ctx.request_repaint();
|
|
}
|
|
});
|
|
|
|
Ok(BytesPoll::Pending { size: None })
|
|
}
|
|
}
|
|
|
|
fn forget(&self, uri: &str) {
|
|
let _ = self.cache.lock().remove(uri);
|
|
}
|
|
|
|
fn forget_all(&self) {
|
|
self.cache.lock().clear();
|
|
}
|
|
|
|
fn byte_size(&self) -> usize {
|
|
self.cache
|
|
.lock()
|
|
.values()
|
|
.map(|entry| match entry {
|
|
Poll::Ready(Ok(file)) => {
|
|
file.bytes.len() + file.mime.as_ref().map_or(0, |m| m.len())
|
|
}
|
|
Poll::Ready(Err(err)) => err.len(),
|
|
_ => 0,
|
|
})
|
|
.sum()
|
|
}
|
|
}
|