Compare commits

...

17 Commits

Author SHA1 Message Date
andy.boot 9452049aff Increment version 2020-03-01 16:23:26 +00:00
andy.boot 09e3c27e70 Merge pull request #80 from bootandy/colours3
Rethink colors
2020-03-01 16:20:00 +00:00
andy.boot b0bfe654c4 Rethink colors
Use LS_COLORS to print default color of each file or folder

Keep showing largest sub folder in red.
2020-03-01 16:10:22 +00:00
andy.boot 6bc44de495 Merge pull request #74 from rasmushalland/win-perf
Avoid opening all files for reading on windows
2020-03-01 14:49:47 +00:00
Rasmus Halland efb455c739 Opening files on windows got a lot cheaper.
We avoid passing FILE_READ_DATA to CreateFile.
2020-03-01 14:41:21 +01:00
andy.boot 2c58041885 Clean up windows performance
Instead of generating random values for the drive and inode counter on
windows we return None instead
2020-03-01 14:41:21 +01:00
Rasmus Halland c30f31c22c Avoid opening all files for reading on windows
It can be very expensive to do that, especially when it causes windows defender to read the files and scan them.
2020-03-01 14:41:21 +01:00
andy.boot 59f2cdfb84 Merge pull request #79 from NeelChotai/colours
use LS_COLORS for largest subdirectories
2020-03-01 00:59:34 +00:00
Neel Chotai 795d91237d update args descriptions 2020-03-01 00:51:36 +00:00
Neel Chotai 26ae050f16 use LS_COLORS for directories 2020-03-01 00:51:32 +00:00
Neel Chotai 58b395e7ee fix compiler warning 2020-03-01 00:49:10 +00:00
andy.boot 3beb2b5274 Merge pull request #78 from bootandy/ab-fix-build
Maybe fix windows 8 support
2020-02-29 23:06:57 +00:00
andy.boot 19d938b05e Maybe fix windows 8 support
enable_ansi_support must be run for windows 10 but is not required and
indeed fails if run on windows < 10.

Disable the expect so the code might run on windows 8

