use proj_sys::{proj_context_set_network_callbacks, PJ_CONTEXT, PROJ_NETWORK_HANDLE};
use reqwest::blocking::{Client, RequestBuilder, Response};
use reqwest::Method;
use std::ffi::CString;
use std::os::raw::c_ulonglong;
use std::ptr;
use crate::proj::{ProjError, _string};
use libc::c_char;
use libc::c_void;
use std::boxed::Box;
use std::{thread, time};
const CLIENT: &str = concat!("proj-rs/", env!("CARGO_PKG_VERSION"));
const MAX_RETRIES: u8 = 8;
const RETRY_CODES: [u16; 4] = [429, 500, 502, 504];
struct HandleData {
request: reqwest::blocking::RequestBuilder,
headers: reqwest::header::HeaderMap,
hptr: Option<*const c_char>,
}
impl HandleData {
fn new(
request: reqwest::blocking::RequestBuilder,
headers: reqwest::header::HeaderMap,
hptr: Option<*const c_char>,
) -> Self {
Self {
request,
headers,
hptr,
}
}
}
fn get_wait_time_exp(retrycount: i32) -> u64 {
if retrycount == 0 {
return 0;
}
(retrycount as u64).pow(2) * 100u64
}
fn error_handler<'a>(res: &'a mut Response, rb: RequestBuilder) -> Result<&'a Response, ProjError> {
let mut status = res.status().as_u16();
let mut retries = 0;
if res.status().is_server_error() || RETRY_CODES.contains(&status) {
while (res.status().is_server_error() || RETRY_CODES.contains(&status))
&& retries <= MAX_RETRIES
{
retries += 1;
let wait = time::Duration::from_millis(get_wait_time_exp(retries as i32));
thread::sleep(wait);
let retry = rb.try_clone().ok_or(ProjError::RequestCloneError)?;
let with_range = retry.header("Client", CLIENT);
*res = with_range.send()?;
status = res.status().as_u16();
}
} else if res.status().is_client_error() {
return Err(ProjError::DownloadError(
res.status().as_str().to_string(),
res.url().to_string(),
retries,
));
}
if !res.status().is_success() {
return Err(ProjError::DownloadError(
res.status().as_str().to_string(),
res.url().to_string(),
retries,
));
}
Ok(res)
}
pub(crate) unsafe extern "C" fn network_open(
pc: *mut PJ_CONTEXT,
url: *const c_char,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
out_size_read: *mut usize,
error_string_max_size: usize,
out_error_string: *mut c_char,
ud: *mut c_void,
) -> *mut PROJ_NETWORK_HANDLE {
match _network_open(
pc,
url,
offset,
size_to_read,
buffer,
out_size_read,
error_string_max_size,
out_error_string,
ud,
) {
Ok(res) => res,
Err(e) => {
let err_string = e.to_string();
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
ptr::null_mut() as *mut PROJ_NETWORK_HANDLE
}
}
}
unsafe fn _network_open(
_: *mut PJ_CONTEXT,
url: *const c_char,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
out_size_read: *mut usize,
_: usize,
out_error_string: *mut c_char,
_: *mut c_void,
) -> Result<*mut PROJ_NETWORK_HANDLE, ProjError> {
let url = _string(url)?;
let end = offset as usize + size_to_read - 1;
let hvalue = format!("bytes={}-{}", offset, end);
let clt = Client::builder().build()?;
let req = clt.request(Method::GET, &url);
let initial = req.try_clone().ok_or(ProjError::RequestCloneError)?;
let with_headers = initial.header("Range", &hvalue).header("Client", CLIENT);
let mut res = with_headers.send()?;
let eh_rb = req
.try_clone()
.ok_or(ProjError::RequestCloneError)?
.header("Range", &hvalue);
error_handler(&mut res, eh_rb)?;
let contentlength = res.content_length().ok_or(ProjError::ContentLength)? as usize;
out_size_read.write(contentlength);
let headers = res.headers().clone();
&res.bytes()?
.as_ptr()
.copy_to_nonoverlapping(buffer as *mut u8, contentlength.min(size_to_read));
let hd = HandleData::new(req, headers, None);
let hd_boxed = Box::new(hd);
let void: *mut c_void = Box::into_raw(hd_boxed) as *mut c_void;
let opaque: *mut PROJ_NETWORK_HANDLE = void as *mut PROJ_NETWORK_HANDLE;
let err_string = "";
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
Ok(opaque)
}
pub(crate) unsafe extern "C" fn network_close(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
_: *mut c_void,
) {
let hd = &*(handle as *const c_void as *mut HandleData);
if let Some(header) = hd.hptr {
let _ = CString::from_raw(header as *mut i8);
}
let _ = *hd;
}
pub(crate) unsafe extern "C" fn network_get_header_value(
pc: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
header_name: *const c_char,
ud: *mut c_void,
) -> *const c_char {
let mut hd = &mut *(handle as *const c_void as *mut HandleData);
match _network_get_header_value(pc, handle, header_name, ud) {
Ok(res) => res,
Err(_) => {
let hvalue = "";
let cstr = CString::new(hvalue).unwrap();
let err = cstr.into_raw();
hd.hptr = Some(err);
err
}
}
}
unsafe fn _network_get_header_value(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
header_name: *const c_char,
_: *mut c_void,
) -> Result<*const c_char, ProjError> {
let lookup = _string(header_name)?.to_lowercase();
let mut hd = &mut *(handle as *mut c_void as *mut HandleData);
let hvalue = hd
.headers
.get(&lookup)
.ok_or_else(|| ProjError::HeaderError(lookup.to_string()))?
.to_str()?;
let cstr = CString::new(hvalue).unwrap();
let header = cstr.into_raw();
hd.hptr = Some(header);
Ok(header)
}
pub(crate) unsafe extern "C" fn network_read_range(
pc: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
error_string_max_size: usize,
out_error_string: *mut c_char,
ud: *mut c_void,
) -> usize {
match _network_read_range(
pc,
handle,
offset,
size_to_read,
buffer,
error_string_max_size,
out_error_string,
ud,
) {
Ok(res) => res,
Err(e) => {
let err_string = e.to_string().replace("0", "nought");
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
0usize
}
}
}
fn _network_read_range(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
_: usize,
out_error_string: *mut c_char,
_: *mut c_void,
) -> Result<usize, ProjError> {
let end = offset as usize + size_to_read - 1;
let hvalue = format!("bytes={}-{}", offset, end);
let mut hd = unsafe { &mut *(handle as *const c_void as *mut HandleData) };
let initial = hd.request.try_clone().ok_or(ProjError::RequestCloneError)?;
let with_headers = initial.header("Range", &hvalue).header("Client", CLIENT);
let mut res = with_headers.send()?;
let eh_rb = hd
.request
.try_clone()
.ok_or(ProjError::RequestCloneError)?
.header("Range", &hvalue);
error_handler(&mut res, eh_rb)?;
let headers = res.headers().clone();
let contentlength = res.content_length().ok_or(ProjError::ContentLength)? as usize;
unsafe {
res.bytes()?
.as_ptr()
.copy_to_nonoverlapping(buffer as *mut u8, contentlength.min(size_to_read));
}
let err_string = "";
unsafe {
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
}
hd.headers = headers;
Ok(contentlength)
}
pub(crate) fn set_network_callbacks(ctx: *mut PJ_CONTEXT) -> i32 {
let ud: *mut c_void = ptr::null_mut();
unsafe {
proj_context_set_network_callbacks(
ctx,
Some(network_open),
Some(network_close),
Some(network_get_header_value),
Some(network_read_range),
ud,
)
}
}