Skip to content

Commit dd4b051

Browse files
authored
Merge pull request #16 from sreedevk/interactive-mode
Interactive mode
2 parents b826dbe + 533f81f commit dd4b051

File tree

6 files changed

+123
-8
lines changed

6 files changed

+123
-8
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "deduplicator"
3-
version = "0.0.6"
3+
version = "0.0.7"
44
edition = "2021"
55
description = "find,filter,delete Duplicates"
66
license = "MIT"

src/app/file_manager.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::database::File;
2+
use anyhow::Result;
3+
use colored::Colorize;
4+
5+
pub fn delete_files(files: Vec<File>) -> Result<()> {
6+
files.into_iter().for_each(|file| {
7+
match std::fs::remove_file(file.path.clone()) {
8+
Ok(_) => println!("{}: {}", "DELETED".green(), file.path),
9+
Err(e) => println!("{}: {}", "FAILED".red(), file.path)
10+
}
11+
});
12+
13+
Ok(())
14+
}

src/app/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
#![allow(unused)]
2+
13
mod event_handler;
24
mod events;
35
mod formatter;
46
mod ui;
7+
pub mod file_manager;
58

69
use std::{io, thread, time::Duration};
710

@@ -32,7 +35,11 @@ impl App {
3235
// Self::init_render_loop(&mut term)?;
3336
// Self::cleanup(&mut term)?;
3437

35-
output::print(duplicates, app_args); /* TODO: APP TUI INIT FUNCTION */
38+
match app_args.interactive {
39+
true => output::interactive(duplicates, app_args),
40+
false => output::print(duplicates, app_args) /* TODO: APP TUI INIT FUNCTION */
41+
}
42+
3643
Ok(())
3744
}
3845

src/output.rs

+95-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
use std::{collections::HashMap, fs};
1+
use std::{collections::HashMap, fs, io};
2+
use std::io::Write;
23

34
use anyhow::Result;
45
use chrono::offset::Utc;
56
use chrono::DateTime;
67
use colored::Colorize;
78
use humansize::{format_size, DECIMAL};
9+
use itertools::Itertools;
810

11+
use crate::app::file_manager;
912
use crate::database::File;
1013
use crate::params::Params;
11-
use prettytable::{row, Cell, Row, format, Table};
14+
use prettytable::{format, row, Cell, Row, Table};
1215

1316
fn format_path(path: &str, opts: &Params) -> Result<String> {
1417
let display_path = path.replace(&opts.get_directory()?, "");
@@ -50,16 +53,104 @@ fn group_duplicates(duplicates: Vec<File>) -> HashMap<String, Vec<File>> {
5053
duplicate_mapper
5154
}
5255

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+
53144
pub fn print(duplicates: Vec<File>, opts: &Params) {
145+
print_meta_info(&duplicates, opts);
146+
54147
let mut output_table = Table::new();
55148
let grouped_duplicates: HashMap<String, Vec<File>> = group_duplicates(duplicates);
56149

57150
output_table.set_titles(row!["hash", "duplicates"]);
58151
grouped_duplicates.iter().for_each(|(hash, group)| {
59152
let mut inner_table = Table::new();
60-
// inner_table.set_format(inner_table_format);
61153
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
62-
//inner_table.set_titles(row!["filename", "size", "updated_at"]);
63154
group.iter().for_each(|file| {
64155
inner_table.add_row(row![
65156
format_path(&file.path, opts).unwrap_or_default().blue(),

src/params.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ pub struct Params {
1212
/// Run Deduplicator on dir different from pwd
1313
#[arg(long)]
1414
pub dir: Option<PathBuf>,
15-
/// Don't use cache for indexing files (default = true)
15+
/// Don't use cache for indexing files (default = false)
1616
#[arg(long, short)]
1717
pub nocache: bool,
18+
/// Delete files interactively
19+
#[arg(long, short)]
20+
pub interactive: bool
1821
}
1922

2023
impl Params {

0 commit comments

Comments
 (0)