performance tweaks

This commit is contained in:
sreedev
2023-01-18 00:24:50 -05:00
parent 51f5f8e61a
commit ea748c60d8
5 changed files with 107 additions and 105 deletions
Generated
-37
View File
@@ -314,7 +314,6 @@ dependencies = [
"memmap2", "memmap2",
"prettytable-rs", "prettytable-rs",
"rayon", "rayon",
"sqlite",
"thiserror", "thiserror",
"tokio", "tokio",
"unicode-segmentation", "unicode-segmentation",
@@ -693,12 +692,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "0.3.19" version = "0.3.19"
@@ -878,36 +871,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "sqlite"
version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12e072cb5fb89b3fe5e9c9584676348feb503f9fb3ae829d9868171bc5372d48"
dependencies = [
"libc",
"sqlite3-sys",
]
[[package]]
name = "sqlite3-src"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1815a7a02c996eb8e5c64f61fcb6fd9b12e593ce265c512c5853b2513635691"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "sqlite3-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d47c99824fc55360ba00caf28de0b8a0458369b832e016a64c13af0ad9fbb9ee"
dependencies = [
"libc",
"sqlite3-src",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
-1
View File
@@ -22,7 +22,6 @@ itertools = "0.10.5"
memmap2 = "0.5.8" memmap2 = "0.5.8"
prettytable-rs = "0.10.0" prettytable-rs = "0.10.0"
rayon = "1.6.1" rayon = "1.6.1"
sqlite = "0.30.3"
thiserror = "1.0.38" thiserror = "1.0.38"
tokio = { version = "1.23.0", features = ["full"] } tokio = { version = "1.23.0", features = ["full"] }
unicode-segmentation = "1.10.0" unicode-segmentation = "1.10.0"
+3 -2
View File
@@ -4,14 +4,15 @@ use colored::Colorize;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct File { pub struct File {
pub path: String, pub path: String,
pub hash: String, pub size: Option<u64>,
pub hash: Option<String>,
} }
pub fn delete_files(files: Vec<File>) -> Result<()> { pub fn delete_files(files: Vec<File>) -> Result<()> {
files.into_iter().for_each(|file| { files.into_iter().for_each(|file| {
match std::fs::remove_file(file.path.clone()) { match std::fs::remove_file(file.path.clone()) {
Ok(_) => println!("{}: {}", "DELETED".green(), file.path), Ok(_) => println!("{}: {}", "DELETED".green(), file.path),
Err(e) => println!("{}: {}", "FAILED".red(), file.path) Err(_) => println!("{}: {}", "FAILED".red(), file.path)
} }
}); });
+17 -31
View File
@@ -1,14 +1,15 @@
use std::io::Write; use crate::file_manager::{self, File};
use std::{collections::HashMap, fs, io}; use crate::params::Params;
use anyhow::Result; use anyhow::Result;
use chrono::offset::Utc; use chrono::offset::Utc;
use chrono::DateTime; use chrono::DateTime;
use colored::Colorize; use colored::Colorize;
use dashmap::DashMap;
use humansize::{format_size, DECIMAL}; use humansize::{format_size, DECIMAL};
use itertools::Itertools; use itertools::Itertools;
use crate::file_manager::{self, File};
use crate::params::Params;
use prettytable::{format, row, Table}; use prettytable::{format, row, Table};
use std::io::Write;
use std::{fs, io};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
fn format_path(path: &str, opts: &Params) -> Result<String> { fn format_path(path: &str, opts: &Params) -> Result<String> {
@@ -42,19 +43,7 @@ fn modified_time(path: &String) -> Result<String> {
Ok(modified_time.format("%Y-%m-%d %H:%M:%S").to_string()) Ok(modified_time.format("%Y-%m-%d %H:%M:%S").to_string())
} }
fn group_duplicates(duplicates: Vec<File>) -> HashMap<String, Vec<File>> { fn print_meta_info() {
let mut duplicate_mapper: HashMap<String, Vec<File>> = HashMap::new();
duplicates.into_iter().for_each(|file| {
duplicate_mapper
.entry(file.hash.clone())
.and_modify(|value| value.push(file.clone()))
.or_insert_with(|| vec![file]);
});
duplicate_mapper
}
fn print_meta_info(duplicates: &Vec<File>, opts: &Params) {
println!("Deduplicator v{}", std::env!("CARGO_PKG_VERSION")); println!("Deduplicator v{}", std::env!("CARGO_PKG_VERSION"));
} }
@@ -122,20 +111,19 @@ fn process_group_action(duplicates: &Vec<File>, dup_index: usize, dup_size: usiz
match scan_group_confirmation().unwrap() { match scan_group_confirmation().unwrap() {
true => { true => {
file_manager::delete_files(files_to_delete.collect_vec()); file_manager::delete_files(files_to_delete.collect_vec()).ok();
} }
false => println!("{}", "\nCancelled Delete Operation.".red()), false => println!("{}", "\nCancelled Delete Operation.".red()),
} }
} }
pub fn interactive(duplicates: Vec<File>, opts: &Params) { pub fn interactive(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
print_meta_info(&duplicates, opts); print_meta_info();
let grouped_duplicates = group_duplicates(duplicates); duplicates
.clone()
grouped_duplicates .into_iter()
.iter()
.enumerate() .enumerate()
.for_each(|(gindex, (hash, group))| { .for_each(|(gindex, (_, group))| {
let mut itable = Table::new(); let mut itable = Table::new();
itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); itable.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
itable.set_titles(row!["index", "filename", "size", "updated_at"]); itable.set_titles(row!["index", "filename", "size", "updated_at"]);
@@ -148,18 +136,16 @@ pub fn interactive(duplicates: Vec<File>, opts: &Params) {
]); ]);
}); });
process_group_action(group, gindex, grouped_duplicates.len(), itable); process_group_action(&group, gindex, duplicates.len(), itable);
}); });
} }
pub fn print(duplicates: Vec<File>, opts: &Params) { pub fn print(duplicates: DashMap<String, Vec<File>>, opts: &Params) {
print_meta_info(&duplicates, opts); print_meta_info();
let mut output_table = Table::new(); let mut output_table = Table::new();
let grouped_duplicates: HashMap<String, Vec<File>> = group_duplicates(duplicates);
output_table.set_titles(row!["hash", "duplicates"]); output_table.set_titles(row!["hash", "duplicates"]);
grouped_duplicates.iter().for_each(|(hash, group)| { duplicates.into_iter().for_each(|(hash, group)| {
let mut inner_table = Table::new(); let mut inner_table = Table::new();
inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); inner_table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
group.iter().for_each(|file| { group.iter().for_each(|file| {
+87 -34
View File
@@ -1,39 +1,57 @@
use anyhow::Result; use anyhow::Result;
use dashmap::DashMap;
use fxhash::hash64 as hasher; use fxhash::hash64 as hasher;
use glob::glob; use glob::glob;
use indicatif::{ParallelProgressIterator, ProgressStyle}; use indicatif::{ParallelProgressIterator, ProgressStyle};
use memmap2::Mmap;
use rayon::prelude::*; use rayon::prelude::*;
use std::hash::Hasher; use std::hash::Hasher;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use memmap2::Mmap;
use dashmap::DashMap;
use crate::{file_manager::File, params::Params}; use crate::{file_manager::File, params::Params};
pub fn duplicates(app_opts: &Params) -> Result<Vec<File>> { #[derive(Clone, Copy)]
let scan_results = scan(app_opts)?; enum IndexCritera {
let index_store = index_files(scan_results)?; Size,
Hash,
}
let duplicate_files = index_store pub fn duplicates(app_opts: &Params) -> Result<DashMap<String, Vec<File>>> {
let scan_results = scan(app_opts)?;
let size_index_store = index_files(scan_results, IndexCritera::Size)?;
let sizewize_duplicate_files = size_index_store
.into_par_iter() .into_par_iter()
.filter(|(_, files)| files.len() > 1) .filter(|(_, files)| files.len() > 1)
.map(|(_, files)| files ) .map(|(_, files)| files)
.flatten() .flatten()
.collect::<Vec<File>>(); .collect::<Vec<File>>();
Ok(duplicate_files) if sizewize_duplicate_files.len() > 1 {
let size_wise_duplicate_paths = sizewize_duplicate_files
.into_par_iter()
.map(|file| file.path)
.collect::<Vec<String>>();
let hash_index_store = index_files(size_wise_duplicate_paths, IndexCritera::Hash)?;
let duplicate_files = hash_index_store
.into_par_iter()
.filter(|(_, files)| files.len() > 1)
.collect();
Ok(duplicate_files)
} else {
Ok(DashMap::new())
}
} }
fn scan(app_opts: &Params) -> Result<Vec<String>> { fn scan(app_opts: &Params) -> Result<Vec<String>> {
let glob_patterns: Vec<PathBuf> = app_opts.get_glob_patterns(); let glob_patterns: Vec<PathBuf> = app_opts.get_glob_patterns();
let files: Vec<String> = glob_patterns let files: Vec<String> = glob_patterns
.par_iter() .par_iter()
.progress_with_style( .progress_with_style(ProgressStyle::with_template(
ProgressStyle::with_template( "{spinner:.green} [scanning files] [{wide_bar:.cyan/blue}] {pos}/{len} files",
"{spinner:.green} [scanning files] [{wide_bar:.cyan/blue}] {pos}/{len} files", )?)
)
.unwrap(),
)
.filter_map(|glob_pattern| glob(glob_pattern.as_os_str().to_str()?).ok()) .filter_map(|glob_pattern| glob(glob_pattern.as_os_str().to_str()?).ok())
.flat_map(|file_vec| { .flat_map(|file_vec| {
file_vec file_vec
@@ -50,25 +68,60 @@ fn scan(app_opts: &Params) -> Result<Vec<String>> {
Ok(files) Ok(files)
} }
fn index_files(files: Vec<String>) -> Result<DashMap<String, Vec<File>>> { fn process_file_size_index(fpath: String) -> Result<File> {
Ok(File {
path: fpath.clone(),
size: Some(fs::metadata(fpath)?.len()),
hash: None,
})
}
fn process_file_hash_index(fpath: String) -> Result<File> {
Ok(File {
path: fpath.clone(),
size: None,
hash: Some(hash_file(&fpath).unwrap_or_default()),
})
}
fn process_file_index(
fpath: String,
store: &DashMap<String, Vec<File>>,
index_criteria: IndexCritera,
) {
match index_criteria {
IndexCritera::Size => {
let processed_file = process_file_size_index(fpath).unwrap();
store
.entry(processed_file.size.unwrap_or_default().to_string())
.and_modify(|fileset| fileset.push(processed_file.clone()))
.or_insert_with(|| vec![processed_file]);
}
IndexCritera::Hash => {
let processed_file = process_file_hash_index(fpath).unwrap();
let indexhash = processed_file.clone().hash.unwrap_or_default();
store
.entry(indexhash)
.and_modify(|fileset| fileset.push(processed_file.clone()))
.or_insert_with(|| vec![processed_file]);
}
}
}
fn index_files(
files: Vec<String>,
index_criteria: IndexCritera,
) -> Result<DashMap<String, Vec<File>>> {
let store: DashMap<String, Vec<File>> = DashMap::new(); let store: DashMap<String, Vec<File>> = DashMap::new();
files files
.into_par_iter() .into_par_iter()
.progress_with_style( .progress_with_style(ProgressStyle::with_template(
ProgressStyle::with_template( "{spinner:.green} [indexing files] [{wide_bar:.cyan/blue}] {pos}/{len} files",
"{spinner:.green} [indexing files] [{wide_bar:.cyan/blue}] {pos}/{len} files", )?)
)?, .for_each(|file| process_file_index(file, &store, index_criteria));
)
.for_each(|file| {
let hash = hash_file(&file).unwrap_or_default();
let fobj = File { path: file, hash: hash.clone() };
store
.entry(hash)
.and_modify(|fileset| fileset.push(fobj.clone()) )
.or_insert_with(|| vec![fobj]);
});
Ok(store) Ok(store)
} }
pub fn incremental_hashing(filepath: &str) -> Result<String> { pub fn incremental_hashing(filepath: &str) -> Result<String> {
@@ -76,9 +129,8 @@ pub fn incremental_hashing(filepath: &str) -> Result<String> {
let fmap = unsafe { Mmap::map(&file)? }; let fmap = unsafe { Mmap::map(&file)? };
let mut inchasher = fxhash::FxHasher::default(); let mut inchasher = fxhash::FxHasher::default();
fmap fmap.chunks(1_000_000)
.chunks(1_000) .for_each(|mega| inchasher.write(mega));
.for_each(|kilo| { inchasher.write(kilo) });
Ok(format!("{}", inchasher.finish())) Ok(format!("{}", inchasher.finish()))
} }
@@ -91,8 +143,9 @@ pub fn standard_hashing(filepath: &str) -> Result<String> {
pub fn hash_file(filepath: &str) -> Result<String> { pub fn hash_file(filepath: &str) -> Result<String> {
let filemeta = fs::metadata(filepath)?; let filemeta = fs::metadata(filepath)?;
match filemeta.len() < 1_000_000 { // NOTE: USE INCREMENTAL HASHING ONLY FOR FILES > 100MB
match filemeta.len() < 100_000_000 {
true => standard_hashing(filepath), true => standard_hashing(filepath),
false => incremental_hashing(filepath) false => incremental_hashing(filepath),
} }
} }