1use filesize::file_real_size_fast;
2use nu_glob::Pattern;
3use nu_protocol::{ShellError, Signals, Span, Value, record, shell_error::io::IoError};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct DirBuilder {
8 pub tag: Span,
9 pub min: Option<u64>,
10 pub deref: bool,
11 pub exclude: Option<Pattern>,
12 pub long: bool,
13}
14
15impl DirBuilder {
16 pub fn new(
17 tag: Span,
18 min: Option<u64>,
19 deref: bool,
20 exclude: Option<Pattern>,
21 long: bool,
22 ) -> DirBuilder {
23 DirBuilder {
24 tag,
25 min,
26 deref,
27 exclude,
28 long,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
34pub struct DirInfo {
35 dirs: Vec<DirInfo>,
36 files: Vec<FileInfo>,
37 errors: Vec<ShellError>,
38 size: u64,
39 blocks: u64,
40 path: PathBuf,
41 tag: Span,
42 long: bool,
43}
44
45#[derive(Debug, Clone)]
46pub struct FileInfo {
47 path: PathBuf,
48 size: u64,
49 blocks: Option<u64>,
50 tag: Span,
51 long: bool,
52}
53
54impl FileInfo {
55 pub fn new(
56 path: impl Into<PathBuf>,
57 deref: bool,
58 tag: Span,
59 long: bool,
60 ) -> Result<Self, ShellError> {
61 let path = path.into();
62 let m = if deref {
63 std::fs::metadata(&path)
64 } else {
65 std::fs::symlink_metadata(&path)
66 };
67
68 match m {
69 Ok(d) => {
70 let block_size = file_real_size_fast(&path, &d).ok();
71
72 Ok(FileInfo {
73 path,
74 blocks: block_size,
75 size: d.len(),
76 tag,
77 long,
78 })
79 }
80 Err(e) => Err(IoError::new(e, tag, path).into()),
81 }
82 }
83}
84
85impl DirInfo {
86 pub fn new(
87 path: impl Into<PathBuf>,
88 params: &DirBuilder,
89 depth: Option<u64>,
90 span: Span,
91 signals: &Signals,
92 ) -> Result<Self, ShellError> {
93 let path = path.into();
94 let from_io_error = IoError::factory(span, path.as_path());
95
96 let mut s = Self {
97 dirs: Vec::new(),
98 errors: Vec::new(),
99 files: Vec::new(),
100 size: 0,
101 blocks: 0,
102 tag: params.tag,
103 path: path.clone(),
104 long: params.long,
105 };
106
107 match std::fs::metadata(&s.path) {
108 Ok(d) => {
109 s.size = d.len(); s.blocks = file_real_size_fast(&s.path, &d).ok().unwrap_or(0);
111 }
112 Err(e) => s = s.add_error(from_io_error(e).into()),
113 };
114
115 match std::fs::read_dir(&s.path) {
116 Ok(d) => {
117 for f in d {
118 signals.check(span)?;
119
120 match f {
121 Ok(i) => match i.file_type() {
122 Ok(t) if t.is_dir() => {
123 s = s.add_dir(i.path(), depth, params, span, signals)?
124 }
125 Ok(_t) => s = s.add_file(i.path(), params),
126 Err(e) => s = s.add_error(from_io_error(e).into()),
127 },
128 Err(e) => s = s.add_error(from_io_error(e).into()),
129 }
130 }
131 }
132 Err(e) => s = s.add_error(from_io_error(e).into()),
133 }
134 Ok(s)
135 }
136
137 fn add_dir(
138 mut self,
139 path: impl Into<PathBuf>,
140 mut depth: Option<u64>,
141 params: &DirBuilder,
142 span: Span,
143 signals: &Signals,
144 ) -> Result<Self, ShellError> {
145 if let Some(current) = depth {
146 if let Some(new) = current.checked_sub(1) {
147 depth = Some(new);
148 } else {
149 return Ok(self);
150 }
151 }
152
153 let d = DirInfo::new(path, params, depth, span, signals)?;
154 self.size += d.size;
155 self.blocks += d.blocks;
156 self.dirs.push(d);
157 Ok(self)
158 }
159
160 fn add_file(mut self, f: impl Into<PathBuf>, params: &DirBuilder) -> Self {
161 let f = f.into();
162 let include = params.exclude.as_ref().is_none_or(|x| !x.matches_path(&f));
163 if include {
164 match FileInfo::new(f, params.deref, self.tag, self.long) {
165 Ok(file) => {
166 let inc = params.min.is_none_or(|s| file.size >= s);
167 if inc {
168 self.size += file.size;
169 self.blocks += file.blocks.unwrap_or(0);
170 if params.long {
171 self.files.push(file);
172 }
173 }
174 }
175 Err(e) => self = self.add_error(e),
176 }
177 }
178 self
179 }
180
181 fn add_error(mut self, e: ShellError) -> Self {
182 self.errors.push(e);
183 self
184 }
185
186 pub fn get_size(&self) -> u64 {
187 self.size
188 }
189}
190
191impl From<DirInfo> for Value {
192 fn from(d: DirInfo) -> Self {
193 if d.long {
208 Value::record(
209 record! {
210 "path" => Value::string(d.path.display().to_string(), d.tag),
211 "apparent" => Value::filesize(d.size as i64, d.tag),
212 "physical" => Value::filesize(d.blocks as i64, d.tag),
213 "directories" => value_from_vec(d.dirs, d.tag),
214 "files" => value_from_vec(d.files, d.tag)
215 },
216 d.tag,
217 )
218 } else {
219 Value::record(
220 record! {
221 "path" => Value::string(d.path.display().to_string(), d.tag),
222 "apparent" => Value::filesize(d.size as i64, d.tag),
223 "physical" => Value::filesize(d.blocks as i64, d.tag),
224 },
225 d.tag,
226 )
227 }
228 }
229}
230
231impl From<FileInfo> for Value {
232 fn from(f: FileInfo) -> Self {
233 if f.long {
237 Value::record(
238 record! {
239 "path" => Value::string(f.path.display().to_string(), f.tag),
240 "apparent" => Value::filesize(f.size as i64, f.tag),
241 "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
242 "directories" => Value::nothing(Span::unknown()),
243 "files" => Value::nothing(Span::unknown()),
244 },
245 f.tag,
246 )
247 } else {
248 Value::record(
249 record! {
250 "path" => Value::string(f.path.display().to_string(), f.tag),
251 "apparent" => Value::filesize(f.size as i64, f.tag),
252 "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
253 },
254 f.tag,
255 )
256 }
257 }
258}
259
260fn value_from_vec<V>(vec: Vec<V>, tag: Span) -> Value
261where
262 V: Into<Value>,
263{
264 if vec.is_empty() {
265 Value::nothing(tag)
266 } else {
267 let values = vec.into_iter().map(Into::into).collect::<Vec<Value>>();
268 Value::list(values, tag)
269 }
270}