interactive.rs
1 use crate::{fileinfo::FileInfo, formatter::Formatter, params::Params}; 2 use anyhow::Result; 3 use dashmap::DashMap; 4 use prettytable::{format, row, Table}; 5 use std::sync::atomic::AtomicU64; 6 use std::{ 7 io::{self, Write}, 8 sync::Arc, 9 }; 10 11 pub struct Interactive; 12 13 impl Interactive { 14 pub fn init(result: Arc<DashMap<u128, Vec<FileInfo>>>, app_args: &Params) -> Result<()> { 15 let store = result.clone(); 16 if store.is_empty() { 17 println!("No duplicates found matching your search criteria."); 18 } 19 20 let printed_count: AtomicU64 = AtomicU64::new(0); 21 22 store 23 .iter() 24 .filter(|i| i.value().len() > 1) 25 .enumerate() 26 .for_each(|(gindex, i)| { 27 printed_count.fetch_add(1, std::sync::atomic::Ordering::Relaxed); 28 let group = i.value(); 29 let mut itable = Table::new(); 30 itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); 31 itable.set_titles(row!["index", "filename", "size", "updated_at"]); 32 33 let max_path_size = group 34 .iter() 35 .map(|f| f.path.iter().count()) 36 .max() 37 .unwrap_or_default(); 38 39 group.iter().enumerate().for_each(|(index, file)| { 40 itable.add_row(row![ 41 index, 42 Formatter::human_path(file, app_args, max_path_size).unwrap_or_default(), 43 Formatter::human_filesize(file).unwrap_or_default(), 44 Formatter::human_mtime(file).unwrap_or_default() 45 ]); 46 }); 47 48 Self::process_group_action(group, gindex, result.len(), itable); 49 }); 50 51 if printed_count.load(std::sync::atomic::Ordering::Relaxed) < 1 { 52 println!("No duplicates found matching your search criteria."); 53 } 54 55 Ok(()) 56 } 57 58 pub fn scan_group_confirmation() -> Result<bool> { 59 print!("\nconfirm? [y/N]: "); 60 std::io::stdout().flush()?; 61 let mut user_input = String::new(); 62 io::stdin().read_line(&mut user_input)?; 63 64 match user_input.trim() { 65 "Y" | "y" => Ok(true), 66 _ => Ok(false), 67 } 68 } 69 70 pub fn scan_group_instruction() -> Result<String> { 71 println!("\nEnter the indices of the files you want to delete."); 72 println!("You can enter multiple files using commas to seperate file indices."); 73 println!("example: 1,2"); 74 print!("\n> "); 75 std::io::stdout().flush()?; 76 let mut user_input = String::new(); 77 io::stdin().read_line(&mut user_input)?; 78 79 Ok(user_input) 80 } 81 82 pub fn process_group_action( 83 duplicates: &Vec<FileInfo>, 84 dup_index: usize, 85 dup_size: usize, 86 table: Table, 87 ) { 88 println!("\nDuplicate Set {} of {}\n", dup_index + 1, dup_size); 89 table.printstd(); 90 let files_to_delete = Self::scan_group_instruction().unwrap_or_default(); 91 let parsed_file_indices = files_to_delete 92 .trim() 93 .split(',') 94 .filter(|element| !element.is_empty()) 95 .map(|index| index.parse::<usize>().unwrap_or_default()) 96 .collect::<Vec<usize>>(); 97 98 if parsed_file_indices 99 .clone() 100 .into_iter() 101 .any(|index| index > (duplicates.len() - 1)) 102 { 103 println!("Err: File Index Out of Bounds!"); 104 return Self::process_group_action(duplicates, dup_index, dup_size, table); 105 } 106 107 print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 108 109 if parsed_file_indices.is_empty() { 110 return; 111 } 112 113 let files_to_delete = parsed_file_indices 114 .into_iter() 115 .map(|index| duplicates[index].clone()); 116 117 println!("\nThe following files will be deleted:"); 118 files_to_delete 119 .clone() 120 .enumerate() 121 .for_each(|(index, file)| { 122 println!("{}: {}", index, file.path.display()); 123 }); 124 125 match Self::scan_group_confirmation().unwrap() { 126 true => { 127 files_to_delete.into_iter().for_each(|file| { 128 match std::fs::remove_file(file.path.clone()) { 129 Ok(_) => println!("DELETED: {}", file.path.display()), 130 Err(_) => println!("FAILED: {}", file.path.display()), 131 } 132 }); 133 } 134 false => println!("\nCancelled Delete Operation."), 135 } 136 } 137 }