1use std::path::{Path, PathBuf};
8
9use filetime::FileTime;
10use rayon::prelude::*;
11
12use crate::filter::Filter;
13
14use crate::meta::{FileTypeKind, meta_min};
15use crate::{Error, Result, RunControl};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum EntryKind {
20 Dir,
22 File,
24 Symlink(PathBuf),
26}
27
28#[derive(Debug, Clone)]
30pub struct Entry {
31 pub rel: PathBuf,
33 pub kind: EntryKind,
35 pub len: u64,
37 pub mtime: FileTime,
39 pub mode: u32,
41 pub ino: u64,
43 pub dev: u64,
45 pub uid: u32,
47 pub gid: u32,
49}
50
51impl Entry {
52 #[must_use]
54 pub fn is_file(&self) -> bool {
55 matches!(self.kind, EntryKind::File)
56 }
57
58 #[must_use]
60 pub fn is_dir(&self) -> bool {
61 matches!(self.kind, EntryKind::Dir)
62 }
63}
64
65pub fn walk(root: &Path, threads: usize, filter: &Filter) -> Result<Vec<Entry>> {
76 walk_controlled(root, threads, filter, &RunControl::default())
77}
78
79pub fn walk_controlled(
85 root: &Path,
86 threads: usize,
87 filter: &Filter,
88 control: &RunControl,
89) -> Result<Vec<Entry>> {
90 let parallelism = if threads == 0 {
91 jwalk::Parallelism::RayonDefaultPool {
92 busy_timeout: std::time::Duration::from_secs(1),
93 }
94 } else {
95 jwalk::Parallelism::RayonNewPool(threads)
96 };
97
98 let mut entries = Vec::new();
99
100 for dent in jwalk::WalkDir::new(root)
101 .parallelism(parallelism)
102 .skip_hidden(false)
103 .follow_links(false)
104 {
105 control.checkpoint()?;
106 let dent = dent.map_err(|e| Error::io(root, std::io::Error::other(e.to_string())))?;
107 let path = dent.path();
108 if path == root {
109 continue;
110 }
111 let rel = path
112 .strip_prefix(root)
113 .map_err(|_| Error::Containment(path.clone()))?
114 .to_path_buf();
115
116 if !filter.is_empty() && filter.is_excluded(&rel, dent.file_type().is_dir()) {
117 continue;
118 }
119
120 let m = meta_min(&path)?;
121 let kind = match m.kind {
122 FileTypeKind::Symlink => {
123 let target = std::fs::read_link(&path).map_err(|e| Error::io(&path, e))?;
124 EntryKind::Symlink(target)
125 }
126 FileTypeKind::Dir => EntryKind::Dir,
127 FileTypeKind::File => EntryKind::File,
128 FileTypeKind::Other => continue,
130 };
131
132 let len = if matches!(kind, EntryKind::File) {
133 m.len
134 } else {
135 0
136 };
137
138 entries.push(Entry {
139 rel,
140 kind,
141 len,
142 mtime: m.mtime,
143 mode: m.mode,
144 ino: m.ino,
145 dev: m.dev,
146 uid: m.uid,
147 gid: m.gid,
148 });
149 }
150
151 entries.par_sort_unstable_by(|a, b| a.rel.cmp(&b.rel));
152 Ok(entries)
153}