use alloc::ffi::CString;
pub use sys::{FileOptions, FileStat, SEEK_CUR, SEEK_END, SEEK_SET};
use no_std_io::io::{self, Read, Seek, Write};
pub struct FileSystem {
handle: *const sys::playdate_file,
}
impl FileSystem {
pub(crate) fn new(handle: *const sys::playdate_file) -> Self {
Self { handle }
}
pub fn get_error(&self) -> Option<io::Error> {
let c_string = unsafe { (*self.handle).geterr.unwrap()() };
if c_string.is_null() {
None
} else {
let c_str = unsafe { ::core::ffi::CStr::from_ptr(c_string) };
Some(io::Error::new(
io::ErrorKind::Other,
c_str.to_str().unwrap(),
))
}
}
pub fn list_files(
&self,
path: impl AsRef<str>,
show_hidden: bool,
mut callback: impl FnMut(&str),
) -> Result<(), io::Error> {
let c_string = CString::new(path.as_ref()).unwrap();
extern "C" fn callback_wrapper(filename: *const i8, callback: *mut c_void) {
let callback = callback as *mut *mut dyn FnMut(&str);
let callback = unsafe { &mut **callback };
let filename = unsafe { ::core::ffi::CStr::from_ptr(filename) };
callback(filename.to_str().unwrap());
}
let mut callback_dyn: *mut dyn FnMut(&str) = &mut callback;
let callback_dyn_ptr: *mut *mut dyn FnMut(&str) = &mut callback_dyn;
let result = unsafe {
(*self.handle).listfiles.unwrap()(
c_string.as_ptr(),
Some(callback_wrapper),
callback_dyn_ptr as *mut _,
show_hidden as i32,
)
};
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub fn stat(&self, path: impl AsRef<str>) -> io::Result<FileStat> {
let c_string = CString::new(path.as_ref()).unwrap();
let mut stat = FileStat::default();
let result = unsafe { (*self.handle).stat.unwrap()(c_string.as_ptr(), &mut stat) };
if result != 0 {
Ok(stat)
} else {
Err(self.get_error().unwrap())
}
}
pub fn mkdir(&self, path: impl AsRef<str>) -> io::Result<()> {
let c_string = CString::new(path.as_ref()).unwrap();
let result = unsafe { (*self.handle).mkdir.unwrap()(c_string.as_ptr()) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub fn unlink(&self, name: impl AsRef<str>, recursive: bool) -> io::Result<()> {
let c_string = CString::new(name.as_ref()).unwrap();
let result = unsafe { (*self.handle).unlink.unwrap()(c_string.as_ptr(), recursive as i32) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub fn rename(&self, from: impl AsRef<str>, to: impl AsRef<str>) -> io::Result<()> {
let from_c_string = CString::new(from.as_ref()).unwrap();
let to_c_string = CString::new(to.as_ref()).unwrap();
let result =
unsafe { (*self.handle).rename.unwrap()(from_c_string.as_ptr(), to_c_string.as_ptr()) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub fn open(&self, name: impl AsRef<str>, mode: FileOptions) -> io::Result<File> {
let c_string = CString::new(name.as_ref()).unwrap();
let file = unsafe { (*self.handle).open.unwrap()(c_string.as_ptr(), mode) };
if file.is_null() {
Err(self.get_error().unwrap())
} else {
Ok(File::new(file))
}
}
pub(crate) fn close(&self, file: *mut sys::SDFile) -> io::Result<()> {
let result = unsafe { (*self.handle).close.unwrap()(file) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub(crate) fn read(&self, file: *mut sys::SDFile, buf: &mut [u8]) -> io::Result<usize> {
let result = unsafe {
(*self.handle).read.unwrap()(file, buf.as_mut_ptr() as *mut _, buf.len() as u32)
};
if result >= 0 {
Ok(result as usize)
} else {
Err(self.get_error().unwrap())
}
}
pub(crate) fn write(&self, file: *mut sys::SDFile, buf: &[u8]) -> io::Result<usize> {
let result = unsafe {
(*self.handle).write.unwrap()(file, buf.as_ptr() as *const _, buf.len() as u32)
};
if result >= 0 {
Ok(result as usize)
} else {
Err(self.get_error().unwrap())
}
}
pub(crate) fn flush(&self, file: *mut sys::SDFile) -> io::Result<()> {
let result = unsafe { (*self.handle).flush.unwrap()(file) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
pub(crate) fn tell(&self, file: *mut sys::SDFile) -> io::Result<usize> {
let result = unsafe { (*self.handle).tell.unwrap()(file) };
if result >= 0 {
Ok(result as usize)
} else {
Err(self.get_error().unwrap())
}
}
pub(crate) fn seek(&self, file: *mut sys::SDFile, pos: usize, whence: i32) -> io::Result<()> {
let result = unsafe { (*self.handle).seek.unwrap()(file, pos as i32, whence) };
if result != 0 {
Ok(())
} else {
Err(self.get_error().unwrap())
}
}
}
use core::ffi::c_void;
use crate::PLAYDATE;
pub struct File {
handle: *mut sys::SDFile,
}
impl File {
pub(crate) fn new(handle: *mut sys::SDFile) -> Self {
Self { handle }
}
pub fn tell(&self) -> io::Result<usize> {
PLAYDATE.file.tell(self.handle)
}
pub fn open(name: impl AsRef<str>, mode: FileOptions) -> io::Result<Self> {
PLAYDATE.file.open(name, mode)
}
}
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let Ok(size) = PLAYDATE.file.read(self.handle, buf) else {
return Err(io::Error::new(io::ErrorKind::Other, "file read error"));
};
Ok(size)
}
}
impl Write for File {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let Ok(size) = PLAYDATE.file.write(self.handle, buf) else {
return Err(io::Error::new(io::ErrorKind::Other, "file write error"));
};
Ok(size)
}
fn flush(&mut self) -> io::Result<()> {
if PLAYDATE.file.flush(self.handle).is_err() {
Err(io::Error::new(io::ErrorKind::Other, "file flush error"))
} else {
Ok(())
}
}
}
impl Seek for File {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let whence = match pos {
io::SeekFrom::Start(_) => SEEK_SET,
io::SeekFrom::End(_) => SEEK_END,
io::SeekFrom::Current(_) => SEEK_CUR,
};
let pos = match pos {
io::SeekFrom::Start(pos) => pos as usize,
io::SeekFrom::End(pos) => pos as usize,
io::SeekFrom::Current(pos) => pos as usize,
};
if PLAYDATE.file.seek(self.handle, pos, whence as _).is_err() {
Err(io::Error::new(io::ErrorKind::Other, "file seek error"))
} else {
Ok(pos as u64)
}
}
}
impl Drop for File {
fn drop(&mut self) {
PLAYDATE.file.close(self.handle).unwrap();
}
}