diff --git a/CHANGES.md b/CHANGES.md index d4bdd844d..d43bd3a38 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,10 @@ - +- Add `gdal::vsi::read_dir` function. + + - + ## 0.12 - Bump Rust edition to 2021 diff --git a/fixtures/test_vsi_read_dir.zip b/fixtures/test_vsi_read_dir.zip new file mode 100644 index 000000000..e38aeb486 Binary files /dev/null and b/fixtures/test_vsi_read_dir.zip differ diff --git a/src/utils.rs b/src/utils.rs index 2d9cebb28..ff19695b4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use gdal_sys::{self, CPLErr}; use libc::c_char; use std::ffi::{CStr, CString}; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::errors::*; @@ -11,7 +11,23 @@ pub fn _string(raw_ptr: *const c_char) -> String { } pub fn _string_array(raw_ptr: *mut *mut c_char) -> Vec { - let mut ret_val: Vec = vec![]; + _convert_raw_ptr_array(raw_ptr, _string) +} + +pub fn _pathbuf(raw_ptr: *const c_char) -> PathBuf { + let c_str = unsafe { CStr::from_ptr(raw_ptr) }; + c_str.to_string_lossy().into_owned().into() +} + +pub fn _pathbuf_array(raw_ptr: *mut *mut c_char) -> Vec { + _convert_raw_ptr_array(raw_ptr, _pathbuf) +} + +fn _convert_raw_ptr_array(raw_ptr: *mut *mut c_char, convert: F) -> Vec +where + F: Fn(*const c_char) -> R, +{ + let mut ret_val = Vec::new(); let mut i = 0; unsafe { loop { @@ -23,7 +39,7 @@ pub fn _string_array(raw_ptr: *mut *mut c_char) -> Vec { if next.is_null() { break; } - let value = _string(next); + let value = convert(next); i += 1; ret_val.push(value); } diff --git a/src/vsi.rs b/src/vsi.rs index 9c2143731..b31038dcf 100644 --- a/src/vsi.rs +++ b/src/vsi.rs @@ -5,7 +5,31 @@ 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}; +use crate::utils::{_last_null_pointer_err, _path_to_c_string, _pathbuf_array}; + +/// Read the file names from a virtual file system with optional recursion. +pub fn read_dir>(path: P, recursive: bool) -> Result> { + _read_dir(path.as_ref(), recursive) +} + +fn _read_dir(path: &Path, recursive: bool) -> Result> { + 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 + }; + + Ok(_pathbuf_array(data)) +} /// Creates a new VSIMemFile from a given buffer. pub fn create_mem_file>(file_name: P, data: Vec) -> Result<()> { @@ -277,4 +301,43 @@ mod tests { }) ); } + + #[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"); + + // Concatenate "/vsizip/" prefix. + let path = ["/vsizip/", zip_path.to_str().unwrap()].concat(); + + // Read without recursion. + 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); + + // Read with recursion. + 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); + + // Attempting to read without VSI prefix returns error. + assert!(read_dir(zip_path, false).is_err()); + } }