Skip to main content

nu_command/platform/
dir_info.rs

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