use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ptr::NonNull;
use super::file_path_stat::FilePathStat;
use super::file_path_timestamp::FilePathTimestamp;
use super::open_file::OpenFile;
use crate::capi_state::CApiState;
use crate::ctypes::*;
use crate::null_terminated::ToNullTerminatedString;
use crate::{FilePathError, RenameFilePathError};
pub(super) fn last_err() -> String {
let ptr = unsafe { File::fns().geterr.unwrap()() };
match unsafe { crate::null_terminated::parse_null_terminated_utf8(ptr) } {
Ok(s) => s.into(),
Err(e) => format!(
"File: unable to parse UTF-8 error string from Playdate. {}",
e
),
}
}
#[derive(Debug)]
pub struct File;
impl File {
pub(crate) fn new() -> Self {
File
}
pub fn list_files(&self, path: &str) -> Result<impl Iterator<Item = String>, FilePathError> {
ListFilesIterator::new(path).ok_or_else(|| FilePathError {
path: String::from(path),
playdate: last_err(),
})
}
pub fn stat(&self, path: &str) -> Result<FilePathStat, FilePathError> {
let mut s = core::mem::MaybeUninit::<CFileStat>::uninit();
let result =
unsafe { Self::fns().stat.unwrap()(path.to_null_terminated_utf8().as_ptr(), s.as_mut_ptr()) };
match result {
0 => {
let s = unsafe { s.assume_init() };
let modified = FilePathTimestamp {
year: s.m_year,
month: s.m_month,
day: s.m_day,
hour: s.m_hour,
minute: s.m_minute,
second: s.m_second,
};
if s.isdir != 0 {
Ok(FilePathStat::Folder { modified })
} else {
Ok(FilePathStat::File {
size: s.size,
modified,
})
}
}
_ => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
}
}
pub fn make_folder(&self, path: &str) -> Result<(), FilePathError> {
let result = unsafe { Self::fns().mkdir.unwrap()(path.to_null_terminated_utf8().as_ptr()) };
match result {
0 => Ok(()),
_ => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
}
}
pub fn rename(&self, from: &str, to: &str) -> Result<(), RenameFilePathError> {
let result = unsafe {
Self::fns().rename.unwrap()(
from.to_null_terminated_utf8().as_ptr(),
to.to_null_terminated_utf8().as_ptr(),
)
};
match result {
0 => Ok(()),
_ => Err(RenameFilePathError {
from_path: String::from(from),
to_path: String::from(to),
playdate: last_err(),
}),
}
}
pub fn read_file(&self, path: &str) -> Result<Vec<u8>, FilePathError> {
let ptr = NonNull::new(unsafe {
Self::fns().open.unwrap()(
path.to_null_terminated_utf8().as_ptr(),
craydate_sys::FileOptions::kFileReadData | craydate_sys::FileOptions::kFileRead,
)
});
match ptr {
None => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
Some(handle) => {
let mut f = OpenFile::new(handle);
let read_result = f.read_file();
let _close_result = f.close(); read_result.ok_or_else(|| FilePathError {
path: String::from(path),
playdate: last_err(),
})
}
}
}
pub fn write_file(&self, path: &str, contents: &[u8]) -> Result<(), FilePathError> {
let ptr = NonNull::new(unsafe {
Self::fns().open.unwrap()(
path.to_null_terminated_utf8().as_ptr(),
craydate_sys::FileOptions::kFileWrite,
)
});
match ptr {
None => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
Some(handle) => {
let mut f = OpenFile::new(handle);
let write_result = f.write_file(contents);
if f.close() && write_result {
Ok(())
} else {
Err(FilePathError {
path: String::from(path),
playdate: last_err(),
})
}
}
}
}
pub fn delete(&self, path: &str) -> Result<(), FilePathError> {
let result =
unsafe { Self::fns().unlink.unwrap()(path.to_null_terminated_utf8().as_ptr(), false as i32) };
match result {
0 => Ok(()),
_ => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
}
}
pub fn delete_recursive(&self, path: &str) -> Result<(), FilePathError> {
let result =
unsafe { Self::fns().unlink.unwrap()(path.to_null_terminated_utf8().as_ptr(), true as i32) };
match result {
0 => Ok(()),
_ => Err(FilePathError {
path: String::from(path),
playdate: last_err(),
}),
}
}
pub(crate) fn fns() -> &'static craydate_sys::playdate_file {
CApiState::get().cfile
}
}
#[derive(Debug)]
pub struct ListFilesIterator;
impl ListFilesIterator {
fn new(path: &str) -> Option<alloc::vec::IntoIter<String>> {
let mut v = Vec::<String>::new();
unsafe extern "C" fn add_file(filename: *const u8, userdata: *mut c_void) {
let v = &mut *(userdata as *mut Vec<String>);
v.push(crate::null_terminated::parse_null_terminated_utf8(filename).unwrap().into());
}
let result = unsafe {
File::fns().listfiles.unwrap()(
path.to_null_terminated_utf8().as_ptr(),
Some(add_file),
&mut v as *mut Vec<String> as *mut c_void,
)
};
if result == 0 {
Some(v.into_iter())
} else {
None
}
}
}