/ src / interactive.rs
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  }