1use libc::{c_char, c_void};
2use std::error;
3use std::ffi::{CStr, CString, NulError};
4use std::fmt;
5use std::marker::PhantomData;
6use std::path::{Path, PathBuf};
7use std::ptr;
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9use sys::filesystem::SDL_PathInfo;
10
11use crate::get_error;
12use crate::sys;
13use crate::Error;
14
15#[derive(Debug, Clone)]
16pub enum FileSystemError {
17 InvalidPathError(PathBuf),
18 NulError(NulError),
19 SdlError(Error),
20}
21
22macro_rules! path_cstring {
24 ($pathref:ident) => {
25 let Some($pathref) = $pathref.as_ref().to_str() else {
26 return Err(FileSystemError::InvalidPathError(
27 $pathref.as_ref().to_owned(),
28 ));
29 };
30
31 let Ok($pathref) = CString::new($pathref) else {
32 return Err(FileSystemError::InvalidPathError(PathBuf::from($pathref)));
33 };
34 };
35}
36
37macro_rules! cstring_path {
39 ($path:ident, $error:expr) => {
40 let Ok($path) = CStr::from_ptr($path).to_str() else {
41 $error
42 };
43 let $path = Path::new($path);
44 };
45}
46
47#[doc(alias = "SDL_CopyFile")]
48pub fn copy_file(
49 old_path: impl AsRef<Path>,
50 new_path: impl AsRef<Path>,
51) -> Result<(), FileSystemError> {
52 path_cstring!(old_path);
53 path_cstring!(new_path);
54 unsafe {
55 if !sys::filesystem::SDL_CopyFile(old_path.as_ptr(), new_path.as_ptr()) {
56 return Err(FileSystemError::SdlError(get_error()));
57 }
58 }
59 Ok(())
60}
61
62#[doc(alias = "SDL_CreateDirectory")]
63pub fn create_directory(path: impl AsRef<Path>) -> Result<(), FileSystemError> {
64 path_cstring!(path);
65 unsafe {
66 if !sys::filesystem::SDL_CreateDirectory(path.as_ptr()) {
67 return Err(FileSystemError::SdlError(get_error()));
68 }
69 }
70 Ok(())
71}
72
73pub use sys::filesystem::SDL_EnumerationResult as EnumerationResult;
74
75pub type EnumerateCallback = fn(&Path, &Path) -> EnumerationResult;
76
77unsafe extern "C" fn c_enumerate_directory(
78 userdata: *mut c_void,
79 dirname: *const c_char,
80 fname: *const c_char,
81) -> EnumerationResult {
82 let callback: EnumerateCallback = std::mem::transmute(userdata);
83
84 cstring_path!(dirname, return EnumerationResult::FAILURE);
85 cstring_path!(fname, return EnumerationResult::FAILURE);
86
87 callback(dirname, fname)
88}
89
90#[doc(alias = "SDL_EnumerateDirectory")]
91pub fn enumerate_directory(
92 path: impl AsRef<Path>,
93 callback: EnumerateCallback,
94) -> Result<(), FileSystemError> {
95 path_cstring!(path);
96 unsafe {
97 if !sys::filesystem::SDL_EnumerateDirectory(
98 path.as_ptr(),
99 Some(c_enumerate_directory),
100 callback as *mut c_void,
101 ) {
102 return Err(FileSystemError::SdlError(get_error()));
103 }
104 }
105 Ok(())
106}
107
108#[doc(alias = "SDL_GetBasePath")]
109pub fn get_base_path() -> Result<&'static Path, FileSystemError> {
110 unsafe {
111 let path = sys::filesystem::SDL_GetBasePath();
112 cstring_path!(path, return Err(FileSystemError::SdlError(get_error())));
113 Ok(path)
114 }
115}
116
117pub use sys::filesystem::SDL_PathType as PathType;
120
121pub struct PathInfo {
122 internal: SDL_PathInfo,
123}
124
125impl PathInfo {
126 fn path_type(&self) -> PathType {
127 self.internal.r#type as PathType
128 }
129
130 fn size(&self) -> usize {
131 self.internal.size as usize
132 }
133
134 fn create_time(&self) -> SystemTime {
135 UNIX_EPOCH + Duration::from_nanos(self.internal.create_time as u64)
136 }
137
138 fn modify_time(&self) -> SystemTime {
139 UNIX_EPOCH + Duration::from_nanos(self.internal.modify_time as u64)
140 }
141
142 fn access_time(&self) -> SystemTime {
143 UNIX_EPOCH + Duration::from_nanos(self.internal.access_time as u64)
144 }
145}
146
147impl fmt::Debug for PathInfo {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 f.debug_struct("PathInfo")
150 .field(
151 "path_type",
152 match self.path_type() {
153 PathType::DIRECTORY => &"Directory",
154 PathType::FILE => &"File",
155 PathType::NONE => &"None",
156 _ => &"Other",
157 },
158 )
159 .field("size", &self.size())
160 .field("create_time", &self.create_time())
161 .field("modify_time", &self.modify_time())
162 .field("access_time", &self.access_time())
163 .finish()
164 }
165}
166
167#[doc(alias = "SDL_GetPathInfo")]
168pub fn get_path_info(path: impl AsRef<Path>) -> Result<PathInfo, FileSystemError> {
169 let mut info = SDL_PathInfo {
170 r#type: PathType::NONE,
171 size: 0,
172 create_time: 0,
173 modify_time: 0,
174 access_time: 0,
175 };
176 path_cstring!(path);
177
178 unsafe {
179 if !sys::filesystem::SDL_GetPathInfo(path.as_ptr(), &mut info as *mut SDL_PathInfo) {
180 return Err(FileSystemError::SdlError(get_error()));
181 }
182 }
183
184 Ok(PathInfo { internal: info })
185}
186
187#[derive(Debug, Clone)]
188pub enum PrefPathError {
189 InvalidOrganizationName(NulError),
190 InvalidApplicationName(NulError),
191 SdlError(Error),
192}
193
194impl fmt::Display for PrefPathError {
195 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196 use self::PrefPathError::*;
197
198 match *self {
199 InvalidOrganizationName(ref e) => write!(f, "Invalid organization name: {}", e),
200 InvalidApplicationName(ref e) => write!(f, "Invalid application name: {}", e),
201 SdlError(ref e) => write!(f, "SDL error: {}", e),
202 }
203 }
204}
205
206impl error::Error for PrefPathError {
207 fn description(&self) -> &str {
208 use self::PrefPathError::*;
209
210 match *self {
211 InvalidOrganizationName(_) => "invalid organization name",
212 InvalidApplicationName(_) => "invalid application name",
213 SdlError(ref e) => &e.0,
214 }
215 }
216}
217
218#[doc(alias = "SDL_GetPrefPath")]
221pub fn get_pref_path(org_name: &str, app_name: &str) -> Result<PathBuf, PrefPathError> {
222 let org = match CString::new(org_name) {
223 Ok(s) => s,
224 Err(err) => return Err(PrefPathError::InvalidOrganizationName(err)),
225 };
226 let app = match CString::new(app_name) {
227 Ok(s) => s,
228 Err(err) => return Err(PrefPathError::InvalidApplicationName(err)),
229 };
230
231 let path = unsafe {
232 let buf = sys::filesystem::SDL_GetPrefPath(
233 org.as_ptr() as *const c_char,
234 app.as_ptr() as *const c_char,
235 );
236 let path = PathBuf::from(CStr::from_ptr(buf).to_str().unwrap());
237 sys::stdinc::SDL_free(buf as *mut c_void);
238 path
239 };
240
241 if path.as_os_str().is_empty() {
242 Err(PrefPathError::SdlError(get_error()))
243 } else {
244 Ok(path)
245 }
246}
247
248pub use sys::filesystem::SDL_Folder as Folder;
249
250#[doc(alias = "SDL_GetUserFolder")]
251pub fn get_user_folder(folder: Folder) -> Result<&'static Path, FileSystemError> {
252 unsafe {
253 let path = sys::filesystem::SDL_GetUserFolder(folder);
254 cstring_path!(path, return Err(FileSystemError::SdlError(get_error())));
255 Ok(path)
256 }
257}
258
259bitflags! {
260 pub struct GlobFlags: sys::filesystem::SDL_GlobFlags {
261 const NONE = 0;
262 const CASEINSENSITIVE = sys::filesystem::SDL_GLOB_CASEINSENSITIVE;
263 }
264}
265
266pub struct GlobResultsIter<'a> {
267 results: &'a GlobResults<'a>,
268 index: isize,
269}
270
271impl<'a> Iterator for GlobResultsIter<'a> {
272 type Item = &'a Path;
273 fn next(&mut self) -> Option<Self::Item> {
274 let current = self.results.get(self.index);
275 self.index += 1;
276 current
277 }
278}
279
280pub struct GlobResults<'a> {
281 internal: *mut *mut c_char,
282 count: isize,
283 phantom: PhantomData<&'a *mut *mut c_char>,
284}
285
286impl GlobResults<'_> {
287 fn new(internal: *mut *mut c_char, count: isize) -> Self {
288 Self {
289 internal,
290 count,
291 phantom: PhantomData,
292 }
293 }
294
295 fn len(&self) -> usize {
296 self.count as usize
297 }
298
299 fn get<I>(&self, index: I) -> Option<&Path>
300 where
301 I: Into<isize>,
302 {
303 let index = index.into();
304 if index >= self.count {
305 return None;
306 }
307 unsafe {
308 let path = *self.internal.offset(index);
309 cstring_path!(path, return None);
310 Some(path)
311 }
312 }
313}
314
315impl<'a> IntoIterator for &'a GlobResults<'a> {
316 type Item = &'a Path;
317 type IntoIter = GlobResultsIter<'a>;
318 fn into_iter(self) -> Self::IntoIter {
319 Self::IntoIter {
320 results: self,
321 index: 0,
322 }
323 }
324}
325
326impl Drop for GlobResults<'_> {
327 fn drop(&mut self) {
328 unsafe {
329 sys::stdinc::SDL_free(self.internal as *mut c_void);
330 }
331 }
332}
333
334#[doc(alias = "SDL_GlobDirectory")]
335pub fn glob_directory(
336 path: impl AsRef<Path>,
337 pattern: Option<&str>,
338 flags: GlobFlags,
339) -> Result<GlobResults, FileSystemError> {
340 path_cstring!(path);
341 let pattern = match pattern {
342 Some(pattern) => match CString::new(pattern) {
343 Ok(pattern) => Some(pattern),
344 Err(error) => return Err(FileSystemError::NulError(error)),
345 },
346 None => None,
347 };
348 let pattern_ptr = pattern.as_ref().map_or(ptr::null(), |pat| pat.as_ptr());
349 let mut count = 0;
350
351 let results = unsafe {
352 let paths = sys::filesystem::SDL_GlobDirectory(
353 path.as_ptr(),
354 pattern_ptr,
355 flags.bits(),
356 &mut count as *mut i32,
357 );
358 if paths.is_null() {
359 return Err(FileSystemError::SdlError(get_error()));
360 }
361 GlobResults::new(paths, count as isize)
362 };
363 Ok(results)
364}
365
366#[doc(alias = "SDL_RemovePath")]
367pub fn remove_path(path: impl AsRef<Path>) -> Result<(), FileSystemError> {
368 path_cstring!(path);
369 unsafe {
370 if !sys::filesystem::SDL_RemovePath(path.as_ptr()) {
371 return Err(FileSystemError::SdlError(get_error()));
372 }
373 }
374 Ok(())
375}
376
377#[doc(alias = "SDL_RenamePath")]
378pub fn rename_path(
379 old_path: impl AsRef<Path>,
380 new_path: impl AsRef<Path>,
381) -> Result<(), FileSystemError> {
382 path_cstring!(old_path);
383 path_cstring!(new_path);
384
385 unsafe {
386 if !sys::filesystem::SDL_RenamePath(old_path.as_ptr(), new_path.as_ptr()) {
387 return Err(FileSystemError::SdlError(get_error()));
388 }
389 }
390
391 Ok(())
392}