1use std::{ffi::{CString, CStr}, convert::TryInto};
2
3use crate::{db_internal::{fs_close, fs_open, fs_read, fs_write, fs_seek, fs_tell, fs_eof, fs_deviceExists, fs_deviceEject, fs_fileExists, fs_closeDir, fs_openDir, fs_readDir, clock_timestampToDatetime, fs_rewindDir, fs_allocMemoryCard}, clock::DateTime};
4
5const ESUCCESS: i32 = 0;
6const EACCESS: i32 = 2;
7const EEXIST: i32 = 20;
8const EFBIG: i32 = 22;
9const ENFILE: i32 = 41;
10const ENODEV: i32 = 43;
11const ENOENT: i32 = 44;
12const ENOSPC: i32 = 51;
13const EROFS: i32 = 69;
14const ESPIPE: i32 = 70;
15
16#[repr(C)]
17#[derive(Clone, Copy)]
18pub enum FileMode {
19 Read,
20 Write
21}
22
23#[repr(C)]
24#[derive(Clone, Copy)]
25pub enum SeekOrigin {
26 Begin,
27 Current,
28 End,
29}
30
31#[derive(Debug)]
32pub enum IOError {
33 TooManyFilesOpen,
34 ReadOnlyFileSystem,
35 FileNotFound,
36 DirectoryNotFound,
37 NoSuchDevice,
38 NotSupported,
39 InvalidSeek,
40 FileTooBig,
41 FileAlreadyExists,
42 NoSpaceOnDevice,
43 ReachedEndOfFile
44}
45
46pub struct FileStream {
47 handle: i32,
48}
49
50impl std::io::Read for FileStream {
51 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
52 unsafe {
53 let result = fs_read(self.handle, buf.as_mut_ptr().cast(), buf.len().try_into().unwrap());
54
55 match crate::db_internal::ERRNO {
56 ESUCCESS => {
57 }
58 EACCESS => {
59 return Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
60 }
61 _ => {
62 panic!("Unhandled errno");
63 }
64 }
65
66 return Ok(result.try_into().unwrap());
67 }
68 }
69}
70
71impl std::io::Write for FileStream {
72 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
73 unsafe {
74 let result = fs_write(self.handle, buf.as_ptr().cast(), buf.len().try_into().unwrap());
75
76 match crate::db_internal::ERRNO {
77 ESUCCESS => {
78 }
79 EACCESS => {
80 return Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
81 }
82 EFBIG => {
83 return Err(std::io::Error::new(std::io::ErrorKind::Other, "File size limit reached"));
84 }
85 _ => {
86 panic!("Unhandled errno");
87 }
88 }
89
90 return Ok(result.try_into().unwrap());
91 }
92 }
93
94 fn flush(&mut self) -> std::io::Result<()> {
95 unsafe {
96 crate::db_internal::fs_flush(self.handle);
97
98 match crate::db_internal::ERRNO {
99 ESUCCESS => {
100 return Ok(());
101 },
102 EACCESS => {
103 return Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied));
104 },
105 _ => {
106 panic!("Unhandled errno");
107 }
108 }
109 }
110 }
111}
112
113impl std::io::Seek for FileStream {
114 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
115 unsafe {
116 let result = match pos {
117 std::io::SeekFrom::Start(position) => {
118 fs_seek(self.handle, position.try_into().unwrap(), SeekOrigin::Begin)
119 },
120 std::io::SeekFrom::Current(position) => {
121 fs_seek(self.handle, position.try_into().unwrap(), SeekOrigin::Current)
122 },
123 std::io::SeekFrom::End(position) => {
124 fs_seek(self.handle, position.try_into().unwrap(), SeekOrigin::End)
125 }
126 };
127
128 match crate::db_internal::ERRNO {
129 ESUCCESS => {
130 }
131 ESPIPE => {
132 return Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe));
133 }
134 _ => {
135 panic!("Unhandled errno");
136 }
137 }
138
139 return Ok(result.try_into().unwrap());
140 }
141 }
142}
143
144impl FileStream {
145 pub fn open(path: &str, mode: FileMode) -> Result<FileStream, IOError> {
148 unsafe {
149 let path_cstr = CString::new(path).expect("Failed creating C string");
150 let handle = fs_open(path_cstr.as_ptr(), mode);
151
152 if handle == 0 {
153 match crate::db_internal::ERRNO {
154 ENFILE => {
155 return Err(IOError::TooManyFilesOpen);
156 }
157 ENOENT => {
158 return Err(IOError::FileNotFound);
159 }
160 EROFS => {
161 return Err(IOError::ReadOnlyFileSystem);
162 }
163 ENODEV => {
164 return Err(IOError::NoSuchDevice);
165 }
166 _ => {
167 panic!("Unhandled errno");
168 }
169 }
170 }
171
172 return Ok(FileStream {
173 handle: handle
174 });
175 }
176 }
177
178 pub fn allocate_memory_card(path: &str, icondata: &[u8;128], iconpalette: &[u16;16], blocks: i32) -> Result<FileStream, IOError> {
180 unsafe {
181 let path_cstr = CString::new(path).expect("Failed creating C string");
182 let handle = fs_allocMemoryCard(path_cstr.as_ptr(), icondata.as_ptr(), iconpalette.as_ptr(), blocks);
183
184 if handle == 0 {
185 match crate::db_internal::ERRNO {
186 EEXIST => {
187 return Err(IOError::FileAlreadyExists);
188 }
189 ENOSPC => {
190 return Err(IOError::NoSpaceOnDevice);
191 }
192 ENODEV => {
193 return Err(IOError::NoSuchDevice);
194 }
195 _ => {
196 panic!("Unhandled errno");
197 }
198 }
199 }
200
201 return Ok(FileStream {
202 handle: handle
203 });
204 }
205 }
206
207 pub fn position(&self) -> i32 {
209 unsafe {
210 return fs_tell(self.handle);
211 }
212 }
213
214 pub fn end_of_file(&self) -> bool {
216 unsafe {
217 return fs_eof(self.handle);
218 }
219 }
220}
221
222impl Drop for FileStream {
223 fn drop(&mut self) {
224 unsafe { fs_close(self.handle); }
225 }
226}
227
228pub struct DirectoryEntry {
229 pub name: String,
230 pub is_directory: bool,
231 pub size: i32,
232 pub created: DateTime,
233 pub modified: DateTime,
234}
235
236pub struct DirectoryInfo {
237 handle: i32,
238}
239
240impl DirectoryInfo {
241 pub fn open(path: &str) -> Result<DirectoryInfo, IOError> {
243 unsafe {
244 let path_cstr = CString::new(path).expect("Failed creating C string");
245 let result = fs_openDir(path_cstr.as_ptr());
246
247 match crate::db_internal::ERRNO {
248 ESUCCESS => {
249 }
250 ENOENT => {
251 return Err(IOError::DirectoryNotFound);
252 }
253 ENODEV => {
254 return Err(IOError::NoSuchDevice);
255 }
256 _ => {
257 panic!("Unhandled errno");
258 }
259 }
260
261 return Ok(DirectoryInfo {
262 handle: result
263 });
264 }
265 }
266
267 pub fn read(self) -> Option<DirectoryEntry> {
269 unsafe {
270 let dir_info_ptr = fs_readDir(self.handle);
271
272 if dir_info_ptr.is_null() {
273 return None;
274 }
275
276 let name_cstr = CStr::from_ptr((*dir_info_ptr).name.as_ptr());
277 let name_str = name_cstr.to_str().unwrap();
278
279 let mut created_dt = DateTime {
280 year: 0,
281 month: 0,
282 day: 0,
283 hour: 0,
284 minute: 0,
285 second: 0,
286 };
287 clock_timestampToDatetime((*dir_info_ptr).created, &mut created_dt);
288
289 let mut modified_dt = DateTime {
290 year: 0,
291 month: 0,
292 day: 0,
293 hour: 0,
294 minute: 0,
295 second: 0,
296 };
297 clock_timestampToDatetime((*dir_info_ptr).modified, &mut modified_dt);
298
299 return Some(DirectoryEntry {
300 name: name_str.to_string(),
301 is_directory: (*dir_info_ptr).is_directory != 0,
302 size: (*dir_info_ptr).size,
303 created: created_dt,
304 modified: modified_dt,
305 });
306 }
307 }
308
309 pub fn rewind(self) {
311 unsafe {
312 fs_rewindDir(self.handle);
313 }
314 }
315}
316
317impl Drop for DirectoryInfo {
318 fn drop(&mut self) {
319 unsafe { fs_closeDir(self.handle); }
320 }
321}
322
323pub fn device_exists(device: &str) -> bool {
326 unsafe {
327 let path_cstr = CString::new(device).expect("Failed creating C string");
328 return fs_deviceExists(path_cstr.as_ptr());
329 }
330}
331
332pub fn device_eject(device: &str) {
334 unsafe {
335 let path_cstr = CString::new(device).expect("Failed creating C string");
336 fs_deviceEject(path_cstr.as_ptr());
337 }
338}
339
340pub fn file_exists(path: &str) -> bool {
342 unsafe {
343 let path_cstr = CString::new(path).expect("Failed creating C string");
344 return fs_fileExists(path_cstr.as_ptr());
345 }
346}