use crate::ffi;
use crate::core::RaylibHandle;
use std::ffi::{CStr, CString, OsString, c_char};
fn os_to_cstring(s: impl Into<OsString>) -> CString {
let stripped = s.into().to_string_lossy().replace('\0', "");
CString::new(stripped).expect("interior NUL bytes are stripped above")
}
#[derive(Debug, Clone)]
pub struct FilePathIter<'a> {
iter: std::slice::Iter<'a, Option<&'a c_char>>,
}
impl<'a> FilePathIter<'a> {
unsafe fn new(list: *mut *mut c_char, count: u32) -> Self {
assert!(!list.is_null(), "file path array cannot be null");
assert!(list.is_aligned(), "file path array must be aligned");
let list = list.cast::<Option<&'a c_char>>();
let iter = unsafe { std::slice::from_raw_parts(list, count as usize) }.iter();
Self { iter }
}
fn func(f: &Option<&'a c_char>) -> &'a str {
let s = std::slice::from_ref(f.expect("file path string cannot be null"));
unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap()
}
}
impl<'a> Iterator for FilePathIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(Self::func)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
#[inline]
fn count(self) -> usize {
self.len()
}
fn last(self) -> Option<Self::Item> {
self.iter.last().map(Self::func)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth(n).map(Self::func)
}
}
impl DoubleEndedIterator for FilePathIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().map(Self::func)
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth_back(n).map(Self::func)
}
}
impl ExactSizeIterator for FilePathIter<'_> {
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
make_thin_wrapper!(
FilePathList,
ffi::FilePathList,
ffi::UnloadDirectoryFiles,
readonly
);
make_thin_wrapper!(
DroppedFilePathList,
ffi::FilePathList,
ffi::UnloadDroppedFiles,
readonly
);
impl FilePathList {
#[inline]
pub const fn count(&self) -> u32 {
self.0.count
}
pub fn paths(&self) -> Vec<&str> {
unsafe { std::slice::from_raw_parts(self.0.paths, self.count() as usize) }
.iter()
.map(|f| unsafe { CStr::from_ptr(*f) }.to_str().unwrap())
.collect()
}
pub fn iter(&self) -> FilePathIter<'_> {
unsafe { FilePathIter::new(self.0.paths, self.count()) }
}
}
impl DroppedFilePathList {
#[inline]
pub const fn count(&self) -> u32 {
self.0.count
}
pub fn paths(&self) -> Vec<&str> {
unsafe { std::slice::from_raw_parts(self.0.paths, self.count() as usize) }
.iter()
.map(|f| unsafe { CStr::from_ptr(*f) }.to_str().unwrap())
.collect()
}
pub fn iter(&self) -> FilePathIter<'_> {
unsafe { FilePathIter::new(self.0.paths, self.count()) }
}
}
impl RaylibHandle {
#[inline]
pub fn is_file_dropped(&self) -> bool {
unsafe { ffi::IsFileDropped() }
}
#[inline]
pub fn is_file_extension<A>(&self, file_name: A, file_ext: A) -> bool
where
A: Into<OsString>,
{
let file_name = os_to_cstring(file_name);
let file_ext = os_to_cstring(file_ext);
unsafe { ffi::IsFileExtension(file_name.as_ptr(), file_ext.as_ptr()) }
}
pub fn application_directory(&self) -> String {
unsafe {
let st = ffi::GetApplicationDirectory();
let c_str = CStr::from_ptr(st);
c_str.to_str().unwrap().to_string()
}
}
pub fn get_file_length<A>(&self, filename: A) -> i32
where
A: Into<OsString>,
{
let c_str = os_to_cstring(filename);
unsafe { ffi::GetFileLength(c_str.as_ptr()) }
}
#[must_use]
pub fn is_path_file<A>(&self, filename: A) -> bool
where
A: Into<OsString>,
{
let c_str = os_to_cstring(filename);
unsafe { ffi::IsPathFile(c_str.as_ptr()) }
}
pub fn load_directory_files<A>(&self, dir_path: A) -> FilePathList
where
A: Into<OsString>,
{
unsafe {
let c_str = os_to_cstring(dir_path);
FilePathList(ffi::LoadDirectoryFiles(c_str.as_ptr()))
}
}
pub fn load_directory_files_ex<A>(
&self,
dir_path: A,
filter: String,
scan_sub_dirs: bool,
) -> FilePathList
where
A: Into<OsString>,
{
unsafe {
let dir_c_str = os_to_cstring(dir_path);
let filter_c_str = CString::new(filter.replace('\0', "")).unwrap();
FilePathList(ffi::LoadDirectoryFilesEx(
dir_c_str.as_ptr(),
filter_c_str.as_ptr(),
scan_sub_dirs,
))
}
}
#[inline]
pub fn load_dropped_files(&self) -> DroppedFilePathList {
unsafe { DroppedFilePathList(ffi::LoadDroppedFiles()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::ManuallyDrop;
#[test]
fn os_to_cstring_strips_interior_nul() {
assert_eq!(os_to_cstring("a\0b\0c").to_str().unwrap(), "abc");
assert_eq!(os_to_cstring("clean/path").to_str().unwrap(), "clean/path");
assert_eq!(os_to_cstring("\0").to_str().unwrap(), "");
}
#[test]
#[should_panic(expected = "file path array cannot be null")]
fn test_null_list() {
let list = ManuallyDrop::new(FilePathList(ffi::FilePathList {
count: 0,
paths: std::ptr::null_mut(),
}));
let _it = list.iter();
}
#[test]
#[should_panic(expected = "file path string cannot be null")]
fn test_null_item() {
let mut paths = [std::ptr::null_mut()];
let list = ManuallyDrop::new(FilePathList(ffi::FilePathList {
count: 1,
paths: paths.as_mut_ptr(),
}));
let mut it = list.iter();
let _f = it.next();
}
#[test]
#[should_panic(expected = "file path string cannot be null")]
fn test_null_item_double_ended() {
let mut paths = [std::ptr::null_mut()];
let list = ManuallyDrop::new(FilePathList(ffi::FilePathList {
count: 1,
paths: paths.as_mut_ptr(),
}));
let mut it = list.iter();
let _f = it.next_back();
}
#[test]
fn test_len() {
let mut paths = [
c"apple".as_ptr().cast_mut(),
c"orange".as_ptr().cast_mut(),
c"banana".as_ptr().cast_mut(),
c"mango".as_ptr().cast_mut(),
c"pineapple".as_ptr().cast_mut(),
];
let list = ManuallyDrop::new(FilePathList(ffi::FilePathList {
count: 5,
paths: paths.as_mut_ptr(),
}));
let mut it = list.iter();
assert_eq!(it.len(), 5);
assert_eq!(it.next(), Some("apple"));
assert_eq!(it.len(), 4);
assert_eq!(it.next(), Some("orange"));
assert_eq!(it.len(), 3);
assert_eq!(it.next(), Some("banana"));
assert_eq!(it.len(), 2);
assert_eq!(it.next(), Some("mango"));
assert_eq!(it.len(), 1);
assert_eq!(it.next(), Some("pineapple"));
assert_eq!(it.len(), 0);
assert_eq!(it.next(), None);
}
#[test]
fn test_len_double_ended() {
let mut paths = [
c"apple".as_ptr().cast_mut(),
c"orange".as_ptr().cast_mut(),
c"banana".as_ptr().cast_mut(),
c"mango".as_ptr().cast_mut(),
c"pineapple".as_ptr().cast_mut(),
];
let list = ManuallyDrop::new(FilePathList(ffi::FilePathList {
count: 5,
paths: paths.as_mut_ptr(),
}));
let mut it = list.iter();
assert_eq!(it.len(), 5);
assert_eq!(it.next_back(), Some("pineapple"));
assert_eq!(it.len(), 4);
assert_eq!(it.next_back(), Some("mango"));
assert_eq!(it.len(), 3);
assert_eq!(it.next_back(), Some("banana"));
assert_eq!(it.len(), 2);
assert_eq!(it.next_back(), Some("orange"));
assert_eq!(it.len(), 1);
assert_eq!(it.next_back(), Some("apple"));
assert_eq!(it.len(), 0);
assert_eq!(it.next_back(), None);
}
}