sqlarfs/
list.rs

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/// Options for sorting and filtering a list of files.
20///
21/// This is used with [`Archive::list_with`].
22///
23/// Unless you specify a sort criteria with [`ListOptions::by_mtime`] or [`ListOptions::by_size`],
24/// the order of the returned files is unspecified.
25///
26/// [`Archive::list_with`]: crate::Archive::list_with
27#[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    /// Create a new [`ListOptions`] with default settings.
46    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    /// Only return files that are descendants of the given `directory`.
58    ///
59    /// This returns all descendants, not just immediate children.
60    ///
61    /// Passing an empty path will list all files in the archive.
62    ///
63    /// This does not include the `directory` itself in the list of descendants.
64    ///
65    /// If `directory` is a regular file, the returned list will be empty.
66    ///
67    /// This is mutually exclusive with [`ListOptions::children_of`].
68    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    /// Only return files that are immediate children of the given `directory`.
80    ///
81    /// Passing an empty path will list all files in the root of the archive.
82    ///
83    /// This does not include the `directory` itself in the list of descendants.
84    ///
85    /// If `directory` is a regular file, the returned list will be empty.
86    ///
87    /// This is mutually exclusive with [`ListOptions::descendants_of`].
88    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    /// Only return files of this [`FileType`].
100    ///
101    /// This is mutually exclusive with [`ListOptions::by_size`].
102    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    /// Sort by depth in the directory tree.
114    ///
115    /// This ensures parents always come before their children (or children before their parents in
116    /// descending mode).
117    ///
118    /// This is mutually exclusive with [`ListOptions::by_mtime`] and [`ListOptions::by_size`].
119    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    /// Sort by last modification time.
131    ///
132    /// This is mutually exclusive with [`ListOptions::by_depth`] and [`ListOptions::by_size`].
133    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    /// Sort by file size.
145    ///
146    /// If this is specified, then the list will only contain regular files, skipping directories
147    /// and symbolic links.
148    ///
149    /// This is mutually exclusive with [`ListOptions::by_depth`], [`ListOptions::by_mtime`] and
150    /// [`ListOptions::file_type`].
151    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    /// Sort in ascending order (the default).
163    ///
164    /// This is mutually exclusive with [`ListOptions::desc`].
165    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    /// Sort in descending order.
177    ///
178    /// This is mutually exclusive with [`ListOptions::asc`].
179    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/// An entry when iterating over a list of files.
192///
193/// You can use [`Archive::list`] and [`Archive::list_with`] to iterate over the files in an
194/// archive.
195///
196/// [`Archive::list`]: crate::Archive::list
197/// [`Archive::list_with`]: crate::Archive::list_with
198#[derive(Debug)]
199pub struct ListEntry {
200    pub(super) path: PathBuf,
201    pub(super) metadata: FileMetadata,
202}
203
204impl ListEntry {
205    /// The file path.
206    pub fn path(&self) -> &Path {
207        &self.path
208    }
209
210    /// Consume this entry and return the owned file path.
211    pub fn into_path(self) -> PathBuf {
212        self.path
213    }
214
215    /// The file metadata.
216    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/// An iterator over the files in an archive.
260///
261/// This is returned by [`Archive::list`] and [`Archive::list_with`].
262///
263/// [`Archive::list`]: crate::Archive::list
264/// [`Archive::list_with`]: crate::Archive::list_with
265#[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}