use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::path::{Path, PathBuf};
use gdal_sys::{VSIFCloseL, VSIFileFromMemBuffer, VSIFree, VSIGetMemFileBuffer, VSIUnlink};
use crate::errors::{GdalError, Result};
use crate::utils::{_last_null_pointer_err, _path_to_c_string, _pathbuf_array};
pub fn read_dir<P: AsRef<Path>>(path: P, recursive: bool) -> Result<Vec<PathBuf>> {
fn _read_dir(path: &Path, recursive: bool) -> Result<Vec<PathBuf>> {
let path = _path_to_c_string(path)?;
let data = if recursive {
let data = unsafe { gdal_sys::VSIReadDirRecursive(path.as_ptr()) };
if data.is_null() {
return Err(_last_null_pointer_err("VSIReadDirRecursive"));
}
data
} else {
let data = unsafe { gdal_sys::VSIReadDir(path.as_ptr()) };
if data.is_null() {
return Err(_last_null_pointer_err("VSIReadDir"));
}
data
};
let ret = Ok(_pathbuf_array(data));
unsafe { gdal_sys::CSLDestroy(data) };
ret
}
_read_dir(path.as_ref(), recursive)
}
pub fn create_mem_file<P: AsRef<Path>>(file_name: P, data: Vec<u8>) -> Result<()> {
fn _create_mem_file(file_name: &Path, data: Vec<u8>) -> Result<()> {
let file_name = _path_to_c_string(file_name)?;
let mut data = ManuallyDrop::new(data);
let handle = unsafe {
VSIFileFromMemBuffer(
file_name.as_ptr(),
data.as_mut_ptr(),
data.len() as u64,
true as i32,
)
};
if handle.is_null() {
ManuallyDrop::into_inner(data);
return Err(_last_null_pointer_err("VSIGetMemFileBuffer"));
}
unsafe {
VSIFCloseL(handle);
}
Ok(())
}
_create_mem_file(file_name.as_ref(), data)
}
pub struct MemFileRef<'d> {
file_name: PathBuf,
data_ref: PhantomData<&'d mut ()>,
}
impl<'d> MemFileRef<'d> {
pub fn new(file_name: &Path) -> MemFileRef<'d> {
Self {
file_name: file_name.into(),
data_ref: PhantomData,
}
}
}
impl Drop for MemFileRef<'_> {
fn drop(&mut self) {
let _ = unlink_mem_file(&self.file_name);
}
}
pub fn create_mem_file_from_ref<P: AsRef<Path>>(
file_name: P,
data: &mut [u8],
) -> Result<MemFileRef<'_>> {
fn _create_mem_file_from_ref<'d>(
file_name: &Path,
data: &'d mut [u8],
) -> Result<MemFileRef<'d>> {
let file_name_c = _path_to_c_string(file_name)?;
let handle = unsafe {
VSIFileFromMemBuffer(
file_name_c.as_ptr(),
data.as_mut_ptr(),
data.len() as u64,
false as i32,
)
};
if handle.is_null() {
return Err(_last_null_pointer_err("VSIGetMemFileBuffer"));
}
unsafe {
VSIFCloseL(handle);
}
Ok(MemFileRef::new(file_name))
}
_create_mem_file_from_ref(file_name.as_ref(), data)
}
pub fn unlink_mem_file<P: AsRef<Path>>(file_name: P) -> Result<()> {
fn _unlink_mem_file(file_name: &Path) -> Result<()> {
let file_name_c = _path_to_c_string(file_name)?;
let rv = unsafe { VSIUnlink(file_name_c.as_ptr()) };
if rv != 0 {
return Err(GdalError::UnlinkMemFile {
file_name: file_name.display().to_string(),
});
}
Ok(())
}
_unlink_mem_file(file_name.as_ref())
}
pub fn get_vsi_mem_file_bytes_owned<P: AsRef<Path>>(file_name: P) -> Result<Vec<u8>> {
fn _get_vsi_mem_file_bytes_owned(file_name: &Path) -> Result<Vec<u8>> {
let file_name = _path_to_c_string(file_name)?;
let owned_bytes = unsafe {
let mut length: u64 = 0;
let bytes = VSIGetMemFileBuffer(file_name.as_ptr(), &mut length, true as i32);
if bytes.is_null() {
return Err(_last_null_pointer_err("VSIGetMemFileBuffer"));
}
let slice = std::slice::from_raw_parts(bytes, length as usize);
let vec = slice.to_vec();
VSIFree(bytes.cast::<std::ffi::c_void>());
vec
};
Ok(owned_bytes)
}
_get_vsi_mem_file_bytes_owned(file_name.as_ref())
}
pub fn call_on_mem_file_bytes<F, R, P: AsRef<Path>>(file_name: P, fun: F) -> Result<R>
where
F: FnOnce(&[u8]) -> R,
{
let file_name = _path_to_c_string(file_name.as_ref())?;
let mut length: u64 = 0;
let bytes = unsafe { VSIGetMemFileBuffer(file_name.as_ptr(), &mut length, false as i32) };
if bytes.is_null() {
return Err(_last_null_pointer_err("VSIGetMemFileBuffer"));
}
let slice = unsafe { std::slice::from_raw_parts(bytes, length as usize) };
Ok(fun(slice))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_and_retrieve_mem_file() {
let file_name = "/vsimem/525ebf24-a030-4677-bb4e-a921741cabe0";
create_mem_file(file_name, vec![1_u8, 2, 3, 4]).unwrap();
let bytes = get_vsi_mem_file_bytes_owned(file_name).unwrap();
assert_eq!(bytes, vec![1_u8, 2, 3, 4]);
assert!(matches!(
unlink_mem_file(file_name).unwrap_err(),
GdalError::UnlinkMemFile {
file_name
}
if file_name == file_name
));
}
#[test]
fn create_and_callmem_file() {
let file_name = "/vsimem/ee08caf2-a510-4b21-a4c4-44c1ebd763c8";
create_mem_file(file_name, vec![1_u8, 2, 3, 4]).unwrap();
let result = call_on_mem_file_bytes(file_name, |bytes| {
bytes.iter().map(|b| b * 2).collect::<Vec<u8>>()
})
.unwrap();
assert_eq!(result, vec![2_u8, 4, 6, 8]);
unlink_mem_file(file_name).unwrap();
}
#[test]
fn create_and_unlink_mem_file() {
let file_name = "/vsimem/bbf5f1d6-c1e9-4469-a33b-02cd9173132d";
create_mem_file(file_name, vec![1_u8, 2, 3, 4]).unwrap();
unlink_mem_file(file_name).unwrap();
}
#[test]
fn no_mem_file() {
assert!(matches!(
get_vsi_mem_file_bytes_owned("foobar").unwrap_err(),
GdalError::NullPointer {
method_name: "VSIGetMemFileBuffer",
msg,
}
if msg.is_empty()
));
}
#[test]
fn create_and_unlink_mem_file_from_ref() {
let file_name = "/vsimem/58e61d06-c96b-4ac0-9dd5-c37f69508454";
let mut data = vec![1_u8, 2, 3, 4];
let ref_handle = create_mem_file_from_ref(file_name, &mut data).unwrap();
drop(ref_handle);
assert_eq!(data, vec![1_u8, 2, 3, 4]);
}
#[test]
fn mem_file_ref_double_unlink() {
let file_name = "/vsimem/86df94a7-051d-4582-b141-d705ba8d8e83";
let mut data = vec![1_u8, 2, 3, 4];
let ref_handle = create_mem_file_from_ref(file_name, &mut data).unwrap();
unlink_mem_file(file_name).unwrap();
drop(ref_handle);
}
#[test]
fn test_vsi_read_dir() {
use std::path::Path;
let zip_path = Path::new(file!())
.parent()
.unwrap()
.parent()
.unwrap()
.join("fixtures")
.join("test_vsi_read_dir.zip");
let path = ["/vsizip/", zip_path.to_str().unwrap()].concat();
let expected = [
Path::new("folder"),
Path::new("File 1.txt"),
Path::new("File 2.txt"),
Path::new("File 3.txt"),
];
let files = read_dir(path.as_str(), false).unwrap();
assert_eq!(files, expected);
let expected = [
Path::new("folder/"),
Path::new("folder/File 4.txt"),
Path::new("File 1.txt"),
Path::new("File 2.txt"),
Path::new("File 3.txt"),
];
let files = read_dir(path.as_str(), true).unwrap();
assert_eq!(files, expected);
assert!(read_dir(zip_path, false).is_err());
}
}