dbsdk_rs/
io.rs

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    /// Open a file from the filesystem (paths are given in the form of "/\[device\]/path/to/file") <br/>
146    /// Valid devices are "cd", "ma", and "mb"
147    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    /// Allocate a new file on the memory card device given in the path string of the given size in 512-byte blocks for writing
179    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    /// Get the position within the stream
208    pub fn position(&self) -> i32 {
209        unsafe {
210            return fs_tell(self.handle);
211        }
212    }
213
214    /// Gets whether the stream has reached its end
215    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    /// Open the given directory
242    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    /// Read the next entry from the directory list
268    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    /// Rewind to the beginning of the directory list
310    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
323/// Check if the given device exists <br/>
324/// Valid devices are "cd", "ma", and "mb"
325pub 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
332/// Eject the given device, if it supports being ejected
333pub 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
340/// Check if the given file exists
341pub 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}