use crate::filter::cls_gitignore_matcher::GitignoreMatcher;
use crate::filter::fnc_apply_filters::apply_filters;
use crate::reader::fnc_read_dir::read_dir;
use crate::{FileEntry, Result, TraversalOptions};
use std::collections::HashSet;
use std::path::PathBuf;
pub(super) struct StreamState {
stack: Vec<(PathBuf, usize)>,
max_depth: usize,
options: TraversalOptions,
gitignore: Option<GitignoreMatcher>,
visited: HashSet<PathBuf>,
current_entries: Vec<FileEntry>,
current_index: usize,
current_depth: usize,
}
impl StreamState {
pub(super) fn new(
root: PathBuf,
max_depth: usize,
options: TraversalOptions,
gitignore: Option<GitignoreMatcher>,
) -> Self {
let mut visited = HashSet::new();
if let Ok(canon) = root.canonicalize() {
visited.insert(canon);
}
Self {
stack: vec![(root, 0)],
max_depth,
options,
gitignore,
visited,
current_entries: Vec::new(),
current_index: 0,
current_depth: 0,
}
}
pub(super) async fn next_entry(&mut self) -> Option<(Result<FileEntry>, StreamState)> {
loop {
if self.current_index < self.current_entries.len() {
let entry = self.current_entries[self.current_index].clone();
self.current_index += 1;
if entry.is_dir && self.current_depth < self.max_depth {
let next_depth = self.current_depth + 1;
let should_visit = if let Ok(canon) = entry.path.canonicalize() {
if self.visited.contains(&canon) {
false
} else {
self.visited.insert(canon);
true
}
} else {
true
};
if should_visit {
self.stack.push((entry.path.clone(), next_depth));
}
}
return Some((
Ok(entry),
StreamState {
stack: std::mem::take(&mut self.stack),
max_depth: self.max_depth,
options: self.options.clone(),
gitignore: self.gitignore.clone(),
visited: std::mem::take(&mut self.visited),
current_entries: std::mem::take(&mut self.current_entries),
current_index: self.current_index,
current_depth: self.current_depth,
},
));
}
let (current_dir, depth) = match self.stack.pop() {
Some(dir) => dir,
None => return None, };
self.current_depth = depth;
let entries = match read_dir(¤t_dir).await {
Ok(entries) => entries,
Err(_) => {
continue;
}
};
let filtered: Vec<FileEntry> = entries
.into_iter()
.filter(|entry| {
apply_filters(entry, &self.options, self.gitignore.as_ref()).is_pass()
})
.collect();
self.current_entries = filtered;
self.current_index = 0;
}
}
}