mirror of
https://github.com/sreedevk/deduplicator.git
synced 2026-08-26 18:15:33 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd4b051378 | ||
|
|
533f81f724 | ||
|
|
471e60fa6c | ||
|
|
3fd4869869 | ||
|
|
99b87cf7fa | ||
|
|
b826dbe118 | ||
|
|
0f33b4d6b5 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -303,7 +303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deduplicator"
|
||||
version = "0.0.6"
|
||||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deduplicator"
|
||||
version = "0.0.6"
|
||||
version = "0.0.7"
|
||||
edition = "2021"
|
||||
description = "find,filter,delete Duplicates"
|
||||
license = "MIT"
|
||||
|
||||
@@ -42,4 +42,7 @@ cargo install deduplicator
|
||||
</p>
|
||||
<h2 align="center">Screenshots</h2>
|
||||
|
||||

|
||||
<p align="center">
|
||||
<img align="center" src="https://user-images.githubusercontent.com/36154121/211458077-90092aa3-496c-492f-a061-618059890d5f.png" width="500" height="400" />
|
||||
</p>
|
||||
|
||||
|
||||
14
src/app/file_manager.rs
Normal file
14
src/app/file_manager.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::database::File;
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
|
||||
pub fn delete_files(files: Vec<File>) -> Result<()> {
|
||||
files.into_iter().for_each(|file| {
|
||||
match std::fs::remove_file(file.path.clone()) {
|
||||
Ok(_) => println!("{}: {}", "DELETED".green(), file.path),
|
||||
Err(e) => println!("{}: {}", "FAILED".red(), file.path)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
#![allow(unused)]
|
||||
|
||||
mod event_handler;
|
||||
mod events;
|
||||
mod formatter;
|
||||
mod ui;
|
||||
pub mod file_manager;
|
||||
|
||||
use std::{io, thread, time::Duration};
|
||||
|
||||
@@ -32,7 +35,11 @@ impl App {
|
||||
// Self::init_render_loop(&mut term)?;
|
||||
// Self::cleanup(&mut term)?;
|
||||
|
||||
output::print(duplicates, app_args); /* TODO: APP TUI INIT FUNCTION */
|
||||
match app_args.interactive {
|
||||
true => output::interactive(duplicates, app_args),
|
||||
false => output::print(duplicates, app_args) /* TODO: APP TUI INIT FUNCTION */
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use std::{collections::HashMap, fs};
|
||||
use std::{collections::HashMap, fs, io};
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::offset::Utc;
|
||||
use chrono::DateTime;
|
||||
use colored::Colorize;
|
||||
use humansize::{format_size, DECIMAL};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::app::file_manager;
|
||||
use crate::database::File;
|
||||
use crate::params::Params;
|
||||
use prettytable::{row, Cell, Row, format, Table};
|
||||
use prettytable::{format, row, Cell, Row, Table};
|
||||
|
||||
fn format_path(path: &str, opts: &Params) -> Result<String> {
|
||||
let display_path = path.replace(&opts.get_directory()?, "");
|
||||
@@ -50,16 +53,104 @@ fn group_duplicates(duplicates: Vec<File>) -> HashMap<String, Vec<File>> {
|
||||
duplicate_mapper
|
||||
}
|
||||
|
||||
fn print_meta_info(duplicates: &Vec<File>, opts: &Params) {
|
||||
println!("Deduplicator v{}", std::env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
|
||||
fn scan_group_instruction() -> Result<String> {
|
||||
println!("\nEnter the indices of the files you want to delete.");
|
||||
println!("You can enter multiple files using commas to seperate file indices.");
|
||||
println!("example: 1,2");
|
||||
print!("\n> ");
|
||||
std::io::stdout().flush()?;
|
||||
let mut user_input = String::new();
|
||||
io::stdin().read_line(&mut user_input)?;
|
||||
|
||||
Ok(user_input)
|
||||
}
|
||||
|
||||
fn scan_group_confirmation() -> Result<bool> {
|
||||
print!("\nconfirm? [Y/n]: ");
|
||||
std::io::stdout().flush()?;
|
||||
let mut user_input = String::new();
|
||||
io::stdin().read_line(&mut user_input)?;
|
||||
|
||||
match user_input.trim() {
|
||||
"Y" | "y" => Ok(true),
|
||||
_ => Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_group_action(duplicates: &Vec<File>, dup_index: usize, dup_size: usize, table: Table) {
|
||||
println!("\nDuplicate Set {} of {}\n", dup_index + 1, dup_size);
|
||||
table.printstd();
|
||||
let files_to_delete = scan_group_instruction().unwrap_or_default();
|
||||
let parsed_file_indices = files_to_delete
|
||||
.trim()
|
||||
.split(',')
|
||||
.filter(|element| !element.is_empty())
|
||||
.map(|index| index.parse::<usize>().unwrap_or_default())
|
||||
.collect_vec();
|
||||
|
||||
if parsed_file_indices
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|index| index > (duplicates.len() - 1))
|
||||
{
|
||||
println!("{}", "Err: File Index Out of Bounds!".red());
|
||||
return process_group_action(duplicates, dup_index, dup_size, table);
|
||||
}
|
||||
|
||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||
|
||||
if parsed_file_indices.is_empty() { return }
|
||||
|
||||
let files_to_delete = parsed_file_indices
|
||||
.into_iter()
|
||||
.map(|index| duplicates[index].clone());
|
||||
|
||||
println!("\n{}", "The following files will be deleted:".red());
|
||||
files_to_delete.clone().enumerate().for_each(|(index, file)| {
|
||||
println!("{}: {}", index.to_string().blue(), file.path);
|
||||
});
|
||||
|
||||
match scan_group_confirmation().unwrap() {
|
||||
true => { file_manager::delete_files(files_to_delete.collect_vec()); },
|
||||
false => println!("{}", "\nCancelled Delete Operation.".red())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interactive(duplicates: Vec<File>, opts: &Params) {
|
||||
print_meta_info(&duplicates, opts);
|
||||
let grouped_duplicates = group_duplicates(duplicates);
|
||||
|
||||
grouped_duplicates.iter().enumerate().for_each(|(gindex, (hash, group))| {
|
||||
let mut itable = Table::new();
|
||||
itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||
itable.set_titles(row!["index", "filename", "size", "updated_at"]);
|
||||
group.iter().enumerate().for_each(|(index, file)| {
|
||||
itable.add_row(row![
|
||||
index,
|
||||
format_path(&file.path, opts).unwrap_or_default().blue(),
|
||||
file_size(&file.path).unwrap_or_default().red(),
|
||||
modified_time(&file.path).unwrap_or_default().yellow()
|
||||
]);
|
||||
});
|
||||
|
||||
process_group_action(group, gindex, grouped_duplicates.len(), itable);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn print(duplicates: Vec<File>, opts: &Params) {
|
||||
print_meta_info(&duplicates, opts);
|
||||
|
||||
let mut output_table = Table::new();
|
||||
let grouped_duplicates: HashMap<String, Vec<File>> = group_duplicates(duplicates);
|
||||
|
||||
output_table.set_titles(row!["hash", "duplicates"]);
|
||||
grouped_duplicates.iter().for_each(|(hash, group)| {
|
||||
let mut inner_table = Table::new();
|
||||
// inner_table.set_format(inner_table_format);
|
||||
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
|
||||
//inner_table.set_titles(row!["filename", "size", "updated_at"]);
|
||||
group.iter().for_each(|file| {
|
||||
inner_table.add_row(row![
|
||||
format_path(&file.path, opts).unwrap_or_default().blue(),
|
||||
|
||||
@@ -12,9 +12,12 @@ pub struct Params {
|
||||
/// Run Deduplicator on dir different from pwd
|
||||
#[arg(long)]
|
||||
pub dir: Option<PathBuf>,
|
||||
/// Don't use cache for indexing files (default = true)
|
||||
/// Don't use cache for indexing files (default = false)
|
||||
#[arg(long, short)]
|
||||
pub nocache: bool,
|
||||
/// Delete files interactively
|
||||
#[arg(long, short)]
|
||||
pub interactive: bool
|
||||
}
|
||||
|
||||
impl Params {
|
||||
|
||||
Reference in New Issue
Block a user