use std::ffi::{c_char, c_void, CString};
use std::sync::{LazyLock, Mutex};
use gdal_sys::{CPLErr, CPLErrorNum, CPLGetErrorHandlerUserData};
use crate::errors::{CplErrType, Result};
use crate::utils::_string;
pub fn set_config_option(key: &str, value: &str) -> Result<()> {
let c_key = CString::new(key.as_bytes())?;
let c_val = CString::new(value.as_bytes())?;
unsafe {
gdal_sys::CPLSetConfigOption(c_key.as_ptr(), c_val.as_ptr());
};
Ok(())
}
pub fn get_config_option(key: &str, default: &str) -> Result<String> {
let c_key = CString::new(key.as_bytes())?;
let c_default = CString::new(default.as_bytes())?;
let rv = unsafe { gdal_sys::CPLGetConfigOption(c_key.as_ptr(), c_default.as_ptr()) };
Ok(_string(rv).unwrap_or_else(|| default.to_string()))
}
pub fn clear_config_option(key: &str) -> Result<()> {
let c_key = CString::new(key.as_bytes())?;
unsafe {
gdal_sys::CPLSetConfigOption(c_key.as_ptr(), ::std::ptr::null());
};
Ok(())
}
pub fn set_thread_local_config_option(key: &str, value: &str) -> Result<()> {
let c_key = CString::new(key.as_bytes())?;
let c_val = CString::new(value.as_bytes())?;
unsafe {
gdal_sys::CPLSetThreadLocalConfigOption(c_key.as_ptr(), c_val.as_ptr());
};
Ok(())
}
pub fn get_thread_local_config_option(key: &str, default: &str) -> Result<String> {
let c_key = CString::new(key.as_bytes())?;
let c_default = CString::new(default.as_bytes())?;
let rv = unsafe { gdal_sys::CPLGetThreadLocalConfigOption(c_key.as_ptr(), c_default.as_ptr()) };
Ok(_string(rv).unwrap_or_else(|| default.to_string()))
}
pub fn clear_thread_local_config_option(key: &str) -> Result<()> {
let c_key = CString::new(key.as_bytes())?;
unsafe {
gdal_sys::CPLSetThreadLocalConfigOption(c_key.as_ptr(), ::std::ptr::null());
};
Ok(())
}
type ErrorCallbackType = dyn FnMut(CplErrType, i32, &str) + 'static + Send;
type PinnedErrorCallback = Box<Box<ErrorCallbackType>>;
static ERROR_CALLBACK: LazyLock<Mutex<Option<PinnedErrorCallback>>> =
LazyLock::new(Default::default);
pub fn set_error_handler<F>(callback: F)
where
F: FnMut(CplErrType, i32, &str) + 'static + Send + Sync,
{
unsafe extern "C" fn error_handler(
error_type: CPLErr::Type,
error_num: CPLErrorNum,
error_msg_ptr: *const c_char,
) {
let error_msg = _string(error_msg_ptr).unwrap_or_default();
let error_type: CplErrType = error_type.into();
let callback_raw = CPLGetErrorHandlerUserData();
let callback: &mut Box<ErrorCallbackType> = &mut *(callback_raw as *mut Box<_>);
callback(error_type, error_num, &error_msg);
}
let mut callback: PinnedErrorCallback = Box::new(Box::new(callback));
let callback_ref: &mut Box<ErrorCallbackType> = callback.as_mut();
let mut callback_lock = match ERROR_CALLBACK.lock() {
Ok(guard) => guard,
Err(poison_error) => poison_error.into_inner(),
};
unsafe {
gdal_sys::CPLSetErrorHandlerEx(Some(error_handler), callback_ref as *mut _ as *mut c_void);
};
callback_lock.replace(callback);
}
pub fn remove_error_handler() {
let mut callback_lock = match ERROR_CALLBACK.lock() {
Ok(guard) => guard,
Err(poison_error) => poison_error.into_inner(),
};
unsafe {
gdal_sys::CPLSetErrorHandler(None);
};
callback_lock.take();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_options() {
test_set_get_option();
test_set_option_with_embedded_nul();
test_clear_option();
test_set_get_option_thread_local();
test_set_option_with_embedded_nul_thread_local();
test_clear_option_thread_local();
}
fn test_set_get_option() {
assert!(set_config_option("GDAL_CACHEMAX", "128").is_ok());
assert_eq!(
get_config_option("GDAL_CACHEMAX", "").unwrap_or_else(|_| "".to_string()),
"128"
);
assert_eq!(
get_config_option("NON_EXISTENT_OPTION", "DEFAULT_VALUE")
.unwrap_or_else(|_| "".to_string()),
"DEFAULT_VALUE"
);
}
fn test_set_option_with_embedded_nul() {
assert!(set_config_option("f\0oo", "valid").is_err());
assert!(set_config_option("foo", "in\0valid").is_err());
assert!(set_config_option("xxxf\0oo", "in\0valid").is_err());
}
fn test_clear_option() {
assert!(set_config_option("TEST_OPTION", "256").is_ok());
assert_eq!(
get_config_option("TEST_OPTION", "DEFAULT").unwrap_or_else(|_| "".to_string()),
"256"
);
assert!(clear_config_option("TEST_OPTION").is_ok());
assert_eq!(
get_config_option("TEST_OPTION", "DEFAULT").unwrap_or_else(|_| "".to_string()),
"DEFAULT"
);
}
fn test_set_get_option_thread_local() {
assert!(set_thread_local_config_option("GDAL_CACHEMAX", "128").is_ok());
assert_eq!(
get_thread_local_config_option("GDAL_CACHEMAX", "").unwrap_or_else(|_| "".to_string()),
"128"
);
assert_eq!(
get_config_option("GDAL_CACHEMAX", "").unwrap_or_else(|_| "".to_string()),
"128"
);
assert_eq!(
get_thread_local_config_option("NON_EXISTENT_OPTION", "DEFAULT_VALUE")
.unwrap_or_else(|_| "".to_string()),
"DEFAULT_VALUE"
);
}
fn test_set_option_with_embedded_nul_thread_local() {
assert!(set_thread_local_config_option("f\0oo", "valid").is_err());
assert!(set_thread_local_config_option("foo", "in\0valid").is_err());
assert!(set_thread_local_config_option("xxxf\0oo", "in\0valid").is_err());
}
fn test_clear_option_thread_local() {
assert!(set_thread_local_config_option("TEST_OPTION", "256").is_ok());
assert_eq!(
get_thread_local_config_option("TEST_OPTION", "DEFAULT")
.unwrap_or_else(|_| "".to_string()),
"256"
);
assert_eq!(
get_config_option("TEST_OPTION", "DEFAULT").unwrap_or_else(|_| "".to_string()),
"256"
);
assert!(clear_thread_local_config_option("TEST_OPTION").is_ok());
assert_eq!(
get_thread_local_config_option("TEST_OPTION", "DEFAULT")
.unwrap_or_else(|_| "".to_string()),
"DEFAULT"
);
assert_eq!(
get_config_option("TEST_OPTION", "DEFAULT").unwrap_or_else(|_| "".to_string()),
"DEFAULT"
);
}
}