use wasm_bindgen::
{
prelude::*,
JsCast,
};
use web_sys::
{
XmlHttpRequest,
XmlHttpRequestResponseType,
ProgressEvent,
};
use js_sys::
{
ArrayBuffer,
Uint8Array,
};
use std::
{
cell::{RefCell, Cell},
rc::Rc,
};
use crate::{clone, borrow_mut};
use crate::log;
type RequestClosure = Closure<dyn FnMut(ProgressEvent)>;
pub type RequestHandle = usize;
pub struct HttpRequest
{
internal: XmlHttpRequest,
method: String,
url: String,
onabort: Option<RequestClosure>,
onerror: Option<RequestClosure>,
onload: Option<RequestClosure>,
onloadstart: Option<RequestClosure>,
onloadend: Option<RequestClosure>,
onprogress: Option<RequestClosure>,
}
impl HttpRequest
{
fn new(method: String, url: String) -> Result<HttpRequest, JsValue>
{
Ok(HttpRequest
{
internal: XmlHttpRequest::new()?,
method, url,
onabort: None,
onerror: None,
onload: None,
onloadstart: None,
onloadend: None,
onprogress: None
})
}
}
pub struct CallbackArgs(pub ProgressEvent, pub XmlHttpRequest);
pub struct OnloadCallbackArgs(pub CallbackArgs, pub Vec<u8>);
pub struct ResourceLoader
{
requests_left: Cell<i32>,
work_total: Cell<f64>,
work_loaded: Cell<f64>,
global_onloadend: Cell<Option<Box<dyn FnOnce()>>>,
global_onprogress: Option<Box<dyn FnMut(f64, f64)>>,
http_requests: Vec<HttpRequest>,
}
impl ResourceLoader
{
pub fn new() -> Self
{
ResourceLoader
{
requests_left: Cell::new(0),
work_total: Cell::new(0.0),
work_loaded: Cell::new(0.0),
global_onloadend: Cell::new(None),
global_onprogress: None,
http_requests: vec![],
}
}
#[allow(dead_code)]
pub fn set_onloadend<F>(&self, callback: F) where F: 'static + FnOnce()
{
self.global_onloadend.set(Some(Box::new(callback)));
}
#[allow(dead_code)]
pub fn set_onprogress<F>(&mut self, callback: F) where F: 'static + FnMut(f64, f64)
{
self.global_onprogress = Some(Box::new(callback));
}
#[allow(dead_code)]
pub fn add_request(&mut self, method: impl Into<String>, url: impl Into<String>) -> Result<RequestHandle, JsValue>
{
let request = HttpRequest::new(method.into(), url.into())?;
request.internal.set_response_type(XmlHttpRequestResponseType::Arraybuffer);
self.http_requests.push(request);
self.requests_left.set(self.requests_left.get() + 1);
Ok(self.http_requests.len()-1)
}
#[allow(dead_code)]
pub fn set_request_onabort<F>(&mut self, handle: RequestHandle, onabort: F)
-> bool where F: 'static + FnOnce(CallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::once(move |event: ProgressEvent|
{
onabort(CallbackArgs(event, internal));
});
http_request.onabort = Some(closure);
http_request.internal.set_onabort(Some(http_request.onabort.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn set_request_onerror<F>(&mut self, handle: RequestHandle, onerror: F)
-> bool where F: 'static + FnOnce(CallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::once(move |event: ProgressEvent|
{
onerror(CallbackArgs(event, internal));
});
http_request.onerror = Some(closure);
http_request.internal.set_onerror(Some(http_request.onerror.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn set_request_onload<F>(&mut self, handle: RequestHandle, onload: F)
-> bool where F: 'static + FnOnce(OnloadCallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::once(move |event: ProgressEvent|
{
let mut response_vec: Vec<u8> = vec![];
match internal.response()
{
Ok(response) =>
{
let buffer: ArrayBuffer = response.into();
let byte_arr = Uint8Array::new(&buffer);
response_vec = byte_arr.to_vec();
},
Err(err) =>
{
log!("Error getting request response: {:?}", err);
}
}
onload(OnloadCallbackArgs(CallbackArgs(event, internal), response_vec));
});
http_request.onload = Some(closure);
http_request.internal.set_onload(Some(http_request.onload.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn set_request_onloadstart<F>(&mut self, handle: RequestHandle, onloadstart: F)
-> bool where F: 'static + FnOnce(CallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::once(move |event: ProgressEvent|
{
onloadstart(CallbackArgs(event, internal));
});
http_request.onloadstart = Some(closure);
http_request.internal.set_onloadstart(Some(http_request.onloadstart.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn set_request_onloadend<F>(&mut self, handle: RequestHandle, onloadend: F)
-> bool where F: 'static + FnOnce(CallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::once(move |event: ProgressEvent|
{
onloadend(CallbackArgs(event, internal));
});
http_request.onloadend = Some(closure);
http_request.internal.set_onloadend(Some(http_request.onloadend.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn set_request_onprogress<F>(&mut self, handle: RequestHandle, mut onprogress: F)
-> bool where F: 'static + FnMut(CallbackArgs)
{
if let Some(http_request) = self.http_requests.get_mut(handle)
{
clone!(http_request.internal);
let closure = Closure::wrap(Box::new(move |event: ProgressEvent|
{
onprogress(CallbackArgs(event, internal.clone()));
}) as Box<dyn FnMut(_)>);
http_request.onprogress = Some(closure);
http_request.internal.set_onprogress(Some(http_request.onprogress.as_ref().unwrap().as_ref().unchecked_ref()));
true
}
else { false }
}
#[allow(dead_code)]
pub fn submit(self)
{
let mut loader = self;
let mut http_requests = vec![];
std::mem::swap(&mut loader.http_requests, &mut http_requests);
let loader = Rc::new(RefCell::new(loader));
for http_request in &mut http_requests
{
let closure =
{
let onloadend = http_request.onloadend.take();
clone!(loader);
Closure::once(move |event: ProgressEvent|
{
if let Some(onloadend) = onloadend
{
let onloadend: js_sys::Function = onloadend.into_js_value().into();
if let Err(err) = onloadend.call1(&JsValue::null(), &event)
{
log!("Error executing request onloadend func: {:?}", err);
}
}
let loader_borrow = loader.borrow();
let left = loader_borrow.requests_left.get();
loader_borrow.requests_left.set(left - 1);
if left <= 1
{
if let Some(global_onloadend) = loader_borrow.global_onloadend.take()
{
global_onloadend();
}
}
})
};
http_request.onloadend = Some(closure);
http_request.internal.set_onloadend(Some(http_request.onloadend.as_ref().unwrap().as_ref().unchecked_ref()));
let closure =
{
let onprogress = http_request.onprogress.take();
let onprogress: Option<js_sys::Function> = if let Some(closure) = onprogress
{
Some(closure.into_js_value().into())
}
else { None };
let onprogress = Rc::new(RefCell::new(onprogress));
let loader = Rc::downgrade(&loader);
let mut set_total = false;
clone!(onprogress);
Closure::wrap(Box::new(move |event: ProgressEvent|
{
if let Some(onprogress) = onprogress.borrow_mut().as_mut()
{
if let Err(err) = onprogress.call1(&JsValue::null(), &event)
{
log!("Error executing request onprogress func: {:?}", err);
}
}
if event.length_computable()
{
if let Some(loader) = loader.upgrade()
{
borrow_mut!(loader);
if !set_total
{
loader.work_total.set(loader.work_total.get() + event.total());
set_total = true;
}
let loaded = loader.work_loaded.get() + event.loaded();
loader.work_loaded.set(loaded);
let work_total = loader.work_total.get();
if let Some(global_onprogress) = loader.global_onprogress.as_mut()
{
global_onprogress(loaded, work_total);
}
}
}
}) as Box<dyn FnMut(_)>)
};
http_request.onprogress = Some(closure);
http_request.internal.set_onprogress(Some(http_request.onprogress.as_ref().unwrap().as_ref().unchecked_ref()));
http_request.internal.open(&http_request.method, &http_request.url).expect("request opened");
http_request.internal.send().expect("request sent");
}
loader.borrow_mut().http_requests = http_requests;
}
}