dirstat_rs/
lib.rs

1use rayon::prelude::*;
2use serde::Serialize;
3use std::error::Error;
4use std::ffi::OsStr;
5use std::fs;
6use std::path::Path;
7
8mod ffi;
9
10#[derive(Serialize)]
11pub struct DiskItem {
12    pub name: String,
13    pub disk_size: u64,
14    pub children: Option<Vec<DiskItem>>,
15}
16
17impl DiskItem {
18    pub fn from_analyze(
19        path: &Path,
20        apparent: bool,
21        root_dev: u64,
22    ) -> Result<Self, Box<dyn Error>> {
23        let name = path
24            .file_name()
25            .unwrap_or(&OsStr::new("."))
26            .to_string_lossy()
27            .to_string();
28
29        let file_info = FileInfo::from_path(path, apparent)?;
30
31        match file_info {
32            FileInfo::Directory { volume_id } => {
33                if volume_id != root_dev {
34                    return Err("Filesystem boundary crossed".into());
35                }
36
37                let sub_entries = fs::read_dir(path)?
38                    .filter_map(Result::ok)
39                    .collect::<Vec<_>>();
40
41                let mut sub_items = sub_entries
42                    .par_iter()
43                    .filter_map(|entry| {
44                        DiskItem::from_analyze(&entry.path(), apparent, root_dev).ok()
45                    })
46                    .collect::<Vec<_>>();
47
48                sub_items.sort_unstable_by(|a, b| a.disk_size.cmp(&b.disk_size).reverse());
49
50                Ok(DiskItem {
51                    name,
52                    disk_size: sub_items.iter().map(|di| di.disk_size).sum(),
53                    children: Some(sub_items),
54                })
55            }
56            FileInfo::File { size, .. } => Ok(DiskItem {
57                name,
58                disk_size: size,
59                children: None,
60            }),
61        }
62    }
63}
64
65pub enum FileInfo {
66    File { size: u64, volume_id: u64 },
67    Directory { volume_id: u64 },
68}
69
70impl FileInfo {
71    #[cfg(unix)]
72    pub fn from_path(path: &Path, apparent: bool) -> Result<Self, Box<dyn Error>> {
73        use std::os::unix::fs::MetadataExt;
74
75        let md = path.symlink_metadata()?;
76        if md.is_dir() {
77            Ok(FileInfo::Directory {
78                volume_id: md.dev(),
79            })
80        } else {
81            let size = if apparent {
82                md.blocks() * 512
83            } else {
84                md.len()
85            };
86            Ok(FileInfo::File {
87                size,
88                volume_id: md.dev(),
89            })
90        }
91    }
92
93    #[cfg(windows)]
94    pub fn from_path(path: &Path, apparent: bool) -> Result<Self, Box<dyn Error>> {
95        use winapi_util::{file, Handle};
96        const FILE_ATTRIBUTE_DIRECTORY: u64 = 0x10;
97
98        let h = Handle::from_path_any(path)?;
99        let md = file::information(h)?;
100
101        if md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 {
102            Ok(FileInfo::Directory {
103                volume_id: md.volume_serial_number(),
104            })
105        } else {
106            let size = if apparent {
107                ffi::compressed_size(path)?
108            } else {
109                md.file_size()
110            };
111            Ok(FileInfo::File {
112                size,
113                volume_id: md.volume_serial_number(),
114            })
115        }
116    }
117}