use nightshade::prelude::ehttp;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub struct PendingGltfMulti {
pub display_name: String,
pub gltf_bytes: Vec<u8>,
pub resources: HashMap<String, Vec<u8>>,
}
struct DownloadProgress {
gltf_bytes: Option<Vec<u8>>,
resources: HashMap<String, Vec<u8>>,
expected_resources: usize,
failed: bool,
}
pub fn start_multi_fetch(
gltf_url: String,
resources: Vec<(String, String)>,
display_name: String,
pending: Arc<Mutex<Option<PendingGltfMulti>>>,
loading: Arc<Mutex<Option<String>>>,
) {
let progress = Arc::new(Mutex::new(DownloadProgress {
gltf_bytes: None,
resources: HashMap::new(),
expected_resources: resources.len(),
failed: false,
}));
{
let progress = Arc::clone(&progress);
let pending = Arc::clone(&pending);
let loading = Arc::clone(&loading);
let display = display_name.clone();
ehttp::fetch(
ehttp::Request::get(&gltf_url),
move |result: ehttp::Result<ehttp::Response>| {
let bytes = result.ok().filter(|r| r.ok).map(|r| r.bytes);
let mut prog = progress.lock().unwrap();
match bytes {
Some(b) => prog.gltf_bytes = Some(b),
None => prog.failed = true,
}
try_finalize(&mut prog, &pending, &loading, display.clone());
},
);
}
for (key, url) in resources {
let progress = Arc::clone(&progress);
let pending = Arc::clone(&pending);
let loading = Arc::clone(&loading);
let display = display_name.clone();
ehttp::fetch(
ehttp::Request::get(&url),
move |result: ehttp::Result<ehttp::Response>| {
let bytes = result.ok().filter(|r| r.ok).map(|r| r.bytes);
let mut prog = progress.lock().unwrap();
match bytes {
Some(b) => {
prog.resources.insert(key.clone(), b);
}
None => prog.failed = true,
}
try_finalize(&mut prog, &pending, &loading, display.clone());
},
);
}
}
fn try_finalize(
progress: &mut DownloadProgress,
pending: &Arc<Mutex<Option<PendingGltfMulti>>>,
loading: &Arc<Mutex<Option<String>>>,
display_name: String,
) {
if progress.failed {
if let Ok(mut state) = loading.lock() {
*state = None;
}
return;
}
if progress.gltf_bytes.is_none() {
return;
}
if progress.resources.len() < progress.expected_resources {
return;
}
let gltf_bytes = progress.gltf_bytes.take().unwrap();
let resources = std::mem::take(&mut progress.resources);
if let Ok(mut slot) = pending.lock() {
*slot = Some(PendingGltfMulti {
display_name,
gltf_bytes,
resources,
});
}
if let Ok(mut state) = loading.lock() {
*state = None;
}
}
pub fn percent_decode(input: &str) -> String {
let bytes = input.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut index = 0;
while index < bytes.len() {
if bytes[index] == b'%'
&& index + 2 < bytes.len()
&& let (Some(high), Some(low)) =
(hex_value(bytes[index + 1]), hex_value(bytes[index + 2]))
{
out.push((high << 4) | low);
index += 3;
continue;
}
out.push(bytes[index]);
index += 1;
}
String::from_utf8(out).unwrap_or_else(|_| input.to_string())
}
fn hex_value(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
pub fn external_uris_from_gltf(gltf_bytes: &[u8]) -> Vec<String> {
let Ok(root) = nightshade::prelude::serde_json::from_slice::<
nightshade::prelude::serde_json::Value,
>(gltf_bytes) else {
return Vec::new();
};
let mut uris = Vec::new();
for section_key in ["buffers", "images"] {
let Some(array) = root.get(section_key).and_then(|v| v.as_array()) else {
continue;
};
for item in array {
if let Some(uri) = item.get("uri").and_then(|v| v.as_str())
&& !uri.starts_with("data:")
{
uris.push(uri.to_string());
}
}
}
uris
}