use crate::sys;
use std::{
ffi::{self, CStr, CString},
ptr, slice,
};
type NetworkCallback = Box<dyn FnOnce(NetworkRequest, i32)>;
pub struct NetworkRequestBuilder<'a> {
url: CString,
headers: Vec<CString>,
data: Option<&'a mut [u8]>,
callback: Option<Box<NetworkCallback>>,
}
impl<'a> NetworkRequestBuilder<'a> {
pub fn new(url: &str) -> Option<Self> {
Some(Self {
url: CString::new(url).ok()?,
headers: vec![],
data: None,
callback: None,
})
}
pub fn with_header(mut self, header: &str) -> Option<Self> {
self.headers.push(CString::new(header).ok()?);
Some(self)
}
pub fn with_data(mut self, data: &'a mut [u8]) -> Self {
self.data = Some(data);
self
}
pub fn with_callback(mut self, callback: impl FnOnce(NetworkRequest, i32) + 'static) -> Self {
self.callback = Some(Box::new(Box::new(callback)));
self
}
pub fn get(self) -> Option<NetworkRequest> {
self.do_request(None, sys::fsNetworkHttpRequestGet)
}
pub fn post(self, post_field: &str) -> Option<NetworkRequest> {
let post_field = CString::new(post_field).unwrap();
self.do_request(Some(post_field), sys::fsNetworkHttpRequestPost)
}
pub fn put(self) -> Option<NetworkRequest> {
self.do_request(None, sys::fsNetworkHttpRequestPut)
}
fn do_request(
mut self,
post_field: Option<CString>,
request: unsafe extern "C" fn(
*const ::std::os::raw::c_char,
*mut sys::FsNetworkHttpRequestParam,
sys::HttpRequestCallback,
*mut ::std::os::raw::c_void,
) -> sys::FsNetworkRequestId,
) -> Option<NetworkRequest> {
let raw_post_field =
post_field.map_or(ptr::null_mut(), |f| f.as_c_str().as_ptr() as *mut i8);
let mut headers = self
.headers
.iter_mut()
.map(|h| h.as_ptr() as *mut i8)
.collect::<Vec<_>>();
let data_len = self.data.as_ref().map_or(0, |d| d.len());
let mut params = sys::FsNetworkHttpRequestParam {
postField: raw_post_field,
headerOptions: headers.as_mut_ptr(),
headerOptionsSize: headers.len() as std::os::raw::c_uint,
data: self
.data
.as_mut()
.map_or(ptr::null_mut(), |d| d.as_mut_ptr()),
dataSize: data_len as std::os::raw::c_uint,
};
let callback_data = self.callback.map_or(ptr::null_mut(), Box::into_raw) as *mut _;
let request_id = unsafe {
request(
self.url.as_ptr(),
&mut params as *mut sys::FsNetworkHttpRequestParam,
Some(Self::c_wrapper),
callback_data,
)
};
if request_id == 0 {
let _: Box<NetworkCallback> = unsafe { Box::from_raw(callback_data as *mut _) };
None
} else {
Some(NetworkRequest(request_id))
}
}
extern "C" fn c_wrapper(
request_id: sys::FsNetworkRequestId,
status_code: i32,
user_data: *mut ffi::c_void,
) {
if !user_data.is_null() {
let callback: Box<NetworkCallback> = unsafe { Box::from_raw(user_data as *mut _) };
callback(NetworkRequest(request_id), status_code);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NetworkRequestState {
Invalid,
New,
WaitingForData,
DataReady,
Failed,
}
impl From<sys::FsNetworkHttpRequestState> for NetworkRequestState {
fn from(value: sys::FsNetworkHttpRequestState) -> Self {
match value {
sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_INVALID => Self::Invalid,
sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_NEW => Self::New,
sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_WAITING_FOR_DATA => {
Self::WaitingForData
}
sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_DATA_READY => {
Self::DataReady
}
sys::FsNetworkHttpRequestState_FS_NETWORK_HTTP_REQUEST_STATE_FAILED => Self::Failed,
_ => panic!("Unknown request state"),
}
}
}
#[derive(Clone, Copy)]
pub struct NetworkRequest(sys::FsNetworkRequestId);
impl NetworkRequest {
pub fn cancel(&self) -> bool {
unsafe { sys::fsNetworkHttpCancelRequest(self.0) }
}
pub fn data_size(&self) -> usize {
unsafe { sys::fsNetworkHttpRequestGetDataSize(self.0) as usize }
}
pub fn data(&self) -> Option<Vec<u8>> {
let data_size = self.data_size();
if data_size == 0 {
return None;
}
unsafe {
let data = sys::fsNetworkHttpRequestGetData(self.0);
if data.is_null() {
None
} else {
let result = slice::from_raw_parts(data, data_size).to_owned();
libc::free(data as *mut ffi::c_void);
Some(result)
}
}
}
pub fn error_code(&self) -> i32 {
unsafe { sys::fsNetworkHttpRequestGetErrorCode(self.0) }
}
pub fn state(&self) -> NetworkRequestState {
unsafe { sys::fsNetworkHttpRequestGetState(self.0).into() }
}
pub fn header_section(&self, section: &str) -> Option<String> {
let section = CString::new(section).ok()?;
unsafe {
let a = sys::fsNetworkHttpRequestGetHeaderSection(self.0, section.as_ptr());
if a.is_null() {
None
} else {
let result = CStr::from_ptr(a).to_str().ok().map(|s| s.to_owned());
libc::free(a as *mut ffi::c_void);
result
}
}
}
}