Skip to main content

weavatrix_scan/walker/
mod.rs

1use crate::report::{FileIdentity, FileVersion};
2use crate::walk_builder::{EntryFilter, EntrySorter};
3use crate::walk_platform::{PlatformDirectoryInfo, directory_info};
4use crate::walk_types::{DirectoryIdentity, FileSystemId};
5pub use crate::walk_types::{
6    ErrorPolicy, RootSymlinkPolicy, WalkEntry, WalkError, WalkOperation, WalkOptions,
7    WalkSkipReason,
8};
9use std::collections::{HashSet, VecDeque};
10use std::fs::{self, FileType};
11use std::io;
12use std::path::{Path, PathBuf};
13use std::sync::Arc;
14
15mod construction;
16
17#[derive(Debug)]
18pub(crate) struct PendingDirectory {
19    pub(crate) path: PathBuf,
20    pub(crate) depth: usize,
21    pub(crate) identity: Option<DirectoryIdentity>,
22    pub(crate) post_entry: Option<WalkEntry>,
23}
24
25// Keeping ReadDir inline avoids one heap allocation per directory on the hot
26// traversal path; the stack is depth-bounded and buffered at max_open.
27#[allow(clippy::large_enum_variant)]
28pub(crate) enum DirectoryEntries {
29    Open(fs::ReadDir),
30    Buffered(VecDeque<io::Result<fs::DirEntry>>),
31}
32
33impl DirectoryEntries {
34    #[allow(clippy::inline_always)]
35    #[inline(always)]
36    pub(crate) fn next(&mut self) -> Option<io::Result<fs::DirEntry>> {
37        match self {
38            Self::Open(entries) => entries.next(),
39            Self::Buffered(entries) => entries.pop_front(),
40        }
41    }
42
43    #[allow(clippy::inline_always)]
44    #[inline(always)]
45    pub(crate) const fn is_open(&self) -> bool {
46        matches!(self, Self::Open(_))
47    }
48}
49
50pub(crate) struct DirectoryFrame {
51    pub(crate) path: PathBuf,
52    pub(crate) depth: usize,
53    pub(crate) entries: DirectoryEntries,
54    pub(crate) identity: Option<DirectoryIdentity>,
55    pub(crate) post_entry: Option<WalkEntry>,
56}
57
58/// Iterative depth-first filesystem walker.
59///
60/// Paths remain native `PathBuf` values; no lossy UTF-8 conversion occurs.
61/// Open directory handles are bounded by `WalkOptions::max_open`; when a deep
62/// tree reaches the limit, the oldest remaining directory entries are buffered
63/// and its handle is closed.
64#[allow(clippy::struct_excessive_bools)]
65pub struct Walker {
66    pub(crate) root: Arc<PathBuf>,
67    pub(crate) root_components: usize,
68    pub(crate) root_file_type: Option<FileType>,
69    pub(crate) root_bytes: Option<u64>,
70    pub(crate) root_version: Option<FileVersion>,
71    pub(crate) root_file_system: Option<FileSystemId>,
72    pub(crate) root_directory_info: Option<PlatformDirectoryInfo>,
73    pub(crate) options: WalkOptions,
74    pub(crate) frames: Vec<DirectoryFrame>,
75    pub(crate) open_handles: usize,
76    pub(crate) yield_root: bool,
77    pub(crate) pending_directory: Option<PendingDirectory>,
78    pub(crate) skip_pending_directory: bool,
79    pub(crate) active_directories: HashSet<DirectoryIdentity>,
80    pub(crate) finished: bool,
81    pub(crate) sorter: Option<EntrySorter>,
82    pub(crate) filter: Option<EntryFilter>,
83    pub(crate) skip_stdout: Option<FileIdentity>,
84    pub(crate) contents_first: bool,
85    pub(crate) deferred_entry: Option<WalkEntry>,
86    pub(crate) plain_entries: bool,
87}