1 | | - | use std::{collections::HashMap, fs}; |
| 1 | + | use std::{collections::HashMap, fs, io}; |
| 2 | + | use std::io::Write; |
2 | 3 | | |
3 | 4 | | use anyhow::Result; |
4 | 5 | | use chrono::offset::Utc; |
5 | 6 | | use chrono::DateTime; |
6 | 7 | | use colored::Colorize; |
7 | 8 | | use humansize::{format_size, DECIMAL}; |
| 9 | + | use itertools::Itertools; |
8 | 10 | | |
| 11 | + | use crate::app::file_manager; |
9 | 12 | | use crate::database::File; |
10 | 13 | | use crate::params::Params; |
11 | | - | use prettytable::{row, Cell, Row, format, Table}; |
| 14 | + | use prettytable::{format, row, Cell, Row, Table}; |
12 | 15 | | |
13 | 16 | | fn format_path(path: &str, opts: &Params) -> Result<String> { |
14 | 17 | | let display_path = path.replace(&opts.get_directory()?, ""); |
| skipped 35 lines |
50 | 53 | | duplicate_mapper |
51 | 54 | | } |
52 | 55 | | |
| 56 | + | fn print_meta_info(duplicates: &Vec<File>, opts: &Params) { |
| 57 | + | println!("Deduplicator v{}", std::env!("CARGO_PKG_VERSION")); |
| 58 | + | } |
| 59 | + | |
| 60 | + | fn scan_group_instruction() -> Result<String> { |
| 61 | + | println!("\nEnter the indices of the files you want to delete."); |
| 62 | + | println!("You can enter multiple files using commas to seperate file indices."); |
| 63 | + | println!("example: 1,2"); |
| 64 | + | print!("\n> "); |
| 65 | + | std::io::stdout().flush()?; |
| 66 | + | let mut user_input = String::new(); |
| 67 | + | io::stdin().read_line(&mut user_input)?; |
| 68 | + | |
| 69 | + | Ok(user_input) |
| 70 | + | } |
| 71 | + | |
| 72 | + | fn scan_group_confirmation() -> Result<bool> { |
| 73 | + | print!("\nconfirm? [Y/n]: "); |
| 74 | + | std::io::stdout().flush()?; |
| 75 | + | let mut user_input = String::new(); |
| 76 | + | io::stdin().read_line(&mut user_input)?; |
| 77 | + | |
| 78 | + | match user_input.trim() { |
| 79 | + | "Y" | "y" => Ok(true), |
| 80 | + | _ => Ok(false) |
| 81 | + | } |
| 82 | + | } |
| 83 | + | |
| 84 | + | fn process_group_action(duplicates: &Vec<File>, dup_index: usize, dup_size: usize, table: Table) { |
| 85 | + | println!("\nDuplicate Set {} of {}\n", dup_index + 1, dup_size); |
| 86 | + | table.printstd(); |
| 87 | + | let files_to_delete = scan_group_instruction().unwrap_or_default(); |
| 88 | + | let parsed_file_indices = files_to_delete |
| 89 | + | .trim() |
| 90 | + | .split(',') |
| 91 | + | .filter(|element| !element.is_empty()) |
| 92 | + | .map(|index| index.parse::<usize>().unwrap_or_default()) |
| 93 | + | .collect_vec(); |
| 94 | + | |
| 95 | + | if parsed_file_indices |
| 96 | + | .clone() |
| 97 | + | .into_iter() |
| 98 | + | .any(|index| index > (duplicates.len() - 1)) |
| 99 | + | { |
| 100 | + | println!("{}", "Err: File Index Out of Bounds!".red()); |
| 101 | + | return process_group_action(duplicates, dup_index, dup_size, table); |
| 102 | + | } |
| 103 | + | |
| 104 | + | print!("{esc}[2J{esc}[1;1H", esc = 27 as char); |
| 105 | + | |
| 106 | + | if parsed_file_indices.is_empty() { return } |
| 107 | + | |
| 108 | + | let files_to_delete = parsed_file_indices |
| 109 | + | .into_iter() |
| 110 | + | .map(|index| duplicates[index].clone()); |
| 111 | + | |
| 112 | + | println!("\n{}", "The following files will be deleted:".red()); |
| 113 | + | files_to_delete.clone().enumerate().for_each(|(index, file)| { |
| 114 | + | println!("{}: {}", index.to_string().blue(), file.path); |
| 115 | + | }); |
| 116 | + | |
| 117 | + | match scan_group_confirmation().unwrap() { |
| 118 | + | true => { file_manager::delete_files(files_to_delete.collect_vec()); }, |
| 119 | + | false => println!("{}", "\nCancelled Delete Operation.".red()) |
| 120 | + | } |
| 121 | + | } |
| 122 | + | |
| 123 | + | pub fn interactive(duplicates: Vec<File>, opts: &Params) { |
| 124 | + | print_meta_info(&duplicates, opts); |
| 125 | + | let grouped_duplicates = group_duplicates(duplicates); |
| 126 | + | |
| 127 | + | grouped_duplicates.iter().enumerate().for_each(|(gindex, (hash, group))| { |
| 128 | + | let mut itable = Table::new(); |
| 129 | + | itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); |
| 130 | + | itable.set_titles(row!["index", "filename", "size", "updated_at"]); |
| 131 | + | group.iter().enumerate().for_each(|(index, file)| { |
| 132 | + | itable.add_row(row![ |
| 133 | + | index, |
| 134 | + | format_path(&file.path, opts).unwrap_or_default().blue(), |
| 135 | + | file_size(&file.path).unwrap_or_default().red(), |
| 136 | + | modified_time(&file.path).unwrap_or_default().yellow() |
| 137 | + | ]); |
| 138 | + | }); |
| 139 | + | |
| 140 | + | process_group_action(group, gindex, grouped_duplicates.len(), itable); |
| 141 | + | }); |
| 142 | + | } |
| 143 | + | |
53 | 144 | | pub fn print(duplicates: Vec<File>, opts: &Params) { |
| 145 | + | print_meta_info(&duplicates, opts); |
| 146 | + | |
54 | 147 | | let mut output_table = Table::new(); |
55 | 148 | | let grouped_duplicates: HashMap<String, Vec<File>> = group_duplicates(duplicates); |
56 | 149 | | |
57 | 150 | | output_table.set_titles(row!["hash", "duplicates"]); |
58 | 151 | | grouped_duplicates.iter().for_each(|(hash, group)| { |
59 | 152 | | let mut inner_table = Table::new(); |
60 | | - | // inner_table.set_format(inner_table_format); |
61 | 153 | | inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); |
62 | | - | //inner_table.set_titles(row!["filename", "size", "updated_at"]); |
63 | 154 | | group.iter().for_each(|file| { |
64 | 155 | | inner_table.add_row(row![ |
65 | 156 | | format_path(&file.path, opts).unwrap_or_default().blue(), |
| skipped 10 lines |