use clap::Parser;
use comfy_table::Table;
use indicatif::{HumanBytes, HumanDuration, ProgressBar, ProgressStyle};
use log::warn;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::Path;
use std::time::Instant;
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
pub target_path: String,
}
struct Counter {
count: u64,
storage_size: u64,
}
impl Counter {
pub fn new(count: u64, storage_size: u64) -> Counter {
Counter {
count,
storage_size,
}
}
pub fn update(&mut self, count: i64, storage_size: i64) {
self.count = (self.count as i64 + count) as u64;
self.storage_size = (self.storage_size as i64 + storage_size) as u64;
}
}
fn scan(
path: &Path,
record: &mut HashMap<String, Counter>,
pb: ProgressBar,
) -> Result<(), Box<dyn Error>> {
pb.set_message(path.to_str().unwrap().to_string());
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Err(e) = scan(path.as_path(), record, pb.clone()) {
warn!(" {}. Skip {}", e, path.to_str().unwrap());
}
} else if let Some(extension) = path.extension() {
let extension = extension.to_str().unwrap().to_string();
let counter = record
.entry(extension)
.or_insert_with(|| Counter::new(0, 0));
let mut file_size: i64 = 0;
if let Ok(attribute) = fs::metadata(&path) {
file_size = attribute.len() as i64;
}
counter.update(1, file_size);
}
}
Ok(())
}
fn print_to_screen(record: &HashMap<String, Counter>) {
let mut record: Vec<(&String, &Counter)> = record.iter().collect();
record.sort_by(|a, b| b.1.count.cmp(&a.1.count));
let mut table = Table::new();
table.set_header(vec!["File type", "Count", "Total size"]);
for (ext, counter) in record {
table.add_row(vec![
ext,
&counter.count.to_string(),
&HumanBytes(counter.storage_size).to_string(),
]);
}
for i in 1..3 {
if let Some(column) = table.column_mut(i) {
column.set_cell_alignment(comfy_table::CellAlignment::Right)
}
}
println!("{table}");
}
pub fn run(config: &Args) -> Result<(), Box<dyn Error>> {
let mut record: HashMap<String, Counter> = HashMap::new();
let target_path = Path::new(&config.target_path);
let bar_style = ProgressStyle::with_template("{spinner} {msg}").unwrap();
let pb = ProgressBar::new_spinner()
.with_message("Counting...")
.with_style(bar_style);
pb.enable_steady_tick(std::time::Duration::from_millis(100));
let started = Instant::now();
scan(target_path, &mut record, pb.clone())?;
pb.finish_and_clear();
println!("Finished in {}.", HumanDuration(started.elapsed()));
print_to_screen(&record);
Ok(())
}