use super::legacy::{LegacyOperation, legacy};
use super::{Completion, c_string, completion, copy_bytes, failed, loaded_bytes, receive};
use emscripten_functions_sys::emscripten as ffi;
use futures::channel::oneshot;
use std::{
ffi::{CStr, CString, c_char, c_void},
io,
};
pub async fn fetch(url: &str) -> io::Result<Vec<u8>> {
let url = c_string(url)?;
let (arg, receiver) = completion::<Vec<u8>>();
unsafe {
ffi::emscripten_async_wget_data(
url.as_ptr(),
arg,
Some(loaded_bytes),
Some(failed::<Vec<u8>>),
)
};
receive(receiver).await
}
pub async fn wget(url: &str, file: &str) -> io::Result<()> {
legacy(LegacyOperation::Download(c_string(url)?, c_string(file)?)).await
}
struct DownloadGuard<T> {
handle: i32,
sender: Box<std::sync::Mutex<Option<Completion<T>>>>,
_local: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl<T> Drop for DownloadGuard<T> {
fn drop(&mut self) {
unsafe { ffi::emscripten_async_wget2_abort(self.handle) };
}
}
unsafe fn download_complete<T>(arg: *mut c_void, result: io::Result<T>) {
let sender = unsafe { &*arg.cast::<std::sync::Mutex<Option<Completion<T>>>>() }
.lock()
.unwrap()
.take();
if let Some(sender) = sender {
let _ = sender.send(result);
}
}
unsafe extern "C" fn download_bytes(_: u32, arg: *mut c_void, data: *mut c_void, len: u32) {
let result = unsafe { copy_bytes(data, len as usize) };
unsafe { download_complete(arg, result) };
}
unsafe extern "C" fn download_file(_: u32, arg: *mut c_void, _: *const c_char) {
unsafe { download_complete(arg, Ok(())) };
}
unsafe extern "C" fn download_error<T>(_: u32, arg: *mut c_void, status: i32) {
unsafe {
download_complete::<T>(
arg,
Err(io::Error::other(format!("HTTP request failed: {status}"))),
)
};
}
unsafe extern "C" fn download_data_error(
_: u32,
arg: *mut c_void,
status: i32,
message: *const c_char,
) {
let message = if message.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned()
};
unsafe {
download_complete::<Vec<u8>>(
arg,
Err(io::Error::other(format!(
"HTTP request failed: {status} {message}"
))),
)
};
}
fn download_state<T>() -> (DownloadGuard<T>, oneshot::Receiver<io::Result<T>>) {
let (sender, receiver) = oneshot::channel();
(
DownloadGuard {
handle: -1,
sender: Box::new(std::sync::Mutex::new(Some(sender))),
_local: std::marker::PhantomData,
},
receiver,
)
}
pub async fn wget2_data(url: &str, method: &str, params: &str) -> io::Result<Vec<u8>> {
let url = c_string(url)?;
let method = request_method(method)?;
let params = c_string(params)?;
let (mut request, receiver) = download_state::<Vec<u8>>();
let arg = (&*request.sender as *const std::sync::Mutex<Option<Completion<Vec<u8>>>>)
.cast_mut()
.cast();
request.handle = unsafe {
ffi::emscripten_async_wget2_data(
url.as_ptr(),
method.as_ptr(),
params.as_ptr(),
arg,
1,
Some(download_bytes),
Some(download_data_error),
None,
)
};
let result = receive(receiver).await;
drop(request);
result
}
pub async fn wget2(url: &str, file: &str, method: &str, params: &str) -> io::Result<()> {
let url = c_string(url)?;
let file = c_string(file)?;
let method = request_method(method)?;
let params = c_string(params)?;
let (mut request, receiver) = download_state::<()>();
let arg = (&*request.sender as *const std::sync::Mutex<Option<Completion<()>>>)
.cast_mut()
.cast();
request.handle = unsafe {
ffi::emscripten_async_wget2(
url.as_ptr(),
file.as_ptr(),
method.as_ptr(),
params.as_ptr(),
arg,
Some(download_file),
Some(download_error::<()>),
None,
)
};
let result = receive(receiver).await;
drop(request);
result
}
fn request_method(method: &str) -> io::Result<CString> {
match method {
"GET" | "POST" => c_string(method),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"expected GET or POST",
)),
}
}