diff --git a/Cargo.toml b/Cargo.toml index d175e89..588df2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dust" -version = "0.1.0" -authors = ["bootandy "] +version = "0.1.1" +authors = ["bootandy ", "nebkor "] [dependencies] ansi_term = "0.11" diff --git a/README.md b/README.md index 142722b..5d67764 100644 --- a/README.md +++ b/README.md @@ -16,27 +16,22 @@ Usage: dust -n 30 (Shows 30 directories not 15) ``` -dust . - 161M . - 160M └── ./target - 123M ├── ./target/debug - 83M │ ├── ./target/debug/deps - 16M │ │ ├── ./target/debug/deps/libclap-82e6176feef5d4b7.rlib - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents - 8.6M │ │ └── ./target/debug/deps/dust-993f7d919d92f0f8.dSYM/Contents/Resources - 27M │ ├── ./target/debug/incremental - 12M │ └── ./target/debug/build - 20M ├── ./target/x86_64-apple-darwin - 20M │ └── ./target/x86_64-apple-darwin/debug - 20M │ └── ./target/x86_64-apple-darwin/debug/deps - 16M │ └── ./target/x86_64-apple-darwin/debug/deps/libclap-7e3f8513c52cd558.rlib - 16M └── ./target/release - 13M └── ./target/release/deps +djin:git/dust> dust + 65M . + 65M └─┬ ./target + 49M ├─┬ ./target/debug + 26M │ ├─┬ ./target/debug/deps + 21M │ │ └── ./target/debug/deps/libclap-9e6625ac8ff074ad.rlib + 13M │ ├── ./target/debug/dust + 8.9M │ └─┬ ./target/debug/incremental + 6.7M │ ├─┬ ./target/debug/incremental/dust-2748eiei2tcnp + 6.7M │ │ └─┬ ./target/debug/incremental/dust-2748eiei2tcnp/s-ezd6jnik5u-163pyem-1aab9ncf5glum + 3.0M │ │ └── ./target/debug/incremental/dust-2748eiei2tcnp/s-ezd6jnik5u-163pyem-1aab9ncf5glum/dep-graph.bin + 2.2M │ └─┬ ./target/debug/incremental/dust-1dlon65p8m3vl + 2.2M │ └── ./target/debug/incremental/dust-1dlon65p8m3vl/s-ezd6jncecv-1xsnfd0-4dw9l1r2th2t + 15M └─┬ ./target/release + 9.2M ├─┬ ./target/release/deps + 6.7M │ └── ./target/release/deps/libclap-87bc2534ea57f044.rlib + 5.9M └── ./target/release/dust ``` - Performance: dust is currently about 4 times slower than du. - - - - diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..68555e0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,85 @@ +use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; + +#[derive(Clone, Debug)] +pub struct Node { + entry: DirEnt, + children: Vec, +} + +#[derive(Clone, Debug)] +pub struct DirEnt { + name: String, + size: u64, +} + +impl Node { + pub fn new(entry: DirEnt, children: Vec) -> Self { + Node { + entry: entry, + children: children, + } + } + + pub fn children(&self) -> &Vec { + &self.children + } + + pub fn entry(&self) -> &DirEnt { + &self.entry + } +} + +impl DirEnt { + pub fn new(name: &str, size: u64) -> Self { + DirEnt { + name: String::from(name), + size: size, + } + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn size(&self) -> u64 { + self.size + } +} + +impl Ord for Node { + fn cmp(&self, other: &Self) -> Ordering { + if self.entry.size > other.entry.size { + Ordering::Less + } else if self.entry.size < other.entry.size { + Ordering::Greater + } else { + let my_slashes = self.entry.name.matches('/').count(); + let other_slashes = other.entry.name.matches('/').count(); + + if my_slashes > other_slashes { + Ordering::Greater + } else if my_slashes < other_slashes { + Ordering::Less + } else { + if self.entry.name < other.entry.name { + Ordering::Less + } else if self.entry.name > other.entry.name { + Ordering::Greater + } else { + Ordering::Equal + } + } + } + } +} +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialEq for Node { + fn eq(&self, other: &Self) -> bool { + (&self.entry.name, self.entry.size) == (&other.entry.name, other.entry.size) + } +} +impl Eq for Node {} diff --git a/src/main.rs b/src/main.rs index f797730..655bd05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,73 +1,12 @@ -// test: -// recursive dirs that link to each other. -// Pass in bad dir name -// num to search for is less than num available -// admin files. -// -extern crate ansi_term; +extern crate dust; + +mod utils; +use utils::{display, find_big_ones, get_dir_tree}; + #[macro_use] extern crate clap; - -use std::collections::HashSet; - -use ansi_term::Colour::Fixed; use clap::{App, AppSettings, Arg}; -use std::cmp; -use std::cmp::Ordering; -use std::fs; -use std::fs::ReadDir; -use std::io; - -#[derive(Clone, Debug)] -struct Node { - dir: Dir, - children: Vec, -} -impl Ord for Node { - fn cmp(&self, other: &Self) -> Ordering { - if self.dir.size > other.dir.size { - Ordering::Less - } else if self.dir.size < other.dir.size { - Ordering::Greater - } else { - let my_slashes = self.dir.name.matches('/').count(); - let other_slashes = other.dir.name.matches('/').count(); - - if my_slashes > other_slashes { - Ordering::Greater - } else if my_slashes < other_slashes { - Ordering::Less - } else { - if self.dir.name < other.dir.name { - Ordering::Less - } else if self.dir.name > other.dir.name { - Ordering::Greater - } else { - Ordering::Equal - } - } - } - } -} -impl PartialOrd for Node { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - (&self.dir.name, self.dir.size) == (&other.dir.name, other.dir.size) - } -} -impl Eq for Node {} - -#[derive(Clone, Debug)] -struct Dir { - name: String, - size: u64, -} - static DEFAULT_NUMBER_OF_LINES: &'static str = "15"; fn main() { @@ -101,316 +40,3 @@ fn main() { let slice_it = find_big_ones(&results, number_of_lines); display(permissions, &slice_it); } - -fn get_dir_tree(filenames: &Vec<&str>, apparent_size: bool) -> (bool, Vec) { - let mut permissions = true; - let mut results = vec![]; - for b in filenames { - let mut new_name = String::from(*b); - while new_name.chars().last() == Some('/') && new_name.len() != 1 { - new_name.pop(); - } - let (hp, data) = examine_dir_str(new_name, apparent_size); - permissions = permissions && hp; - results.push(data); - } - (permissions, results) -} - -fn examine_dir_str(loc: String, apparent_size: bool) -> (bool, Node) { - let mut inodes: HashSet<(u64, u64)> = HashSet::new(); - let (hp, result) = examine_dir(fs::read_dir(&loc), apparent_size, &mut inodes); - - // This needs to be folded into the below recursive call somehow - let new_size = result.iter().fold(0, |a, b| a + b.dir.size); - ( - hp, - Node { - dir: Dir { - name: loc, - size: new_size, - }, - children: result, - }, - ) -} - -#[cfg(not(target_os = "macos"))] -fn get_block_size() -> u64 { - 1024 -} - -#[cfg(target_os = "macos")] -fn get_block_size() -> u64 { - 512 -} - -#[cfg(target_os = "linux")] -fn get_metadata(d: &std::fs::DirEntry, s: bool) -> Option<(u64, Option<(u64, u64)>)> { - use std::os::linux::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => { - let inode = Some((md.ino(), md.dev())); - if s { - Some((md.len(), inode)) - } else { - Some((md.st_blocks() * get_block_size(), inode)) - } - } - None => None, - } -} - -#[cfg(target_os = "unix")] -fn get_metadata(d: &std::fs::DirEntry, s: bool) -> Option<(u64, Option<(u64, u64)>)> { - use std::os::unix::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => { - let inode = Some((md.ino(), md.dev())); - if s { - Some((md.len(), inode)) - } else { - Some((md.blocks() * get_block_size(), inode)) - } - } - None => None, - } -} - -#[cfg(target_os = "macos")] -fn get_metadata(d: &std::fs::DirEntry, s: bool) -> Option<(u64, Option<(u64, u64)>)> { - use std::os::macos::fs::MetadataExt; - match d.metadata().ok() { - Some(md) => { - let inode = Some((md.st_ino(), md.st_dev())); - if s { - Some((md.len(), inode)) - } else { - Some((md.st_blocks() * get_block_size(), inode)) - } - } - None => None, - } -} - -#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] -fn get_metadata(d: &std::fs::DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> { - match d.metadata().ok() { - Some(md) => Some((md.len(), None)), - None => None, - } -} - -fn examine_dir( - a_dir: io::Result, - apparent_size: bool, - inodes: &mut HashSet<(u64, u64)>, -) -> (bool, Vec) { - let mut result = vec![]; - let mut have_permission = true; - - if a_dir.is_ok() { - let paths = a_dir.unwrap(); - for dd in paths { - match dd { - Ok(d) => { - let file_type = d.file_type().ok(); - let maybe_size_and_inode = get_metadata(&d, apparent_size); - - match (file_type, maybe_size_and_inode) { - (Some(file_type), Some((size, inode))) => { - let s = d.path().to_string_lossy().to_string(); - if let Some(inode_dev_pair) = inode { - if inodes.contains(&inode_dev_pair) { - continue; - } - inodes.insert(inode_dev_pair); - } - - if d.path().is_dir() && !file_type.is_symlink() { - let (hp, recursive) = - examine_dir(fs::read_dir(d.path()), apparent_size, inodes); - have_permission = have_permission && hp; - let new_size = recursive.iter().fold(size, |a, b| a + b.dir.size); - result.push(Node { - dir: Dir { - name: s, - size: new_size, - }, - children: recursive, - }) - } else { - result.push(Node { - dir: Dir { - name: s, - size: size, - }, - children: vec![], - }) - } - } - (_, None) => have_permission = false, - (_, _) => (), - } - } - Err(_) => (), - } - } - } else { - have_permission = false; - } - (have_permission, result) -} - -// We start with a list of root directories - these must be the biggest folders -// We then repeadedly merge in the children of the biggest directory - Each iteration -// the next biggest directory's children are merged in. -fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { - let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); - new_l.sort(); - - for processed_pointer in 0..max_to_show { - if new_l.len() == processed_pointer { - break; - } - // Must be a list of pointers into new_l otherwise b_list will go out of scope - // when it is deallocated - let mut b_list: Vec<&Node> = new_l[processed_pointer] - .children - .iter() - .map(|a| a) - .collect(); - new_l.extend(b_list); - new_l.sort(); - } - if new_l.len() > max_to_show { - new_l[0..max_to_show + 1].to_vec() - } else { - new_l - } -} - -fn display(permissions: bool, to_display: &Vec<&Node>) -> () { - if !permissions { - eprintln!("Did not have permissions for all directories"); - } - - display_node(to_display[0], &to_display, true, 1, "") -} - -fn display_node>( - node_to_print: &Node, - to_display: &Vec<&Node>, - is_first: bool, - depth: u8, - indentation_str: S, -) { - let mut is = indentation_str.into(); - print_this_node(node_to_print, is_first, depth, is.as_ref()); - - is = is.replace("└─┬", " "); - is = is.replace("└──", " "); - is = is.replace("├──", "│ "); - is = is.replace("├─┬", "│ "); - - let printable_node_slashes = node_to_print.dir.name.matches('/').count(); - - let mut num_siblings = to_display.iter().fold(0, |a, b| { - if node_to_print.children.contains(b) - && b.dir.name.matches('/').count() == printable_node_slashes + 1 - { - a + 1 - } else { - a - } - }); - - let mut is_biggest = true; - let mut has_display_children = false; - for node in to_display { - if node_to_print.children.contains(node) { - let has_children = node.children.len() > 0; - if node.dir.name.matches("/").count() == printable_node_slashes + 1 { - num_siblings -= 1; - for ref n in node.children.iter() { - has_display_children = has_display_children || to_display.contains(n); - } - let has_children = has_children && has_display_children; - let tree_chars = { - if num_siblings == 0 { - if has_children { - "└─┬" - } else { - "└──" - } - } else { - if has_children { - "├─┬" - } else { - "├──" - } - } - }; - display_node( - &node, - to_display, - is_biggest, - depth + 1, - is.to_string() + tree_chars, - ); - is_biggest = false; - } - } - } -} - -fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { - let padded_size = format!("{:>5}", human_readable_number(node_to_print.dir.size),); - println!( - "{} {} {}", - if is_biggest { - Fixed(196).paint(padded_size) - } else { - Fixed(7).paint(padded_size) - }, - indentation_str, - Fixed(7) - .on(Fixed(cmp::min(8, (depth) as u8) + 231)) - .paint(node_to_print.dir.name.to_string()) - ); -} - -fn human_readable_number(size: u64) -> (String) { - let units = vec!["T", "G", "M", "K"]; //make static - - for (i, u) in units.iter().enumerate() { - let marker = 1024u64.pow((units.len() - i) as u32); - if size >= marker { - if size / marker < 10 { - return format!("{:.1}{}", (size as f32 / marker as f32), u); - } else { - return format!("{}{}", (size / marker), u); - } - } - } - return format!("{}B", size); -} - -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn test_human_readable_number() { - assert_eq!(human_readable_number(1), "1B"); - assert_eq!(human_readable_number(956), "956B"); - assert_eq!(human_readable_number(1004), "1004B"); - assert_eq!(human_readable_number(1024), "1.0K"); - assert_eq!(human_readable_number(1536), "1.5K"); - assert_eq!(human_readable_number(1024 * 512), "512K"); - assert_eq!(human_readable_number(1024 * 1024), "1.0M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); - assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..0e7cdd7 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,244 @@ +use std::collections::HashSet; + +use std::fs::{self, ReadDir}; +use std::io; + +use std::cmp; + +use dust::{DirEnt, Node}; + +mod platform; +use self::platform::*; + +extern crate ansi_term; +use self::ansi_term::Colour::Fixed; + +pub fn get_dir_tree(filenames: &Vec<&str>, apparent_size: bool) -> (bool, Vec) { + let mut permissions = true; + let mut results = vec![]; + for &b in filenames { + let mut new_name = String::from(b); + while new_name.chars().last() == Some('/') && new_name.len() != 1 { + new_name.pop(); + } + let (hp, data) = examine_dir_str(&new_name, apparent_size); + permissions = permissions && hp; + results.push(data); + } + (permissions, results) +} + +fn examine_dir_str(loc: &str, apparent_size: bool) -> (bool, Node) { + let mut inodes: HashSet<(u64, u64)> = HashSet::new(); + let (hp, result) = examine_dir(fs::read_dir(loc), apparent_size, &mut inodes); + + // This needs to be folded into the below recursive call somehow + let new_size = result.iter().fold(0, |a, b| a + b.entry().size()); + (hp, Node::new(DirEnt::new(loc, new_size), result)) +} + +fn examine_dir( + a_dir: io::Result, + apparent_size: bool, + inodes: &mut HashSet<(u64, u64)>, +) -> (bool, Vec) { + let mut result = vec![]; + let mut have_permission = true; + + if a_dir.is_ok() { + let paths = a_dir.unwrap(); + for dd in paths { + match dd { + Ok(d) => { + let file_type = d.file_type().ok(); + let maybe_size_and_inode = get_metadata(&d, apparent_size); + + match (file_type, maybe_size_and_inode) { + (Some(file_type), Some((size, inode))) => { + let s = d.path().to_string_lossy().to_string(); + if let Some(inode_dev_pair) = inode { + if inodes.contains(&inode_dev_pair) { + continue; + } + inodes.insert(inode_dev_pair); + } + + if d.path().is_dir() && !file_type.is_symlink() { + let (hp, recursive) = + examine_dir(fs::read_dir(d.path()), apparent_size, inodes); + have_permission = have_permission && hp; + let new_size = + recursive.iter().fold(size, |a, b| a + b.entry().size()); + result.push(Node::new(DirEnt::new(&s, new_size), recursive)) + } else { + result.push(Node::new(DirEnt::new(&s, size), vec![])) + } + } + (_, None) => have_permission = false, + (_, _) => (), + } + } + Err(_) => (), + } + } + } else { + have_permission = false; + } + (have_permission, result) +} + +// We start with a list of root directories - these must be the biggest folders +// We then repeadedly merge in the children of the biggest directory - Each iteration +// the next biggest directory's children are merged in. +pub fn find_big_ones<'a>(l: &'a Vec, max_to_show: usize) -> Vec<&Node> { + let mut new_l: Vec<&Node> = l.iter().map(|a| a).collect(); + new_l.sort(); + + for processed_pointer in 0..max_to_show { + if new_l.len() == processed_pointer { + break; + } + // Must be a list of pointers into new_l otherwise b_list will go out of scope + // when it is deallocated + let mut b_list: Vec<&Node> = new_l[processed_pointer] + .children() + .iter() + .map(|a| a) + .collect(); + new_l.extend(b_list); + new_l.sort(); + } + + if new_l.len() > max_to_show { + new_l[0..max_to_show + 1].to_vec() + } else { + new_l + } +} + +pub fn display(permissions: bool, to_display: &Vec<&Node>) -> () { + if !permissions { + eprintln!("Did not have permissions for all directories"); + } + + display_node(to_display[0], &to_display, true, 1, "") +} + +fn display_node>( + node_to_print: &Node, + to_display: &Vec<&Node>, + is_first: bool, + depth: u8, + indentation_str: S, +) { + let mut is = indentation_str.into(); + print_this_node(node_to_print, is_first, depth, is.as_ref()); + + is = is.replace("└─┬", " "); + is = is.replace("└──", " "); + is = is.replace("├──", "│ "); + is = is.replace("├─┬", "│ "); + + let printable_node_slashes = node_to_print.entry().name().matches('/').count(); + + let mut num_siblings = to_display.iter().fold(0, |a, b| { + if node_to_print.children().contains(b) + && b.entry().name().matches('/').count() == printable_node_slashes + 1 + { + a + 1 + } else { + a + } + }); + + let mut is_biggest = true; + for node in to_display { + if node_to_print.children().contains(node) { + let has_display_children = node.children() + .iter() + .fold(false, |has_kids, n| has_kids || to_display.contains(&n)); + + let has_children = node.children().len() > 0 && has_display_children; + if node.entry().name().matches('/').count() == printable_node_slashes + 1 { + num_siblings -= 1; + + let tree_chars = { + if num_siblings == 0 { + if has_children { + "└─┬" + } else { + "└──" + } + } else { + if has_children { + "├─┬" + } else { + "├──" + } + } + }; + display_node( + &node, + to_display, + is_biggest, + depth + 1, + is.to_string() + tree_chars, + ); + is_biggest = false; + } + } + } +} + +fn print_this_node(node_to_print: &Node, is_biggest: bool, depth: u8, indentation_str: &str) { + let padded_size = format!("{:>5}", human_readable_number(node_to_print.entry().size()),); + println!( + "{} {} {}", + if is_biggest { + Fixed(196).paint(padded_size) + } else { + Fixed(7).paint(padded_size) + }, + indentation_str, + Fixed(7) + .on(Fixed(cmp::min(8, (depth) as u8) + 231)) + .paint(node_to_print.entry().name().to_string()) + ); +} + +fn human_readable_number(size: u64) -> (String) { + let units = vec!["T", "G", "M", "K"]; //make static + + //return format!("{}B", size); + + for (i, u) in units.iter().enumerate() { + let marker = 1024u64.pow((units.len() - i) as u32); + if size >= marker { + if size / marker < 10 { + return format!("{:.1}{}", (size as f32 / marker as f32), u); + } else { + return format!("{}{}", (size / marker), u); + } + } + } + return format!("{}B", size); +} + +mod tests { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_human_readable_number() { + assert_eq!(human_readable_number(1), "1B"); + assert_eq!(human_readable_number(956), "956B"); + assert_eq!(human_readable_number(1004), "1004B"); + assert_eq!(human_readable_number(1024), "1.0K"); + assert_eq!(human_readable_number(1536), "1.5K"); + assert_eq!(human_readable_number(1024 * 512), "512K"); + assert_eq!(human_readable_number(1024 * 1024), "1.0M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 - 1), "1023M"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 20), "20G"); + assert_eq!(human_readable_number(1024 * 1024 * 1024 * 1024), "1.0T"); + } +} diff --git a/src/utils/platform.rs b/src/utils/platform.rs new file mode 100644 index 0000000..bf876f1 --- /dev/null +++ b/src/utils/platform.rs @@ -0,0 +1,76 @@ +use std; + +#[cfg(not(any(target_os = "macos", target_os = "linux")))] +pub fn get_block_size() -> u64 { + 1024 +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +pub fn get_block_size() -> u64 { + 512 +} + +#[cfg(target_os = "linux")] +pub fn get_metadata( + d: &std::fs::DirEntry, + use_apparent_size: bool, +) -> Option<(u64, Option<(u64, u64)>)> { + use std::os::linux::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => { + let inode = Some((md.st_ino(), md.st_dev())); + if use_apparent_size { + Some((md.len(), inode)) + } else { + Some((md.st_blocks() * get_block_size(), inode)) + } + } + None => None, + } +} + +#[cfg(target_os = "unix")] +pub fn get_metadata( + d: &std::fs::DirEntry, + use_apparent_size: bool, +) -> Option<(u64, Option<(u64, u64)>)> { + use std::os::unix::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => { + let inode = Some((md.ino(), md.dev())); + if use_apparent_size { + Some((md.len(), inode)) + } else { + Some((md.blocks() * get_block_size(), inode)) + } + } + None => None, + } +} + +#[cfg(target_os = "macos")] +pub fn get_metadata( + d: &std::fs::DirEntry, + use_apparent_size: bool, +) -> Option<(u64, Option<(u64, u64)>)> { + use std::os::macos::fs::MetadataExt; + match d.metadata().ok() { + Some(md) => { + let inode = Some((md.st_ino(), md.st_dev())); + if use_apparent_size { + Some((md.len(), inode)) + } else { + Some((md.st_blocks() * get_block_size(), inode)) + } + } + None => None, + } +} + +#[cfg(not(any(target_os = "linux", target_os = "unix", target_os = "macos")))] +pub fn get_metadata(d: &std::fs::DirEntry, _apparent: bool) -> Option<(u64, Option<(u64, u64)>)> { + match d.metadata().ok() { + Some(md) => Some((md.len(), None)), + None => None, + } +}