pub mod dataset;
pub mod raster;
pub mod vector;
use crate::ffi::types::*;
use std::os::raw::{c_int, c_void};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn oxigdal_buffer_to_ios_rgba(
buffer: *const OxiGdalBuffer,
out_buffer: *mut OxiGdalBuffer,
) -> OxiGdalErrorCode {
crate::check_null!(buffer, "buffer");
crate::check_null!(out_buffer, "out_buffer");
let src = crate::deref_ptr!(buffer, OxiGdalBuffer, "buffer");
let dst = crate::deref_ptr_mut!(out_buffer, OxiGdalBuffer, "out_buffer");
if src.width != dst.width || src.height != dst.height {
crate::ffi::error::set_last_error("Buffer dimensions mismatch".to_string());
return OxiGdalErrorCode::InvalidArgument;
}
if dst.channels != 4 {
crate::ffi::error::set_last_error("Output buffer must have 4 channels (RGBA)".to_string());
return OxiGdalErrorCode::InvalidArgument;
}
let pixel_count = (src.width * src.height) as usize;
unsafe {
match src.channels {
1 => {
for i in 0..pixel_count {
let gray = *src.data.add(i);
let dst_offset = i * 4;
*dst.data.add(dst_offset) = gray;
*dst.data.add(dst_offset + 1) = gray;
*dst.data.add(dst_offset + 2) = gray;
*dst.data.add(dst_offset + 3) = 255; }
}
3 => {
for i in 0..pixel_count {
let src_offset = i * 3;
let dst_offset = i * 4;
*dst.data.add(dst_offset) = *src.data.add(src_offset);
*dst.data.add(dst_offset + 1) = *src.data.add(src_offset + 1);
*dst.data.add(dst_offset + 2) = *src.data.add(src_offset + 2);
*dst.data.add(dst_offset + 3) = 255; }
}
4 => {
std::ptr::copy_nonoverlapping(src.data, dst.data, pixel_count * 4);
}
_ => {
crate::ffi::error::set_last_error(format!(
"Unsupported channel count: {}",
src.channels
));
return OxiGdalErrorCode::UnsupportedFormat;
}
}
}
OxiGdalErrorCode::Success
}
#[unsafe(no_mangle)]
pub extern "C" fn oxigdal_ios_get_documents_path() -> *mut std::os::raw::c_char {
match std::ffi::CString::new("/Documents") {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn oxigdal_ios_get_cache_path() -> *mut std::os::raw::c_char {
match std::ffi::CString::new("/Library/Caches") {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn oxigdal_ios_memory_warning() -> OxiGdalErrorCode {
let clear_result = unsafe { crate::common::cache::oxigdal_cache_clear() };
if clear_result != OxiGdalErrorCode::Success {
return clear_result;
}
crate::common::oxigdal_mobile_reset_stats();
if let Err(e) = crate::common::cache::set_max_cache_size_mb(10) {
crate::ffi::error::set_last_error(format!(
"Failed to reduce cache on iOS memory warning: {}",
e
));
return OxiGdalErrorCode::AllocationFailed;
}
let cache_dirs = ["/Library/Caches/oxigdal_share", "/tmp/oxigdal"];
for dir in &cache_dirs {
let path = std::path::Path::new(dir);
if path.exists() {
let _ = std::fs::remove_dir_all(path);
}
}
OxiGdalErrorCode::Success
}
pub type OxiGdalIosBackgroundTaskCallback = extern "C" fn(success: c_int, user_data: *mut c_void);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn oxigdal_ios_process_in_background(
dataset: *const OxiGdalDataset,
callback: OxiGdalIosBackgroundTaskCallback,
user_data: *mut c_void,
) -> OxiGdalErrorCode {
crate::check_null!(dataset, "dataset");
let user_data_ptr = user_data as usize;
let callback_fn = callback;
let mut metadata = OxiGdalMetadata {
width: 0,
height: 0,
band_count: 0,
data_type: 0,
epsg_code: 0,
geotransform: [0.0; 6],
};
let result =
unsafe { crate::ffi::raster::oxigdal_dataset_get_metadata(dataset, &mut metadata) };
if result != OxiGdalErrorCode::Success {
callback_fn(0, user_data);
return result;
}
let dataset_ptr = dataset as usize;
let handle = std::thread::Builder::new()
.name("oxigdal-ios-bg".to_string())
.spawn(move || {
let dataset = dataset_ptr as *const OxiGdalDataset;
let user_data = user_data_ptr as *mut c_void;
let success = process_dataset_background(dataset, &metadata);
callback_fn(if success { 1 } else { 0 }, user_data);
});
match handle {
Ok(_) => OxiGdalErrorCode::Success,
Err(e) => {
crate::ffi::error::set_last_error(format!("Failed to spawn background thread: {}", e));
callback_fn(0, user_data);
OxiGdalErrorCode::Unknown
}
}
}
fn process_dataset_background(dataset: *const OxiGdalDataset, metadata: &OxiGdalMetadata) -> bool {
if metadata.band_count > 0 {
let mut stats = OxiGdalStats {
min: 0.0,
max: 0.0,
mean: 0.0,
stddev: 0.0,
valid_count: 0,
};
let result = unsafe {
crate::ffi::raster::oxigdal_dataset_compute_stats(
dataset, 1, 1, &mut stats,
)
};
if result != OxiGdalErrorCode::Success {
return false;
}
}
let preview_width = metadata.width.min(256);
let preview_height = metadata.height.min(256);
if preview_width > 0 && preview_height > 0 {
let buffer_ptr = unsafe {
crate::ffi::oxigdal_buffer_alloc(
preview_width,
preview_height,
metadata.band_count.min(3), )
};
if !buffer_ptr.is_null() {
let read_result = unsafe {
crate::ffi::raster::oxigdal_dataset_read_region(
dataset,
0,
0,
preview_width,
preview_height,
1,
buffer_ptr,
)
};
if read_result == OxiGdalErrorCode::Success {
let preview_key = format!("ios_preview_{}x{}", metadata.width, metadata.height);
let buf = unsafe { &*buffer_ptr };
if !buf.data.is_null() && buf.length > 0 {
let data = unsafe { std::slice::from_raw_parts(buf.data, buf.length).to_vec() };
crate::common::cache::put_cached_tile(
preview_key,
data,
preview_width,
preview_height,
metadata.band_count.min(3),
);
}
}
unsafe {
crate::ffi::oxigdal_buffer_free(buffer_ptr);
}
}
}
true
}
#[unsafe(no_mangle)]
pub extern "C" fn oxigdal_ios_get_background_timeout() -> c_int {
25
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paths() {
let docs_path = oxigdal_ios_get_documents_path();
assert!(!docs_path.is_null());
unsafe {
crate::ffi::error::oxigdal_string_free(docs_path);
}
let cache_path = oxigdal_ios_get_cache_path();
assert!(!cache_path.is_null());
unsafe {
crate::ffi::error::oxigdal_string_free(cache_path);
}
}
#[test]
fn test_memory_warning() {
let _ = crate::common::cache::init_cache(50);
let result = oxigdal_ios_memory_warning();
assert_eq!(result, OxiGdalErrorCode::Success);
let mut size_mb = 0;
let mut max_mb = 0;
let mut entries = 0;
let info_result = unsafe {
crate::common::cache::oxigdal_cache_get_info(&mut size_mb, &mut max_mb, &mut entries)
};
assert_eq!(info_result, OxiGdalErrorCode::Success);
assert_eq!(entries, 0); assert!(max_mb <= 10); }
#[test]
fn test_background_timeout() {
let timeout = oxigdal_ios_get_background_timeout();
assert!(timeout > 0);
assert!(timeout <= 30); }
extern "C" fn test_bg_callback(success: c_int, _user_data: *mut c_void) {
assert!(success == 0 || success == 1);
}
#[test]
fn test_background_processing_null_dataset() {
let result = unsafe {
oxigdal_ios_process_in_background(
std::ptr::null(),
test_bg_callback,
std::ptr::null_mut(),
)
};
assert_eq!(result, OxiGdalErrorCode::NullPointer);
}
}