1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use super::metadata::{FileMetadata, FileType};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ListSort {
8 Size,
9 Mtime,
10 Depth,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SortDirection {
15 Asc,
16 Desc,
17}
18
19#[derive(Debug, Clone)]
28pub struct ListOptions {
29 pub(super) direction: Option<SortDirection>,
30 pub(super) sort: Option<ListSort>,
31 pub(super) ancestor: Option<PathBuf>,
32 pub(super) parent: Option<PathBuf>,
33 pub(super) file_type: Option<FileType>,
34 pub(super) is_invalid: bool,
35}
36
37impl Default for ListOptions {
38 #[cfg_attr(coverage_nightly, coverage(off))]
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl ListOptions {
45 pub fn new() -> Self {
47 Self {
48 direction: None,
49 sort: None,
50 ancestor: None,
51 parent: None,
52 file_type: None,
53 is_invalid: false,
54 }
55 }
56
57 pub fn descendants_of<P: AsRef<Path>>(mut self, directory: P) -> Self {
69 if self.parent.is_some() {
70 self.is_invalid = true;
71 return self;
72 }
73
74 self.ancestor = Some(directory.as_ref().to_path_buf());
75
76 self
77 }
78
79 pub fn children_of<P: AsRef<Path>>(mut self, directory: P) -> Self {
89 if self.ancestor.is_some() {
90 self.is_invalid = true;
91 return self;
92 }
93
94 self.parent = Some(directory.as_ref().to_path_buf());
95
96 self
97 }
98
99 pub fn file_type(mut self, file_type: FileType) -> Self {
103 if self.sort == Some(ListSort::Size) {
104 self.is_invalid = true;
105 return self;
106 }
107
108 self.file_type = Some(file_type);
109
110 self
111 }
112
113 pub fn by_depth(mut self) -> Self {
120 if self.sort.is_some() {
121 self.is_invalid = true;
122 return self;
123 }
124
125 self.sort = Some(ListSort::Depth);
126
127 self
128 }
129
130 pub fn by_mtime(mut self) -> Self {
134 if self.sort.is_some() {
135 self.is_invalid = true;
136 return self;
137 }
138
139 self.sort = Some(ListSort::Mtime);
140
141 self
142 }
143
144 pub fn by_size(mut self) -> Self {
152 if self.sort.is_some() || self.file_type.is_some() {
153 self.is_invalid = true;
154 return self;
155 }
156
157 self.sort = Some(ListSort::Size);
158
159 self
160 }
161
162 pub fn asc(mut self) -> Self {
166 if self.direction.is_some() {
167 self.is_invalid = true;
168 return self;
169 }
170
171 self.direction = Some(SortDirection::Asc);
172
173 self
174 }
175
176 pub fn desc(mut self) -> Self {
180 if self.direction.is_some() {
181 self.is_invalid = true;
182 return self;
183 }
184
185 self.direction = Some(SortDirection::Desc);
186
187 self
188 }
189}
190
191#[derive(Debug)]
199pub struct ListEntry {
200 pub(super) path: PathBuf,
201 pub(super) metadata: FileMetadata,
202}
203
204impl ListEntry {
205 pub fn path(&self) -> &Path {
207 &self.path
208 }
209
210 pub fn into_path(self) -> PathBuf {
212 self.path
213 }
214
215 pub fn metadata(&self) -> &FileMetadata {
217 &self.metadata
218 }
219}
220
221pub type ListMapFunc = Box<dyn FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<ListEntry>>;
222
223#[ouroboros::self_referencing]
224struct ListEntriesInner<'conn> {
225 stmt: rusqlite::Statement<'conn>,
226 #[borrows(mut stmt)]
227 #[covariant]
228 iter: rusqlite::MappedRows<'this, ListMapFunc>,
229}
230
231impl<'conn> fmt::Debug for ListEntriesInner<'conn> {
232 #[cfg_attr(coverage_nightly, coverage(off))]
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 f.debug_struct("ListEntries").finish_non_exhaustive()
235 }
236}
237fn build_list_entries_inner(
238 stmt: rusqlite::Statement,
239 params: Vec<Box<dyn rusqlite::ToSql>>,
240 map_func: ListMapFunc,
241) -> crate::Result<ListEntriesInner> {
242 ListEntriesInnerTryBuilder {
243 stmt,
244 iter_builder: |stmt| {
245 stmt.query_map(
246 params
247 .iter()
248 .map(AsRef::as_ref)
249 .collect::<Vec<_>>()
250 .as_slice(),
251 map_func,
252 )
253 .map_err(crate::Error::from)
254 },
255 }
256 .try_build()
257}
258
259#[derive(Debug)]
266pub struct ListEntries<'conn> {
267 inner: ListEntriesInner<'conn>,
268}
269
270impl<'conn> ListEntries<'conn> {
271 pub(super) fn new(
272 stmt: rusqlite::Statement<'conn>,
273 params: Vec<Box<dyn rusqlite::ToSql>>,
274 map_func: ListMapFunc,
275 ) -> crate::Result<Self> {
276 Ok(Self {
277 inner: build_list_entries_inner(stmt, params, map_func)?,
278 })
279 }
280}
281
282impl<'conn> Iterator for ListEntries<'conn> {
283 type Item = crate::Result<ListEntry>;
284
285 fn next(&mut self) -> Option<Self::Item> {
286 self.inner
287 .with_iter_mut(|iter| iter.next())
288 .map(|item| item.map_err(crate::Error::from))
289 }
290}