1use alloc::ffi::CString;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5pub use sys::{FileOptions, FileStat, SEEK_CUR, SEEK_END, SEEK_SET};
6
7use no_std_io::io::{self};
8
9pub use no_std_io::io::{Read, Seek, Write};
10
11pub struct PlaydateFileSystem {
12 handle: *const sys::playdate_file,
13}
14
15impl PlaydateFileSystem {
16 pub(crate) fn new(handle: *const sys::playdate_file) -> Self {
17 Self { handle }
18 }
19
20 pub fn get_error(&self) -> Option<io::Error> {
22 let c_string = unsafe { (*self.handle).geterr.unwrap()() };
23 if c_string.is_null() {
24 None
25 } else {
26 let c_str = unsafe { ::core::ffi::CStr::from_ptr(c_string) };
27 Some(io::Error::new(
28 io::ErrorKind::Other,
29 c_str.to_str().unwrap(),
30 ))
31 }
32 }
33
34 pub fn list_files(
36 &self,
37 path: impl AsRef<str>,
38 show_hidden: bool,
39 mut callback: impl FnMut(&str),
40 ) -> Result<(), io::Error> {
41 let c_string = CString::new(path.as_ref()).unwrap();
42 extern "C" fn callback_wrapper(filename: *const i8, callback: *mut c_void) {
43 let callback = callback as *mut *mut dyn FnMut(&str);
44 let callback = unsafe { &mut **callback };
45 let filename = unsafe { ::core::ffi::CStr::from_ptr(filename) };
46 callback(filename.to_str().unwrap());
47 }
48 let mut callback_dyn: *mut dyn FnMut(&str) = &mut callback;
49 let callback_dyn_ptr: *mut *mut dyn FnMut(&str) = &mut callback_dyn;
50 let result = unsafe {
51 (*self.handle).listfiles.unwrap()(
52 c_string.as_ptr(),
53 Some(callback_wrapper),
54 callback_dyn_ptr as *mut _,
55 show_hidden as i32,
56 )
57 };
58 if result != 0 {
59 Ok(())
60 } else {
61 Err(self.get_error().unwrap())
62 }
63 }
64
65 pub fn stat(&self, path: impl AsRef<str>) -> io::Result<FileStat> {
67 let c_string = CString::new(path.as_ref()).unwrap();
68 let mut stat = FileStat::default();
69 let result = unsafe { (*self.handle).stat.unwrap()(c_string.as_ptr(), &mut stat) };
70 if result != 0 {
71 Ok(stat)
72 } else {
73 Err(self.get_error().unwrap())
74 }
75 }
76
77 pub fn mkdir(&self, path: impl AsRef<str>) -> io::Result<()> {
79 let c_string = CString::new(path.as_ref()).unwrap();
80 let result = unsafe { (*self.handle).mkdir.unwrap()(c_string.as_ptr()) };
81 if result != 0 {
82 Ok(())
83 } else {
84 Err(self.get_error().unwrap())
85 }
86 }
87
88 pub fn unlink(&self, name: impl AsRef<str>, recursive: bool) -> io::Result<()> {
90 let c_string = CString::new(name.as_ref()).unwrap();
91 let result = unsafe { (*self.handle).unlink.unwrap()(c_string.as_ptr(), recursive as i32) };
92 if result != 0 {
93 Ok(())
94 } else {
95 Err(self.get_error().unwrap())
96 }
97 }
98
99 pub fn rename(&self, from: impl AsRef<str>, to: impl AsRef<str>) -> io::Result<()> {
101 let from_c_string = CString::new(from.as_ref()).unwrap();
102 let to_c_string = CString::new(to.as_ref()).unwrap();
103 let result =
104 unsafe { (*self.handle).rename.unwrap()(from_c_string.as_ptr(), to_c_string.as_ptr()) };
105 if result != 0 {
106 Ok(())
107 } else {
108 Err(self.get_error().unwrap())
109 }
110 }
111
112 pub fn open(&self, name: impl AsRef<str>, mode: FileOptions) -> io::Result<File> {
114 let c_string = CString::new(name.as_ref()).unwrap();
115 let file = unsafe { (*self.handle).open.unwrap()(c_string.as_ptr(), mode) };
116 if file.is_null() {
117 Err(self.get_error().unwrap())
118 } else {
119 Ok(File::new(file))
120 }
121 }
122
123 pub(crate) fn close(&self, file: *mut sys::SDFile) -> io::Result<()> {
125 let result = unsafe { (*self.handle).close.unwrap()(file) };
126 if result == 0 {
127 Ok(())
128 } else {
129 Err(self.get_error().unwrap())
130 }
131 }
132
133 pub(crate) fn read(&self, file: *mut sys::SDFile, buf: &mut [u8]) -> io::Result<usize> {
135 let result = unsafe {
136 (*self.handle).read.unwrap()(file, buf.as_mut_ptr() as *mut _, buf.len() as u32)
137 };
138 if result >= 0 {
139 Ok(result as usize)
140 } else {
141 Err(self.get_error().unwrap())
142 }
143 }
144
145 pub(crate) fn write(&self, file: *mut sys::SDFile, buf: &[u8]) -> io::Result<usize> {
147 let result = unsafe {
148 (*self.handle).write.unwrap()(file, buf.as_ptr() as *const _, buf.len() as u32)
149 };
150 if result >= 0 {
151 Ok(result as usize)
152 } else {
153 Err(self.get_error().unwrap())
154 }
155 }
156
157 pub(crate) fn flush(&self, file: *mut sys::SDFile) -> io::Result<()> {
159 let result = unsafe { (*self.handle).flush.unwrap()(file) };
160 if result != 0 {
161 Ok(())
162 } else {
163 Err(self.get_error().unwrap())
164 }
165 }
166
167 pub(crate) fn tell(&self, file: *mut sys::SDFile) -> io::Result<usize> {
169 let result = unsafe { (*self.handle).tell.unwrap()(file) };
170 if result >= 0 {
171 Ok(result as usize)
172 } else {
173 Err(self.get_error().unwrap())
174 }
175 }
176
177 pub(crate) fn seek(&self, file: *mut sys::SDFile, pos: usize, whence: i32) -> io::Result<()> {
179 let result = unsafe { (*self.handle).seek.unwrap()(file, pos as i32, whence) };
180 if result != 0 {
181 Ok(())
182 } else {
183 Err(self.get_error().unwrap())
184 }
185 }
186}
187
188use core::ffi::c_void;
189
190use crate::PLAYDATE;
191
192pub struct File {
193 handle: *mut sys::SDFile,
194}
195
196impl File {
197 pub(crate) fn new(handle: *mut sys::SDFile) -> Self {
198 Self { handle }
199 }
200
201 pub fn tell(&self) -> io::Result<usize> {
203 PLAYDATE.file.tell(self.handle)
204 }
205
206 pub fn open(name: impl AsRef<str>, mode: FileOptions) -> io::Result<Self> {
208 PLAYDATE.file.open(name, mode)
209 }
210
211 pub fn read_to_string(&mut self) -> io::Result<String> {
213 let mut buf = Vec::new();
214 self.read_to_end(&mut buf)?;
215 Ok(String::from_utf8(buf).unwrap())
216 }
217}
218
219impl Read for File {
220 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
221 let Ok(size) = PLAYDATE.file.read(self.handle, buf) else {
222 return Err(io::Error::new(io::ErrorKind::Other, "file read error"));
223 };
224 Ok(size)
225 }
226}
227
228impl Write for File {
229 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
230 let Ok(size) = PLAYDATE.file.write(self.handle, buf) else {
231 return Err(io::Error::new(io::ErrorKind::Other, "file write error"));
232 };
233 Ok(size)
234 }
235
236 fn flush(&mut self) -> io::Result<()> {
237 if PLAYDATE.file.flush(self.handle).is_err() {
238 Err(io::Error::new(io::ErrorKind::Other, "file flush error"))
239 } else {
240 Ok(())
241 }
242 }
243}
244
245impl Seek for File {
246 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
247 let whence = match pos {
248 io::SeekFrom::Start(_) => SEEK_SET,
249 io::SeekFrom::End(_) => SEEK_END,
250 io::SeekFrom::Current(_) => SEEK_CUR,
251 };
252 let pos = match pos {
253 io::SeekFrom::Start(pos) => pos as usize,
254 io::SeekFrom::End(pos) => pos as usize,
255 io::SeekFrom::Current(pos) => pos as usize,
256 };
257 if PLAYDATE.file.seek(self.handle, pos, whence as _).is_err() {
258 Err(io::Error::new(io::ErrorKind::Other, "file seek error"))
259 } else {
260 Ok(pos as u64)
261 }
262 }
263}
264
265impl Drop for File {
266 fn drop(&mut self) {
267 PLAYDATE.file.close(self.handle).unwrap();
268 }
269}