1use clap::Parser;
2use comfy_table::Table;
3use indicatif::{HumanBytes, HumanDuration, ProgressBar, ProgressStyle};
4use log::warn;
5use std::collections::HashMap;
6use std::error::Error;
7use std::fs;
8use std::path::Path;
9use std::time::Instant;
10
11#[derive(Parser)]
13#[clap(author, version, about, long_about = None)]
14pub struct Args {
15 pub target_path: String,
17}
18
19struct Counter {
21 count: u64,
22 storage_size: u64,
23}
24
25impl Counter {
26 pub fn new(count: u64, storage_size: u64) -> Counter {
28 Counter {
29 count,
30 storage_size,
31 }
32 }
33
34 pub fn update(&mut self, count: i64, storage_size: i64) {
36 self.count = (self.count as i64 + count) as u64;
37 self.storage_size = (self.storage_size as i64 + storage_size) as u64;
38 }
39}
40
41fn scan(
43 path: &Path,
44 record: &mut HashMap<String, Counter>,
45 pb: ProgressBar,
46) -> Result<(), Box<dyn Error>> {
47 pb.set_message(path.to_str().unwrap().to_string());
49
50 let entries = fs::read_dir(path)?;
52 for entry in entries {
53 let entry = entry?;
54 let path = entry.path();
55
56 if path.is_dir() {
58 if let Err(e) = scan(path.as_path(), record, pb.clone()) {
59 warn!(" {}. Skip {}", e, path.to_str().unwrap());
60 }
61 } else if let Some(extension) = path.extension() {
62 let extension = extension.to_str().unwrap().to_string();
63 let counter = record
64 .entry(extension)
65 .or_insert_with(|| Counter::new(0, 0));
66 let mut file_size: i64 = 0;
68 if let Ok(attribute) = fs::metadata(&path) {
69 file_size = attribute.len() as i64;
70 }
71
72 counter.update(1, file_size);
74 }
75 }
76 Ok(())
77}
78
79fn print_to_screen(record: &HashMap<String, Counter>) {
81 let mut record: Vec<(&String, &Counter)> = record.iter().collect();
83 record.sort_by(|a, b| b.1.count.cmp(&a.1.count));
84
85 let mut table = Table::new();
87 table.set_header(vec!["File type", "Count", "Total size"]);
88 for (ext, counter) in record {
89 table.add_row(vec![
90 ext,
91 &counter.count.to_string(),
92 &HumanBytes(counter.storage_size).to_string(),
93 ]);
94 }
95
96 for i in 1..3 {
98 if let Some(column) = table.column_mut(i) {
99 column.set_cell_alignment(comfy_table::CellAlignment::Right)
100 }
101 }
102 println!("{table}");
103}
104
105pub fn run(config: &Args) -> Result<(), Box<dyn Error>> {
107 let mut record: HashMap<String, Counter> = HashMap::new();
109 let target_path = Path::new(&config.target_path);
110
111 let bar_style = ProgressStyle::with_template("{spinner} {msg}").unwrap();
113 let pb = ProgressBar::new_spinner()
114 .with_message("Counting...")
115 .with_style(bar_style);
116 pb.enable_steady_tick(std::time::Duration::from_millis(100));
117
118 let started = Instant::now();
120
121 scan(target_path, &mut record, pb.clone())?;
123
124 pb.finish_and_clear();
126 println!("Finished in {}.", HumanDuration(started.elapsed()));
127 print_to_screen(&record);
128 Ok(())
129}