Skip to main content

weavatrix_scan/stateful_walk/
mod.rs

1use crate::runtime::ParallelRuntime;
2use crate::walk_types::{DirectoryIdentity, FileSystemId, WalkEntry, WalkError, WalkOptions};
3use crate::walker::Walker;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8mod parallel;
9mod serial;
10
11pub use parallel::ParallelStatefulWalker;
12
13type DirectoryProcessor<R, E> = Arc<
14    dyn Fn(usize, &Path, &mut R, &mut Vec<Result<StatefulWalkEntry<E>, WalkError>>)
15        + Send
16        + Sync
17        + 'static,
18>;
19
20/// A walk entry carrying caller-owned state assigned by `process_read_dir`.
21#[derive(Debug)]
22pub struct StatefulWalkEntry<E> {
23    entry: WalkEntry,
24    /// State retained with this entry after the directory callback returns.
25    pub state: E,
26    read_children: bool,
27}
28
29impl<E> StatefulWalkEntry<E> {
30    #[must_use]
31    pub const fn entry(&self) -> &WalkEntry {
32        &self.entry
33    }
34
35    #[must_use]
36    pub fn path(&self) -> &Path {
37        self.entry.path()
38    }
39
40    #[must_use]
41    pub const fn depth(&self) -> usize {
42        self.entry.depth()
43    }
44
45    #[must_use]
46    pub const fn is_file(&self) -> bool {
47        self.entry.is_file()
48    }
49
50    #[must_use]
51    pub const fn is_dir(&self) -> bool {
52        self.entry.is_dir()
53    }
54
55    #[must_use]
56    pub const fn read_children(&self) -> bool {
57        self.read_children
58    }
59
60    /// Controls whether traversal descends into this directory.
61    pub const fn set_read_children(&mut self, enabled: bool) {
62        self.read_children = enabled && self.entry.is_dir() && self.entry.skip_reason().is_none();
63    }
64
65    #[must_use]
66    pub fn into_parts(self) -> (WalkEntry, E) {
67        (self.entry, self.state)
68    }
69}
70
71/// Builder for an iterative DFS walk with per-directory and per-entry state.
72///
73/// `R` is cloned from a processed directory into each accepted child
74/// directory. `E` is attached independently to every yielded entry.
75pub struct StatefulWalkBuilder<R, E> {
76    root: PathBuf,
77    options: WalkOptions,
78    root_read_dir_state: R,
79    processor: Option<DirectoryProcessor<R, E>>,
80    parallelism: usize,
81    runtime: ParallelRuntime,
82}
83
84impl<R, E> StatefulWalkBuilder<R, E>
85where
86    R: Clone + Send + 'static,
87    E: Default + Send + 'static,
88{
89    #[must_use]
90    pub fn new(root: impl Into<PathBuf>, root_read_dir_state: R) -> Self {
91        Self {
92            root: root.into(),
93            options: WalkOptions::default(),
94            root_read_dir_state,
95            processor: None,
96            parallelism: 0,
97            runtime: ParallelRuntime::global(),
98        }
99    }
100
101    #[must_use]
102    pub const fn options(mut self, options: WalkOptions) -> Self {
103        self.options = options;
104        self
105    }
106
107    /// Sets parallel directory workers. Zero selects runtime parallelism.
108    #[must_use]
109    pub const fn with_parallelism(mut self, parallelism: usize) -> Self {
110        self.parallelism = parallelism;
111        self
112    }
113
114    /// Selects the executor used by parallel stateful traversal.
115    #[must_use]
116    pub fn runtime(mut self, runtime: ParallelRuntime) -> Self {
117        self.runtime = runtime;
118        self
119    }
120
121    /// Processes the complete immediate child batch before entries are yielded.
122    ///
123    /// The callback may sort or retain the vector, remove local errors, mutate
124    /// the inherited read-directory state, attach state to entries, and call
125    /// [`StatefulWalkEntry::set_read_children`] for per-entry pruning.
126    #[must_use]
127    pub fn process_read_dir<F>(mut self, processor: F) -> Self
128    where
129        F: Fn(usize, &Path, &mut R, &mut Vec<Result<StatefulWalkEntry<E>, WalkError>>)
130            + Send
131            + Sync
132            + 'static,
133    {
134        self.processor = Some(Arc::new(processor));
135        self
136    }
137
138    /// Builds the stateful iterator after validating the root.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error when the root cannot be resolved or inspected.
143    pub fn build(self) -> Result<StatefulWalker<R, E>, WalkError> {
144        StatefulWalker::new(self)
145    }
146
147    /// Builds a bounded parallel iterator with strict depth-first output.
148    ///
149    /// Directory callbacks run on the selected executor over complete child
150    /// batches. Their mutated directory state is cloned into accepted child
151    /// tasks, while yielded entries retain callback-assigned entry state.
152    ///
153    /// # Errors
154    ///
155    /// Returns a root-validation, coordinator-startup, or worker-submission
156    /// error.
157    pub fn build_parallel_ordered(
158        self,
159        capacity: usize,
160    ) -> Result<ParallelStatefulWalker<E>, WalkError> {
161        ParallelStatefulWalker::start(self, capacity)
162    }
163}
164
165struct DirectoryTask<R> {
166    path: PathBuf,
167    depth: usize,
168    identity: Option<DirectoryIdentity>,
169    ancestors: HashSet<DirectoryIdentity>,
170    read_state: R,
171}
172
173struct DirectoryFrame<R, E> {
174    entries: std::vec::IntoIter<Result<StatefulWalkEntry<E>, WalkError>>,
175    child_state: R,
176    ancestors: HashSet<DirectoryIdentity>,
177}
178
179/// Strict depth-first iterator produced by [`StatefulWalkBuilder`].
180pub struct StatefulWalker<R, E> {
181    root: Arc<PathBuf>,
182    root_file_system: Option<FileSystemId>,
183    options: WalkOptions,
184    processor: Option<DirectoryProcessor<R, E>>,
185    root_entry: Option<StatefulWalkEntry<E>>,
186    pending: Option<DirectoryTask<R>>,
187    frames: Vec<DirectoryFrame<R, E>>,
188}