1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
use core::{ptr::NonNull, ffi::CStr};
use crate::AsPtr;
use sys::ffi;
use sys::error::fs::Error;
use sys::error::fs::Status;
use super::record::Record;
use super::Storage;
use super::Metadata;
use super::File;
use super::Path;
use super::FILE_NAME_LEN_MAX;
use super::Result;


/// Iterator over the entries in a directory.
///
/// This iterator is returned from the [`Storage::read_dir`] function and
/// will yield instances of <code>[crate::fs::Result]<[Entry]></code>. Through a [`Entry`]
/// information like the entry's filename and possibly other metadata can be
/// learned.
///
///
/// # Difference from same thing in the std
///
/// [ReadDir::rewind] methods restarts the iterator.
///
/// [Entry] have no `path`, only filename and metadata.
///
/// Metadata is optional and disabled by default, can be enabled with [ReadDir::with_info] method.
///
/// Entries contains buffer for the filename string,
/// its length can be set with <code>[ReadDir::with_buf_len]<usize></code> method.
///
///
/// # Errors
///
/// This [`crate::fs::Result`] will be an [`Err`] if there's some sort of intermittent
/// IO/FS error during iteration.
///
/// Also returns <code>Err([crate::fs::Error::Internal])</code> when received null-pointer from the underneath cffi API.
pub struct ReadDir<const NAME_BUF_LEN: usize = { FILE_NAME_LEN_MAX as usize }, const WITH_INFO: bool = false>(File);


impl<T: Record> Storage<T> where [(); T::LEN]: Sized {
	/// Note: `path` of directory must not ends with a trailing slash.
	pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<ReadDir> {
		let path = path.as_ref();

		unsafe {
			let dir = ffi::storage_file_alloc(self.0.as_ptr());

			// TODO: remove trailing slash from path
			if ffi::storage_dir_open(dir, path.as_ptr() as _) {
			} else {
				ffi::storage_file_get_error(dir)?;
			}

			NonNull::new(dir).map(File).ok_or(Error::Internal).map(ReadDir::new)
		}
	}
}


impl<const NAME_BUF_LEN: usize, const WITH_INFO: bool> ReadDir<NAME_BUF_LEN, WITH_INFO> {
	pub const fn new(file: File) -> Self { Self(file) }
}
impl<const NAME_BUF_LEN: usize> ReadDir<NAME_BUF_LEN, false> {
	pub fn with_info(self) -> ReadDir<NAME_BUF_LEN, true> { ReadDir(self.0) }
}
impl<const CURRENT_BUF_LEN: usize, const WITH_INFO: bool> ReadDir<CURRENT_BUF_LEN, WITH_INFO> {
	pub fn with_buf_len<const LEN: usize>(self) -> ReadDir<LEN, WITH_INFO> { ReadDir(self.0) }
}


impl<const LEN: usize, const INFO: bool> ReadDir<LEN, INFO> {
	pub fn rewind(&mut self) -> Result<(), sys::error::fs::Error> {
		let ptr = self.0.as_ptr();
		unsafe {
			if ffi::storage_dir_rewind(ptr) {
				Ok(())
			} else {
				Ok(ffi::storage_file_get_error(ptr)?)
			}
		}
	}
}


impl<const LEN: usize> Iterator for ReadDir<LEN, true> {
	type Item = Result<Entry<ffi::FileInfo, LEN>>;

	fn next(&mut self) -> Option<Self::Item> {
		let mut entry = Entry::default();

		unsafe {
			let success = ffi::storage_dir_read(
			                                    self.0.as_ptr(),
			                                    (&mut entry.info) as *mut _,
			                                    entry.name.as_mut_ptr() as _,
			                                    LEN as u16,
			);

			if success {
				Some(Ok(entry))
			} else {
				match ffi::storage_file_get_error(self.0.as_ptr()) {
					Status::FSE_OK | Status::FSE_NOT_EXIST => None,
					err => Some(Err(err.into())),
				}
			}
		}
	}
}

impl<const LEN: usize> Iterator for ReadDir<LEN, false> {
	type Item = Result<Entry<(), LEN>>;

	fn next(&mut self) -> Option<Self::Item> {
		let mut entry = Entry::default();

		unsafe {
			let success = ffi::storage_dir_read(
			                                    self.0.as_ptr(),
			                                    core::ptr::null_mut(),
			                                    entry.name.as_mut_ptr() as _,
			                                    LEN as u16,
			);

			if success {
				Some(Ok(entry))
			} else {
				match ffi::storage_file_get_error(self.0.as_ptr()) {
					Status::FSE_OK | Status::FSE_NOT_EXIST => None,
					err => Some(Err(err.into())),
				}
			}
		}
	}
}


/// Entries returned by the [`ReadDir`] iterator.
///
/// An instance of `Entry` represents an entry inside of a directory on the
/// filesystem. Each entry can be inspected via methods to learn about the filename
/// or possibly other metadata.
#[derive(Debug)]
pub struct Entry<Info, const LEN: usize> {
	name: [u8; LEN],
	info: Info,
}


impl<Info, const LEN: usize> Entry<Info, LEN> {
	pub fn file_name(&self) -> Result<&CStr, core::ffi::FromBytesUntilNulError> { CStr::from_bytes_until_nul(self.name.as_slice()) }
}


impl<const LEN: usize> Entry<ffi::FileInfo, LEN> {
	pub fn metadata(&self) -> Metadata { Metadata(&self.info) }
}

impl<Info, const LEN: usize> Default for Entry<Info, LEN> {
	fn default() -> Self {
		Self { name: [0; LEN],
		       info: unsafe { core::mem::zeroed() } }
	}
}

impl<Info, const LEN: usize> core::fmt::Display for Entry<Info, LEN> {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "{:?}", self.file_name().map_err(|_| core::fmt::Error)?,)
	}
}