may fix:
https://github.com/bootandy/dust/issues/70
2020-02-29 11:13:13 +00:00
andy.boot d4daa82297 Merge pull request #77 from bootandy/ab-fix-build
Ab fix build
2020-02-29 01:35:10 +00:00
andy.boot 21097578d9 Remove feature flag from build
The --features flag was empty which caused the build to fail. As the
features flag was not used it was removed.
2020-02-29 00:42:28 +00:00
andy.boot 069dc184a9 Merge pull request #73 from bootandy/readme
Update readme
2020-02-20 20:39:20 +00:00
andy.boot 02fa657128 Update readme 2020-02-20 20:36:35 +00:00
11 changed files with 269 additions and 149 deletions
+3 -3
View File
@@ -50,7 +50,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}" -- -D warnings
args: ${{ matrix.job.cargo-options }} -- -D warnings
min_version:
name: MinSRV # Minimum supported rust version
@@ -193,13 +193,13 @@ jobs:
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: build
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }}
- name: Test
uses: actions-rs/cargo@v1
with:
use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
command: test
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} --features "${{ matrix.job.features }}"
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }}
- name: Archive executable artifacts
uses: actions/upload-artifact@master
with:
Generated
+11 -1
View File
@@ -180,12 +180,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "du-dust"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_cli 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"terminal_size 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -265,6 +266,14 @@ name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lscolors"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memoffset"
version = "0.5.3"
@@ -560,6 +569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum jwalk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3dbf0a8f61baee43a2918ff50ac6a2d3b2c105bc08ed53bc298779f1263409"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum lscolors 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea3b3414b2d015c4fd689815f2551797f3c2296bb241dd709c7da233ec7cba4b"
"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
+2 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "du-dust"
description = "A more intuitive version of du"
version = "0.5.0"
version = "0.5.1"
authors = ["bootandy <bootandy@gmail.com>", "nebkor <code@ardent.nebcorp.com>"]
edition = "2018"
@@ -24,6 +24,7 @@ path = "src/main.rs"
ansi_term = "=0.12"
clap = "=2.33"
jwalk = "0.4.0"
lscolors = "0.6.0"
num_cpus = "1.12"
terminal_size = "0.1.10"
unicode-width = "0.1.7"
+22 -57
View File
@@ -3,31 +3,32 @@
# Dust
du + rust = dust. Like du but more intuitive
du + rust = dust. Like du but more intuitive.
# Why
Because I want an easy way to see where my disk is being used.
# Demo
![Example](media/snap.png)
## Install
#### Cargo Install
#### Cargo
* cargo install du-dust
* `cargo install du-dust`
#### Download Install
#### Download
* Download linux / mac binary from [Releases](https://github.com/bootandy/dust/releases)
* unzip file: tar -xvf _downloaded_file.tar.gz_
* move file to executable path: sudo mv dust /usr/local/bin/
* Download Linux/Mac binary from [Releases](https://github.com/bootandy/dust/releases)
* unzip file: `tar -xvf _downloaded_file.tar.gz`
* move file to executable path: `sudo mv dust /usr/local/bin/`
## Overview
Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of 1 'Did not have permissions message'.
Dust is meant to give you an instant overview of which directories are using disk space without requiring sort or head. Dust will print a maximum of one 'Did not have permissions message'.
Dust will list the terminal height - 10 biggest sub directories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest sub directory will have its size shown in *red*
## Why?
du has a number of ways of showing you what it finds, in terms of disk consumption, but really, there are only one or two ways you invoke it: with -h for “human readable” units, like 100G or 89k, or with -b for “bytes”. The former is generally used for a quick survey of a directory with a small number of things in it, and the latter for when you have a bunch and need to sort the output numerically, and youre obligated to either further pass it into something like awk to turn bytes into the appropriate human-friendly unit like mega or gigabytes, or pipe thru sort and head while remembering the '-h' flag. Then once you have the top offenders, you recurse down into the largest one and repeat the process until youve found your cruft or gems and can move on.
Dust assumes thats what you wanted to do in the first place, and takes care of tracking the largest offenders in terms of actual size, and showing them to you with human-friendly units and in-context within the filetree.
Dust will list a slightly-less-than-the-terminal-height number of the biggest subdirectories or files and will smartly recurse down the tree to find the larger ones. There is no need for a '-d' flag or a '-h' flag. The largest subdirectories will be colored.
## Usage
@@ -37,48 +38,12 @@ Usage: dust <dir>
Usage: dust <dir> <another_dir> <and_more>
Usage: dust -p <dir> (full-path - does not shorten the path of the subdirectories)
Usage: dust -s <dir> (apparent-size - shows the length of the file as opposed to the amount of disk space it uses)
Usage: dust -n 30 <dir> (Shows 30 directories not 20)
Usage: dust -d 3 <dir> (Shows 3 levels of subdirectories)
Usage: dust -r <dir> (Reverse order of output, with root at the lowest)
Usage: dust -x <dir> (Only show directories on same filesystem)
Usage: dust -X ignore <dir> (Ignore all files and directories with the name 'ignore')
Usage: dust -b <dir> (Do not show percentages or draw the ASCII bars)
```
```
$ dust target
15M ┌── build │ ░█ │ 2%
25M ├── deps │ ░█ │ 4%
45M ┌─┴ release │ ██ │ 7%
84M │ ┌── build │ ▒▒▒▒▒████ │ 13%
7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib│ ▒▒▒▓▓▓▓▓█ │ 1%
16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so│ ▒▒▒▓▓▓▓▓█ │ 2%
18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
19M │ │ ├── libsyn-d4a3458fcb1c592c.rlib │ ▒▒▒▓▓▓▓▓█ │ 3%
135M │ ├─┴ deps │ ▒▒▒██████ │ 20%
228M │ ┌─┴ debug │ █████████ │ 34%
228M ├─┴ rls │ █████████ │ 34%
18M │ ┌── dust │ ░░░░░░░░░░░░░░█ │ 3%
22M │ ├── dust-a0c31c4633c5fc8b │ ░░░░░░░░░░░░░░█ │ 3%
7.4M │ │ ┌── s-fkrj3vfncf-19aj951-1fv3o6tzvr348 │ ░░░░░░░░░░░░░▒█ │ 1%
7.4M │ │ ┌─┴ dust-1i3xquz5fns51 │ ░░░░░░░░░░░░░▒█ │ 1%
40M │ ├─┴ incremental │ ░░░░░░░░░░░░░██ │ 6%
41M │ ├── build │ ░░░░░░░░░░░░░██ │ 6%
7.6M │ │ ┌── libsynstructure-f7552412787ad339.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
8.2M │ │ ├── libserde-ab4b407a415bc8fc.rmeta │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
9.4M │ │ ├── libserde-ab4b407a415bc8fc.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 1%
11M │ │ ├── tests_symlinks-bf063461b7be6a99 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
11M │ │ ├── integration-08f999d253e3b70c │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
15M │ │ ├── dust-1c6e63725d641738 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
16M │ │ ├── libfailure_derive-e18365d3e6be2e2c.so │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 2%
18M │ │ ├── dust-3a419f62b84d73c1 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
18M │ │ ├── dust-2bdf724d4a721d31 │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
18M │ │ ├── libsyn-9ad95b745845d5dd.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
23M │ │ ├── libclap-0dedc35af3ef0670.rlib │ ░░░░▒▒▒▒▒▒▒▒▒▒█ │ 3%
267M │ ├─┴ deps │ ░░░░███████████ │ 40%
392M ├─┴ debug │ ███████████████ │ 59%
667M ┌─┴ target │█████████████████████████ │ 100%
Usage: dust -n 30 <dir> (shows 30 directories instead of the default)
Usage: dust -d 3 <dir> (shows 3 levels of subdirectories)
Usage: dust -r <dir> (reverse order of output, with root at the lowest)
Usage: dust -x <dir> (only show directories on the same filesystem)
Usage: dust -X ignore <dir> (ignore all files and directories with the name 'ignore')
Usage: dust -b <dir> (do not show percentages or draw ASCII bars)
```
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+39 -20
View File
@@ -1,15 +1,17 @@
extern crate ansi_term;
use self::ansi_term::Colour::Fixed;
use self::ansi_term::Style;
use crate::utils::Node;
use self::ansi_term::Colour::Fixed;
use lscolors::{LsColors, Style};
use terminal_size::{terminal_size, Height, Width};
use unicode_width::UnicodeWidthStr;
use std::cmp::max;
use std::cmp::min;
use std::fs;
use std::iter::repeat;
use std::path::Path;
@@ -23,6 +25,7 @@ pub struct DisplayData {
pub colors_on: bool,
pub base_size: u64,
pub longest_string_length: usize,
pub ls_colors: LsColors,
}
impl DisplayData {
@@ -167,6 +170,7 @@ pub fn draw_it(
colors_on: !no_colors,
base_size: c.size,
longest_string_length,
ls_colors: LsColors::from_env().unwrap_or_default(),
};
let draw_data = DrawData {
indent: "".to_string(),
@@ -180,7 +184,8 @@ pub fn draw_it(
// We can probably pass depth instead of indent here.
// It is ugly to feed in ' ' instead of the actual tree characters but we don't need them yet.
fn find_longest_dir_name(node: &Node, indent: &str, long_paths: bool) -> usize {
let longest = UnicodeWidthStr::width(&*get_printable_name(&node.name, long_paths, indent));
let printable_name = get_printable_name(&node.name, long_paths);
let longest = get_unicode_width_of_indent_and_name(indent, &printable_name);
// each none root tree drawing is 2 chars
let full_indent: String = indent.to_string() + " ";
@@ -243,7 +248,7 @@ fn clean_indentation_string(s: &str) -> String {
is
}
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool, indentation: &str) -> String {
fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool) -> String {
let dir_name = dir_name.as_ref();
let printable_name = {
if long_paths {
@@ -258,7 +263,12 @@ fn get_printable_name<P: AsRef<Path>>(dir_name: &P, long_paths: bool, indentatio
dir_name
}
};
format!("{} {}", indentation, printable_name.display())
printable_name.display().to_string()
}
fn get_unicode_width_of_indent_and_name(indent: &str, name: &str) -> usize {
let indent_and_name = format!("{} {}", indent, name);
UnicodeWidthStr::width(&*indent_and_name)
}
pub fn format_string(
@@ -270,15 +280,14 @@ pub fn format_string(
) -> String {
let pretty_size = format!("{:>5}", human_readable_number(node.size));
let percent_size = display_data.percent_size(node);
let percent_size_str = format!("{:.0}%", percent_size * 100.0);
let percent_size_str = format!("{:.0}%", display_data.percent_size(node) * 100.0);
let tree_and_path = get_printable_name(&node.name, display_data.short_paths, &*indent);
let name = get_printable_name(&node.name, display_data.short_paths);
let width = get_unicode_width_of_indent_and_name(indent, &name);
let printable_chars = UnicodeWidthStr::width(&*tree_and_path);
let tree_and_path = tree_and_path
let name_and_padding = name
+ &(repeat(" ")
.take(display_data.longest_string_length - printable_chars)
.take(display_data.longest_string_length - width)
.collect::<String>());
let percents = if percent_bar != "" {
@@ -287,16 +296,26 @@ pub fn format_string(
"".into()
};
format!(
"{} {}{}",
if is_biggest && display_data.colors_on {
Fixed(196).paint(pretty_size)
let pretty_size = if is_biggest && display_data.colors_on {
format!("{}", Fixed(196).paint(pretty_size))
} else {
Style::new().paint(pretty_size)
},
tree_and_path,
percents,
)
pretty_size
};
let pretty_name = if display_data.colors_on {
let meta_result = fs::metadata(node.name.clone());
let directory_color = display_data
.ls_colors
.style_for_path_with_metadata(node.name.clone(), meta_result.as_ref().ok());
let ansi_style = directory_color
.map(Style::to_ansi_term_style)
.unwrap_or_default();
format!("{}", ansi_style.paint(name_and_padding))
} else {
name_and_padding
};
format!("{} {} {}{}", pretty_size, indent, pretty_name, percents)
}
fn human_readable_number(size: u64) -> String {
+26 -15
View File
@@ -16,12 +16,24 @@ mod utils;
static DEFAULT_NUMBER_OF_LINES: usize = 30;
#[cfg(windows)]
fn init_color() {
ansi_term::enable_ansi_support().expect("Couldn't enable color support");
fn init_color(no_color: bool) -> bool {
// Required for windows 10
// Fails to resolve for windows 8 so disable color
match ansi_term::enable_ansi_support() {
Ok(_) => no_color,
Err(_) => {
eprintln!(
"This version of Windows does not support ANSI colors, setting no_color flag"
);
true
}
}
}
#[cfg(not(windows))]
fn init_color() {}
fn init_color(no_color: bool) -> bool {
no_color
}
fn get_height_of_terminal() -> usize {
// Windows CI runners detect a terminal height of 0
@@ -33,8 +45,6 @@ fn get_height_of_terminal() -> usize {
}
fn main() {
init_color();
let default_height = get_height_of_terminal();
let def_num_str = default_height.to_string();
@@ -68,7 +78,7 @@ fn main() {
Arg::with_name("display_full_paths")
.short("p")
.long("full-paths")
.help("If set sub directories will not have their path shortened"),
.help("Subdirectories will not have their path shortened"),
)
.arg(
Arg::with_name("ignore_directory")
@@ -77,37 +87,37 @@ fn main() {
.takes_value(true)
.number_of_values(1)
.multiple(true)
.help("Exclude any file or directory with this name."),
.help("Exclude any file or directory with this name"),
)
.arg(
Arg::with_name("limit_filesystem")
.short("x")
.long("limit-filesystem")
.help("Only count the files and directories in the same filesystem as the supplied directory"),
.help("Only count the files and directories on the same filesystem as the supplied directory"),
)
.arg(
Arg::with_name("display_apparent_size")
.short("s")
.long("apparent-size")
.help("If set will use file length. Otherwise we use blocks"),
.help("Use file length instead of blocks"),
)
.arg(
Arg::with_name("reverse")
.short("r")
.long("reverse")
.help("If applied tree will be printed upside down (biggest highest)"),
.help("Print tree upside down (biggest highest)"),
)
.arg(
Arg::with_name("no_colors")
.short("c")
.long("no_colors")
.help("If applied no colors will be printed (normally largest directories are marked in red"),
.long("no-colors")
.help("No colors will be printed (normally largest directories are colored)"),
)
.arg(
Arg::with_name("no_bars")
.short("b")
.long("no_percent_bars")
.help("If applied no percent bars or percents will be displayed"),
.long("no-percent-bars")
.help("No percent bars or percentages will be displayed"),
)
.arg(Arg::with_name("inputs").multiple(true))
.get_matches();
@@ -157,6 +167,7 @@ fn main() {
return;
}
let no_colors = init_color(options.is_present("no_colors"));
let use_apparent_size = options.is_present("display_apparent_size");
let limit_filesystem = options.is_present("limit_filesystem");
let ignore_directories = match options.values_of("ignore_directory") {
@@ -185,7 +196,7 @@ fn main() {
permissions,
options.is_present("display_full_paths"),
!options.is_present("reverse"),
options.is_present("no_colors"),
no_colors,
options.is_present("no_bars"),
tree,
);
+39 -19
View File
@@ -41,7 +41,7 @@ impl PartialEq for Node {
pub fn is_a_parent_of<P: AsRef<Path>>(parent: P, child: P) -> bool {
let parent = parent.as_ref();
let child = child.as_ref();
(child.starts_with(parent) && !parent.starts_with(child))
child.starts_with(parent) && !parent.starts_with(child)
}
pub fn simplify_dir_names<P: AsRef<Path>>(filenames: Vec<P>) -> HashSet<PathBuf> {
@@ -85,15 +85,18 @@ pub fn get_dir_tree<P: AsRef<Path>>(
None
};
let mut examine_dir_args = ExamineDirMutArsg {
data: &mut data,
file_count_no_permission: &mut permissions,
};
for b in top_level_names.iter() {
examine_dir(
b,
apparent_size,
&restricted_filesystems,
ignore_directories,
&mut data,
&mut permissions,
threads,
&mut examine_dir_args,
);
}
(permissions == 0, data)
@@ -119,14 +122,18 @@ pub fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
path.as_ref().components().collect::<PathBuf>()
}
struct ExamineDirMutArsg<'a> {
data: &'a mut HashMap<PathBuf, u64>,
file_count_no_permission: &'a mut u64,
}
fn examine_dir<P: AsRef<Path>>(
top_dir: P,
apparent_size: bool,
filesystems: &Option<HashSet<u64>>,
ignore_directories: &Option<Vec<PathBuf>>,
data: &mut HashMap<PathBuf, u64>,
file_count_no_permission: &mut u64,
threads: Option<usize>,
mut_args: &mut ExamineDirMutArsg,
) {
let top_dir = top_dir.as_ref();
let mut inodes: HashSet<(u64, u64)> = HashSet::new();
@@ -136,9 +143,11 @@ fn examine_dir<P: AsRef<Path>>(
if let Some(threads_to_start) = threads {
iter = iter.num_threads(threads_to_start);
}
'entry: for entry in iter {
if let Ok(e) = entry {
let maybe_size_and_inode = get_metadata(&e, apparent_size);
if let Some(dirs) = ignore_directories {
let path = e.path();
let parts = path.components().collect::<Vec<std::path::Component>>();
@@ -154,15 +163,16 @@ fn examine_dir<P: AsRef<Path>>(
}
match maybe_size_and_inode {
Some((size, inode, device)) => {
if !should_ignore_file(apparent_size, filesystems, &mut inodes, inode, device) {
process_file_with_size_and_inode(top_dir, data, e, size)
Some(data) => {
let (size, inode_device) = data;
if !should_ignore_file(apparent_size, filesystems, &mut inodes, inode_device) {
process_file_with_size_and_inode(top_dir, mut_args.data, e, size)
}
}
None => *file_count_no_permission += 1,
None => *mut_args.file_count_no_permission += 1,
}
} else {
*file_count_no_permission += 1
*mut_args.file_count_no_permission += 1
}
}
}
@@ -171,9 +181,12 @@ fn should_ignore_file(
apparent_size: bool,
restricted_filesystems: &Option<HashSet<u64>>,
inodes: &mut HashSet<(u64, u64)>,
inode: u64,
device: u64,
maybe_inode_device: Option<(u64, u64)>,
) -> bool {
match maybe_inode_device {
None => false,
Some(data) => {
let (inode, device) = data;
// Ignore files on different devices (if flag applied)
if let Some(rs) = restricted_filesystems {
if !rs.contains(&device) {
@@ -189,6 +202,8 @@ fn should_ignore_file(
inodes.insert((inode, device));
}
false
}
}
}
fn process_file_with_size_and_inode<P: AsRef<Path>>(
@@ -352,14 +367,19 @@ mod tests {
let mut files = HashSet::new();
files.insert((10, 20));
assert!(!should_ignore_file(true, &None, &mut files, 0, 0));
assert!(!should_ignore_file(true, &None, &mut files, Some((0, 0))));
// New file is not known it will be inserted to the hashmp and should not be ignored
assert!(!should_ignore_file(false, &None, &mut files, 11, 12));
assert!(!should_ignore_file(
false,
&None,
&mut files,
Some((11, 12))
));
assert!(files.contains(&(11, 12)));
// The same file will be ignored the second time
assert!(should_ignore_file(false, &None, &mut files, 11, 12));
assert!(should_ignore_file(false, &None, &mut files, Some((11, 12))));
}
#[test]
@@ -373,11 +393,11 @@ mod tests {
// If we are looking at a different device (disk) and the device flag is set
// then apparent_size is irrelevant - we ignore files on other devices
assert!(should_ignore_file(false, &od, &mut files, 11, 12));
assert!(should_ignore_file(true, &od, &mut files, 11, 12));
assert!(should_ignore_file(false, &od, &mut files, Some((11, 12))));
assert!(should_ignore_file(true, &od, &mut files, Some((11, 12))));
// We do not ignore files on the same device
assert!(!should_ignore_file(false, &od, &mut files, 2, 99));
assert!(!should_ignore_file(true, &od, &mut files, 2, 99));
assert!(!should_ignore_file(false, &od, &mut files, Some((2, 99))));
assert!(!should_ignore_file(true, &od, &mut files, Some((2, 99))));
}
}
+95 -9
View File
@@ -12,30 +12,116 @@ fn get_block_size() -> u64 {
}
#[cfg(target_family = "unix")]
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, u64, u64)> {
pub fn get_metadata(d: &DirEntry, use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
use std::os::unix::fs::MetadataExt;
d.metadata.as_ref().unwrap().as_ref().ok().map(|md| {
if use_apparent_size {
(md.len(), md.ino(), md.dev())
(md.len(), Some((md.ino(), md.dev())))
} else {
(md.blocks() * get_block_size(), md.ino(), md.dev())
(md.blocks() * get_block_size(), Some((md.ino(), md.dev())))
}
})
}
#[cfg(target_family = "windows")]
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, u64, u64)> {
use winapi_util::file::information;
use winapi_util::Handle;
pub fn get_metadata(d: &DirEntry, _use_apparent_size: bool) -> Option<(u64, Option<(u64, u64)>)> {
// On windows opening the file to get size, file ID and volume can be very
// expensive because 1) it causes a few system calls, and more importantly 2) it can cause
// windows defender to scan the file.
// Therefore we try to avoid doing that for common cases, mainly those of
// plain files:
let h = Handle::from_path_any(d.path()).ok()?;
// The idea is to make do with the file size that we get from the OS for
// free as part of iterating a folder. Therefore we want to make sure that
// it makes sense to use that free size information:
// Volume boundaries:
// The user can ask us not to cross volume boundaries. If the DirEntry is a
// plain file and not a reparse point or other non-trivial stuff, we assume
// that the file is located on the same volume as the directory that
// contains it.
// File ID:
// This optimization does deprive us of access to a file ID. As a
// workaround, we just make one up that hopefully does not collide with real
// file IDs.
// Hard links: Unresolved. We don't get inode/file index, so hard links
// count once for each link. Hopefully they are not too commonly in use on
// windows.
// Size:
// We assume (naively?) that for the common cases the free size info is the
// same as one would get by doing the expensive thing. Sparse, encrypted and
// compressed files are not included in the common cases, as one can image
// there being more than view on their size.
// Savings in orders of magnitude in terms of time, io and cpu have been
// observed on hdd, windows 10, some 100Ks files taking up some hundreds of
// GBs:
// Consistently opening the file: 30 minutes.
// With this optimization: 8 sec.
use winapi_util::Handle;
fn handle_from_path_limited<P: AsRef<Path>>(path: P) -> io::Result<Handle> {
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
const FILE_READ_ATTRIBUTES: u32 = 0x0080;
// So, it seems that it does does have to be that expensive to open
// files to get their info: Avoiding opening the file with the full
// GENERIC_READ is key:
// https://docs.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights:
// "For example, a Windows file object maps the GENERIC_READ bit to the
// READ_CONTROL and SYNCHRONIZE standard access rights and to the
// FILE_READ_DATA, FILE_READ_EA, and FILE_READ_ATTRIBUTES
// object-specific access rights"
// The flag FILE_READ_DATA seems to be the expensive one, so we'll avoid
// that, and a most of the other ones. Simply because it seems that we
// don't need them.
let file = OpenOptions::new()
.access_mode(FILE_READ_ATTRIBUTES)
.open(path)?;
Ok(Handle::from_file(file))
}
fn get_metadata_expensive(d: &DirEntry) -> Option<(u64, Option<(u64, u64)>)> {
use winapi_util::file::information;
let h = handle_from_path_limited(d.path()).ok()?;
let info = information(&h).ok()?;
Some((
info.file_size(),
info.file_index(),
info.volume_serial_number(),
Some((info.file_index(), info.volume_serial_number())),
))
}
match d.metadata {
Some(Ok(ref md)) => {
use std::os::windows::fs::MetadataExt;
const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20u32;
const FILE_ATTRIBUTE_READONLY: u32 = 0x1u32;
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2u32;
const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4u32;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80u32;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10u32;
let attr_filtered = md.file_attributes()
& !(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
if attr_filtered == FILE_ATTRIBUTE_ARCHIVE
|| attr_filtered == FILE_ATTRIBUTE_DIRECTORY
|| md.file_attributes() == FILE_ATTRIBUTE_NORMAL
{
Some((md.len(), None))
} else {
get_metadata_expensive(&d)
}
}
_ => get_metadata_expensive(&d),
}
}
#[cfg(target_family = "unix")]
+12 -4
View File
@@ -7,13 +7,21 @@ pub fn test_basic_output() {
assert_cli::Assert::main_binary()
.with_args(&["src/test_dir/"])
.stdout()
.contains(" ┌─┴ test_dir ")
.contains(" ┌─┴ ")
.stdout()
.contains(" ┌─┴ many ")
.contains("test_dir ")
.stdout()
.contains(" ├── hello_file")
.contains(" ┌─┴ ")
.stdout()
.contains(" ┌── a_file ")
.contains("many ")
.stdout()
.contains(" ├── ")
.stdout()
.contains("hello_file")
.stdout()
.contains(" ┌── ")
.stdout()
.contains("a_file ")
.unwrap();
}
+4 -4
View File
@@ -38,7 +38,7 @@ pub fn test_soft_sym_link() {
let a = format!("─┴ {}", dir_s);
assert_cli::Assert::main_binary()
.with_args(&["-p", &dir_s])
.with_args(&["-p", "-c", &dir_s])
.stdout()
.contains(a.as_str())
.stdout()
@@ -72,7 +72,7 @@ pub fn test_hard_sym_link() {
// we cannot guarantee which version will appear first.
let result = panic::catch_unwind(|| {
assert_cli::Assert::main_binary()
.with_args(&["-p", dir_s])
.with_args(&["-p", "-c", dir_s])
.stdout()
.contains(a.as_str())
.stdout()
@@ -81,7 +81,7 @@ pub fn test_hard_sym_link() {
});
if result.is_err() {
assert_cli::Assert::main_binary()
.with_args(&["-p", dir_s])
.with_args(&["-p", "-c", dir_s])
.stdout()
.contains(a.as_str())
.stdout()
@@ -110,7 +110,7 @@ pub fn test_recursive_sym_link() {
let b = format!(" └── {}", link_name_s);
assert_cli::Assert::main_binary()
.with_args(&["-r", "-p", dir_s])
.with_args(&["-c", "-r", "-p", dir_s])
.stdout()
.contains(a.as_str())
.stdout()