Implement finding duplicates by size/name (#956)
* Implementing Size+Name method * Partial hashing * Move hashing into different functions * Update * Add some code * Split code into parts * Entry size * Simplify code * Bottom Buttons * Bottom Buttons * Confusion * Libheif * Simplified sorting * Revert libheif change
This commit is contained in:
parent
de4edba380
commit
5272309341
748
Cargo.lock
generated
748
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@ homepage = "https://github.com/qarmin/czkawka"
|
||||||
repository = "https://github.com/qarmin/czkawka"
|
repository = "https://github.com/qarmin/czkawka"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1", features = ["derive"] }
|
clap = { version = "4.2", features = ["derive"] }
|
||||||
|
|
||||||
# For enum types
|
# For enum types
|
||||||
image_hasher = "1.1.2"
|
image_hasher = "1.1.2"
|
||||||
|
|
|
@ -584,6 +584,7 @@ fn parse_checking_method(src: &str) -> Result<CheckingMethod, &'static str> {
|
||||||
match src.to_ascii_lowercase().as_str() {
|
match src.to_ascii_lowercase().as_str() {
|
||||||
"name" => Ok(CheckingMethod::Name),
|
"name" => Ok(CheckingMethod::Name),
|
||||||
"size" => Ok(CheckingMethod::Size),
|
"size" => Ok(CheckingMethod::Size),
|
||||||
|
"size_name" => Ok(CheckingMethod::SizeName),
|
||||||
"hash" => Ok(CheckingMethod::Hash),
|
"hash" => Ok(CheckingMethod::Hash),
|
||||||
_ => Err("Couldn't parse the search method (allowed: NAME, SIZE, HASH)"),
|
_ => Err("Couldn't parse the search method (allowed: NAME, SIZE, HASH)"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,16 @@ directories-next = "2.0.0"
|
||||||
|
|
||||||
# Needed by similar images
|
# Needed by similar images
|
||||||
image_hasher = "1.1.2"
|
image_hasher = "1.1.2"
|
||||||
bk-tree = "0.4.0"
|
bk-tree = "0.5.0"
|
||||||
image = "0.24.5"
|
image = "0.24.6"
|
||||||
hamming = "0.1.3"
|
hamming = "0.1.3"
|
||||||
|
|
||||||
# Needed by same music
|
# Needed by same music
|
||||||
bitflags = "1.3.2"
|
bitflags = "2.0.2"
|
||||||
lofty = "0.11.0"
|
lofty = "0.12.0"
|
||||||
|
|
||||||
# Futures - needed by async progress sender
|
# Futures - needed by async progress sender
|
||||||
futures = "0.3.26"
|
futures = "0.3.28"
|
||||||
|
|
||||||
# Needed by broken files
|
# Needed by broken files
|
||||||
zip = { version = "0.6.4", features = ["aes-crypto", "bzip2", "deflate", "time"], default-features = false }
|
zip = { version = "0.6.4", features = ["aes-crypto", "bzip2", "deflate", "time"], default-features = false }
|
||||||
|
@ -41,7 +41,7 @@ blake3 = "1.3.3"
|
||||||
crc32fast = "1.3.2"
|
crc32fast = "1.3.2"
|
||||||
xxhash-rust = { version = "0.8.6", features = ["xxh3"] }
|
xxhash-rust = { version = "0.8.6", features = ["xxh3"] }
|
||||||
|
|
||||||
tempfile = "3.4.0"
|
tempfile = "3.5.0"
|
||||||
|
|
||||||
# Video Duplicates
|
# Video Duplicates
|
||||||
vid_dup_finder_lib = "0.1.1"
|
vid_dup_finder_lib = "0.1.1"
|
||||||
|
@ -54,8 +54,8 @@ serde_json = "1.0"
|
||||||
|
|
||||||
# Language
|
# Language
|
||||||
i18n-embed = { version = "0.13.8", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13.8", features = ["fluent-system", "desktop-requester"] }
|
||||||
i18n-embed-fl = "0.6.5"
|
i18n-embed-fl = "0.6.6"
|
||||||
rust-embed = "6.6.0"
|
rust-embed = "6.6.1"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
# Raw image files
|
# Raw image files
|
||||||
|
@ -69,10 +69,10 @@ infer = "0.13.0"
|
||||||
num_cpus = "1.15.0"
|
num_cpus = "1.15.0"
|
||||||
|
|
||||||
# Heif/Heic
|
# Heif/Heic
|
||||||
libheif-rs = { version = "0.18.0", optional = true }
|
libheif-rs = { version = "0.18.0", optional = true } # Do not upgrade now, since Ubuntu 22.04 not works with newer version
|
||||||
anyhow = { version = "1.0", optional = true }
|
anyhow = { version = "1.0", optional = true }
|
||||||
|
|
||||||
state="0.5.3"
|
state = "0.5.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub enum TypeOfFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
pub struct CheckedTypes : u32 {
|
pub struct CheckedTypes : u32 {
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ use anyhow::Result;
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
use image::{DynamicImage, ImageBuffer, Rgb};
|
use image::{DynamicImage, ImageBuffer, Rgb};
|
||||||
use imagepipe::{ImageSource, Pipeline};
|
use imagepipe::{ImageSource, Pipeline};
|
||||||
|
// #[cfg(feature = "heif")]
|
||||||
|
// use libheif_rs::LibHeif;
|
||||||
#[cfg(feature = "heif")]
|
#[cfg(feature = "heif")]
|
||||||
use libheif_rs::{ColorSpace, HeifContext, RgbChroma};
|
use libheif_rs::{ColorSpace, HeifContext, RgbChroma};
|
||||||
|
|
||||||
|
@ -126,8 +128,10 @@ pub fn open_cache_folder(cache_file_name: &str, save_to_cache: bool, use_json: b
|
||||||
|
|
||||||
#[cfg(feature = "heif")]
|
#[cfg(feature = "heif")]
|
||||||
pub fn get_dynamic_image_from_heic(path: &str) -> Result<DynamicImage> {
|
pub fn get_dynamic_image_from_heic(path: &str) -> Result<DynamicImage> {
|
||||||
|
// let libheif = LibHeif::new();
|
||||||
let im = HeifContext::read_from_file(path)?;
|
let im = HeifContext::read_from_file(path)?;
|
||||||
let handle = im.primary_image_handle()?;
|
let handle = im.primary_image_handle()?;
|
||||||
|
// let image = libheif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?; // Enable when using libheif 0.19
|
||||||
let image = handle.decode(ColorSpace::Rgb(RgbChroma::Rgb), None)?;
|
let image = handle.decode(ColorSpace::Rgb(RgbChroma::Rgb), None)?;
|
||||||
let width = image.width();
|
let width = image.width();
|
||||||
let height = image.height();
|
let height = image.height();
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub struct ProgressData {
|
||||||
pub enum CheckingMethod {
|
pub enum CheckingMethod {
|
||||||
None,
|
None,
|
||||||
Name,
|
Name,
|
||||||
|
SizeName,
|
||||||
Size,
|
Size,
|
||||||
Hash,
|
Hash,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread::sleep;
|
use std::thread::{sleep, JoinHandle};
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use std::{fs, mem, thread};
|
use std::{fs, mem, thread};
|
||||||
|
|
||||||
|
@ -67,6 +67,8 @@ pub struct Info {
|
||||||
pub number_of_duplicated_files_by_hash: usize,
|
pub number_of_duplicated_files_by_hash: usize,
|
||||||
pub number_of_groups_by_name: usize,
|
pub number_of_groups_by_name: usize,
|
||||||
pub number_of_duplicated_files_by_name: usize,
|
pub number_of_duplicated_files_by_name: usize,
|
||||||
|
pub number_of_groups_by_size_name: usize,
|
||||||
|
pub number_of_duplicated_files_by_size_name: usize,
|
||||||
pub lost_space_by_size: u64,
|
pub lost_space_by_size: u64,
|
||||||
pub lost_space_by_hash: u64,
|
pub lost_space_by_hash: u64,
|
||||||
}
|
}
|
||||||
|
@ -81,11 +83,13 @@ impl Info {
|
||||||
pub struct DuplicateFinder {
|
pub struct DuplicateFinder {
|
||||||
text_messages: Messages,
|
text_messages: Messages,
|
||||||
information: Info,
|
information: Info,
|
||||||
files_with_identical_names: BTreeMap<String, Vec<FileEntry>>, // File Size, File Entry
|
files_with_identical_names: BTreeMap<String, Vec<FileEntry>>, // File Size, File Entry
|
||||||
files_with_identical_size: BTreeMap<u64, Vec<FileEntry>>, // File Size, File Entry
|
files_with_identical_size_names: BTreeMap<(u64, String), Vec<FileEntry>>, // File (Size, Name), File Entry
|
||||||
files_with_identical_hashes: BTreeMap<u64, Vec<Vec<FileEntry>>>, // File Size, next grouped by file size, next grouped by hash
|
files_with_identical_size: BTreeMap<u64, Vec<FileEntry>>, // File Size, File Entry
|
||||||
files_with_identical_names_referenced: BTreeMap<String, (FileEntry, Vec<FileEntry>)>, // File Size, File Entry
|
files_with_identical_hashes: BTreeMap<u64, Vec<Vec<FileEntry>>>, // File Size, next grouped by file size, next grouped by hash
|
||||||
files_with_identical_size_referenced: BTreeMap<u64, (FileEntry, Vec<FileEntry>)>, // File Size, File Entry
|
files_with_identical_names_referenced: BTreeMap<String, (FileEntry, Vec<FileEntry>)>, // File Size, File Entry
|
||||||
|
files_with_identical_size_names_referenced: BTreeMap<(u64, String), (FileEntry, Vec<FileEntry>)>, // File (Size, Name), File Entry
|
||||||
|
files_with_identical_size_referenced: BTreeMap<u64, (FileEntry, Vec<FileEntry>)>, // File Size, File Entry
|
||||||
files_with_identical_hashes_referenced: BTreeMap<u64, Vec<(FileEntry, Vec<FileEntry>)>>, // File Size, next grouped by file size, next grouped by hash
|
files_with_identical_hashes_referenced: BTreeMap<u64, Vec<(FileEntry, Vec<FileEntry>)>>, // File Size, next grouped by file size, next grouped by hash
|
||||||
directories: Directories,
|
directories: Directories,
|
||||||
allowed_extensions: Extensions,
|
allowed_extensions: Extensions,
|
||||||
|
@ -116,8 +120,10 @@ impl DuplicateFinder {
|
||||||
information: Info::new(),
|
information: Info::new(),
|
||||||
files_with_identical_names: Default::default(),
|
files_with_identical_names: Default::default(),
|
||||||
files_with_identical_size: Default::default(),
|
files_with_identical_size: Default::default(),
|
||||||
|
files_with_identical_size_names: Default::default(),
|
||||||
files_with_identical_hashes: Default::default(),
|
files_with_identical_hashes: Default::default(),
|
||||||
files_with_identical_names_referenced: Default::default(),
|
files_with_identical_names_referenced: Default::default(),
|
||||||
|
files_with_identical_size_names_referenced: Default::default(),
|
||||||
files_with_identical_size_referenced: Default::default(),
|
files_with_identical_size_referenced: Default::default(),
|
||||||
files_with_identical_hashes_referenced: Default::default(),
|
files_with_identical_hashes_referenced: Default::default(),
|
||||||
recursive_search: true,
|
recursive_search: true,
|
||||||
|
@ -148,24 +154,30 @@ impl DuplicateFinder {
|
||||||
|
|
||||||
match self.check_method {
|
match self.check_method {
|
||||||
CheckingMethod::Name => {
|
CheckingMethod::Name => {
|
||||||
if !self.check_files_name(stop_receiver, progress_sender) {
|
self.stopped_search = !self.check_files_name(stop_receiver, progress_sender); // TODO restore this to name
|
||||||
self.stopped_search = true;
|
if self.stopped_search {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckingMethod::SizeName => {
|
||||||
|
self.stopped_search = !self.check_files_size_name(stop_receiver, progress_sender);
|
||||||
|
if self.stopped_search {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CheckingMethod::Size => {
|
CheckingMethod::Size => {
|
||||||
if !self.check_files_size(stop_receiver, progress_sender) {
|
self.stopped_search = !self.check_files_size(stop_receiver, progress_sender);
|
||||||
self.stopped_search = true;
|
if self.stopped_search {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CheckingMethod::Hash => {
|
CheckingMethod::Hash => {
|
||||||
if !self.check_files_size(stop_receiver, progress_sender) {
|
self.stopped_search = !self.check_files_size(stop_receiver, progress_sender);
|
||||||
self.stopped_search = true;
|
if self.stopped_search {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !self.check_files_hash(stop_receiver, progress_sender) {
|
self.stopped_search = !self.check_files_hash(stop_receiver, progress_sender);
|
||||||
self.stopped_search = true;
|
if self.stopped_search {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,6 +233,11 @@ impl DuplicateFinder {
|
||||||
&self.files_with_identical_size
|
&self.files_with_identical_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn get_files_sorted_by_size_name(&self) -> &BTreeMap<(u64, String), Vec<FileEntry>> {
|
||||||
|
&self.files_with_identical_size_names
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn get_files_sorted_by_hash(&self) -> &BTreeMap<u64, Vec<Vec<FileEntry>>> {
|
pub const fn get_files_sorted_by_hash(&self) -> &BTreeMap<u64, Vec<Vec<FileEntry>>> {
|
||||||
&self.files_with_identical_hashes
|
&self.files_with_identical_hashes
|
||||||
|
@ -319,6 +336,11 @@ impl DuplicateFinder {
|
||||||
&self.files_with_identical_size_referenced
|
&self.files_with_identical_size_referenced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_files_with_identical_size_names_referenced(&self) -> &BTreeMap<(u64, String), (FileEntry, Vec<FileEntry>)> {
|
||||||
|
&self.files_with_identical_size_names_referenced
|
||||||
|
}
|
||||||
|
|
||||||
fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
||||||
let group_by_func = if self.case_sensitive_name_comparison {
|
let group_by_func = if self.case_sensitive_name_comparison {
|
||||||
|fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_string()
|
|fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_string()
|
||||||
|
@ -388,18 +410,7 @@ impl DuplicateFinder {
|
||||||
self.files_with_identical_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe));
|
self.files_with_identical_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.calculate_name_stats();
|
||||||
if self.use_reference_folders {
|
|
||||||
for (_fe, vector) in self.files_with_identical_names_referenced.values() {
|
|
||||||
self.information.number_of_duplicated_files_by_name += vector.len();
|
|
||||||
self.information.number_of_groups_by_name += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for vector in self.files_with_identical_names.values() {
|
|
||||||
self.information.number_of_duplicated_files_by_name += vector.len() - 1;
|
|
||||||
self.information.number_of_groups_by_name += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_name");
|
Common::print_time(start_time, SystemTime::now(), "check_files_name");
|
||||||
true
|
true
|
||||||
|
@ -411,6 +422,118 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_name_stats(&mut self) {
|
||||||
|
if self.use_reference_folders {
|
||||||
|
for (_fe, vector) in self.files_with_identical_names_referenced.values() {
|
||||||
|
self.information.number_of_duplicated_files_by_name += vector.len();
|
||||||
|
self.information.number_of_groups_by_name += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for vector in self.files_with_identical_names.values() {
|
||||||
|
self.information.number_of_duplicated_files_by_name += vector.len() - 1;
|
||||||
|
self.information.number_of_groups_by_name += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_files_size_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
||||||
|
let group_by_func = if self.case_sensitive_name_comparison {
|
||||||
|
|fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
|fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_lowercase())
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = DirTraversalBuilder::new()
|
||||||
|
.root_dirs(self.directories.included_directories.clone())
|
||||||
|
.group_by(group_by_func)
|
||||||
|
.stop_receiver(stop_receiver)
|
||||||
|
.progress_sender(progress_sender)
|
||||||
|
.checking_method(CheckingMethod::Name)
|
||||||
|
.directories(self.directories.clone())
|
||||||
|
.allowed_extensions(self.allowed_extensions.clone())
|
||||||
|
.excluded_items(self.excluded_items.clone())
|
||||||
|
.recursive_search(self.recursive_search)
|
||||||
|
.minimal_file_size(self.minimal_file_size)
|
||||||
|
.maximal_file_size(self.maximal_file_size)
|
||||||
|
.build()
|
||||||
|
.run();
|
||||||
|
match result {
|
||||||
|
DirTraversalResult::SuccessFiles {
|
||||||
|
start_time,
|
||||||
|
grouped_file_entries,
|
||||||
|
warnings,
|
||||||
|
} => {
|
||||||
|
self.files_with_identical_size_names = grouped_file_entries;
|
||||||
|
self.text_messages.warnings.extend(warnings);
|
||||||
|
|
||||||
|
// Create new BTreeMap without single size entries(files have not duplicates)
|
||||||
|
let mut new_map: BTreeMap<(u64, String), Vec<FileEntry>> = Default::default();
|
||||||
|
|
||||||
|
for (name_size, vector) in &self.files_with_identical_size_names {
|
||||||
|
if vector.len() > 1 {
|
||||||
|
new_map.insert(name_size.clone(), vector.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.files_with_identical_size_names = new_map;
|
||||||
|
|
||||||
|
// Reference - only use in size, because later hash will be counted differently
|
||||||
|
if self.use_reference_folders {
|
||||||
|
let mut btree_map = Default::default();
|
||||||
|
mem::swap(&mut self.files_with_identical_size_names, &mut btree_map);
|
||||||
|
let reference_directories = self.directories.reference_directories.clone();
|
||||||
|
let vec = btree_map
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(_size, vec_file_entry)| {
|
||||||
|
let mut files_from_referenced_folders = Vec::new();
|
||||||
|
let mut normal_files = Vec::new();
|
||||||
|
for file_entry in vec_file_entry {
|
||||||
|
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
|
||||||
|
files_from_referenced_folders.push(file_entry);
|
||||||
|
} else {
|
||||||
|
normal_files.push(file_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((files_from_referenced_folders.pop().unwrap(), normal_files))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<(FileEntry, Vec<FileEntry>)>>();
|
||||||
|
for (fe, vec_fe) in vec {
|
||||||
|
self.files_with_identical_size_names_referenced
|
||||||
|
.insert((fe.size, fe.path.to_string_lossy().to_string()), (fe, vec_fe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.calculate_size_name_stats();
|
||||||
|
|
||||||
|
Common::print_time(start_time, SystemTime::now(), "check_files_size_name");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
DirTraversalResult::SuccessFolders { .. } => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
DirTraversalResult::Stopped => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_size_name_stats(&mut self) {
|
||||||
|
if self.use_reference_folders {
|
||||||
|
for ((size, _name), (_fe, vector)) in &self.files_with_identical_size_names_referenced {
|
||||||
|
self.information.number_of_duplicated_files_by_size_name += vector.len();
|
||||||
|
self.information.number_of_groups_by_size_name += 1;
|
||||||
|
self.information.lost_space_by_size += (vector.len() as u64) * size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ((size, _name), vector) in &self.files_with_identical_size_names {
|
||||||
|
self.information.number_of_duplicated_files_by_size_name += vector.len() - 1;
|
||||||
|
self.information.number_of_groups_by_size_name += 1;
|
||||||
|
self.information.lost_space_by_size += (vector.len() as u64 - 1) * size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read file length and puts it to different boxes(each for different lengths)
|
/// Read file length and puts it to different boxes(each for different lengths)
|
||||||
/// If in box is only 1 result, then it is removed
|
/// If in box is only 1 result, then it is removed
|
||||||
fn check_files_size(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
fn check_files_size(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
||||||
|
@ -459,49 +582,8 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference - only use in size, because later hash will be counted differently
|
self.filter_reference_folders_by_size();
|
||||||
if self.use_reference_folders && self.check_method == CheckingMethod::Size {
|
self.calculate_size_stats();
|
||||||
let mut btree_map = Default::default();
|
|
||||||
mem::swap(&mut self.files_with_identical_size, &mut btree_map);
|
|
||||||
let reference_directories = self.directories.reference_directories.clone();
|
|
||||||
let vec = btree_map
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(_size, vec_file_entry)| {
|
|
||||||
let mut files_from_referenced_folders = Vec::new();
|
|
||||||
let mut normal_files = Vec::new();
|
|
||||||
for file_entry in vec_file_entry {
|
|
||||||
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
|
|
||||||
files_from_referenced_folders.push(file_entry);
|
|
||||||
} else {
|
|
||||||
normal_files.push(file_entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((files_from_referenced_folders.pop().unwrap(), normal_files))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<(FileEntry, Vec<FileEntry>)>>();
|
|
||||||
for (fe, vec_fe) in vec {
|
|
||||||
self.files_with_identical_size_referenced.insert(fe.size, (fe, vec_fe));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.use_reference_folders {
|
|
||||||
for (size, (_fe, vector)) in &self.files_with_identical_size_referenced {
|
|
||||||
self.information.number_of_duplicated_files_by_size += vector.len();
|
|
||||||
self.information.number_of_groups_by_size += 1;
|
|
||||||
self.information.lost_space_by_size += (vector.len() as u64) * size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (size, vector) in &self.files_with_identical_size {
|
|
||||||
self.information.number_of_duplicated_files_by_size += vector.len() - 1;
|
|
||||||
self.information.number_of_groups_by_size += 1;
|
|
||||||
self.information.lost_space_by_size += (vector.len() as u64 - 1) * size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_size");
|
Common::print_time(start_time, SystemTime::now(), "check_files_size");
|
||||||
true
|
true
|
||||||
|
@ -513,35 +595,78 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The slowest checking type, which must be applied after checking for size
|
fn calculate_size_stats(&mut self) {
|
||||||
fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
if self.use_reference_folders {
|
||||||
assert_eq!(self.check_method, CheckingMethod::Hash);
|
for (size, (_fe, vector)) in &self.files_with_identical_size_referenced {
|
||||||
|
self.information.number_of_duplicated_files_by_size += vector.len();
|
||||||
|
self.information.number_of_groups_by_size += 1;
|
||||||
|
self.information.lost_space_by_size += (vector.len() as u64) * size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (size, vector) in &self.files_with_identical_size {
|
||||||
|
self.information.number_of_duplicated_files_by_size += vector.len() - 1;
|
||||||
|
self.information.number_of_groups_by_size += 1;
|
||||||
|
self.information.lost_space_by_size += (vector.len() as u64 - 1) * size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let check_type = Arc::new(self.hash_type);
|
/// This step check for references, only when checking for size.
|
||||||
|
/// This is needed, because later reference folders looks for hashes, not size
|
||||||
|
fn filter_reference_folders_by_size(&mut self) {
|
||||||
|
if self.use_reference_folders && self.check_method == CheckingMethod::Size {
|
||||||
|
let mut btree_map = Default::default();
|
||||||
|
mem::swap(&mut self.files_with_identical_size, &mut btree_map);
|
||||||
|
let reference_directories = self.directories.reference_directories.clone();
|
||||||
|
let vec = btree_map
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(_size, vec_file_entry)| {
|
||||||
|
let mut files_from_referenced_folders = Vec::new();
|
||||||
|
let mut normal_files = Vec::new();
|
||||||
|
for file_entry in vec_file_entry {
|
||||||
|
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
|
||||||
|
files_from_referenced_folders.push(file_entry);
|
||||||
|
} else {
|
||||||
|
normal_files.push(file_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let start_time: SystemTime = SystemTime::now();
|
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
|
||||||
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
|
None
|
||||||
let mut pre_checked_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
} else {
|
||||||
|
Some((files_from_referenced_folders.pop().unwrap(), normal_files))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<(FileEntry, Vec<FileEntry>)>>();
|
||||||
|
for (fe, vec_fe) in vec {
|
||||||
|
self.files_with_identical_size_referenced.insert(fe.size, (fe, vec_fe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//// PROGRESS THREAD START
|
// TODO Generalize this if possible with different tools
|
||||||
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
fn prepare_hash_thread_handler(
|
||||||
|
&self,
|
||||||
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>,
|
||||||
|
progress_thread_run: Arc<AtomicBool>,
|
||||||
let progress_thread_handle = if let Some(progress_sender) = progress_sender {
|
atomic_counter: Arc<AtomicUsize>,
|
||||||
|
current_stage: u8,
|
||||||
|
max_stage: u8,
|
||||||
|
max_value: usize,
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
if let Some(progress_sender) = progress_sender {
|
||||||
let progress_send = progress_sender.clone();
|
let progress_send = progress_sender.clone();
|
||||||
let progress_thread_run = progress_thread_run.clone();
|
let progress_thread_run = progress_thread_run;
|
||||||
let atomic_file_counter = atomic_file_counter.clone();
|
let atomic_counter = atomic_counter;
|
||||||
let files_to_check = self.files_with_identical_size.values().map(Vec::len).sum();
|
|
||||||
let checking_method = self.check_method;
|
let checking_method = self.check_method;
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
progress_send
|
progress_send
|
||||||
.unbounded_send(ProgressData {
|
.unbounded_send(ProgressData {
|
||||||
checking_method,
|
checking_method,
|
||||||
current_stage: 1,
|
current_stage,
|
||||||
max_stage: 2,
|
max_stage,
|
||||||
entries_checked: atomic_file_counter.load(Ordering::Relaxed),
|
entries_checked: atomic_counter.load(Ordering::Relaxed),
|
||||||
entries_to_check: files_to_check,
|
entries_to_check: max_value,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if !progress_thread_run.load(Ordering::Relaxed) {
|
if !progress_thread_run.load(Ordering::Relaxed) {
|
||||||
|
@ -551,166 +676,174 @@ impl DuplicateFinder {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
thread::spawn(|| {})
|
thread::spawn(|| {})
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//// PROGRESS THREAD END
|
fn prehashing(
|
||||||
|
&mut self,
|
||||||
|
stop_receiver: Option<&Receiver<()>>,
|
||||||
|
progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>,
|
||||||
|
pre_checked_map: &mut BTreeMap<u64, Vec<FileEntry>>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let start_time: SystemTime = SystemTime::now();
|
||||||
|
let check_type = self.hash_type;
|
||||||
|
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////// PREHASHING START
|
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
||||||
{
|
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
||||||
let loaded_hash_map;
|
let progress_thread_handle = self.prepare_hash_thread_handler(
|
||||||
let mut records_already_cached: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
progress_sender,
|
||||||
let mut non_cached_files_to_check: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
progress_thread_run.clone(),
|
||||||
|
atomic_file_counter.clone(),
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
self.files_with_identical_size.values().map(Vec::len).sum(),
|
||||||
|
);
|
||||||
|
|
||||||
// Cache algorithm
|
let loaded_hash_map;
|
||||||
// - Load data from cache
|
let mut records_already_cached: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
||||||
// - Convert from BT<u64,Vec<FileEntry>> to BT<String,FileEntry>
|
let mut non_cached_files_to_check: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
||||||
// - Save to proper values
|
|
||||||
if self.use_prehash_cache {
|
|
||||||
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.delete_outdated_cache, &self.hash_type, true) {
|
|
||||||
Some(t) => t,
|
|
||||||
None => Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut loaded_hash_map2: BTreeMap<String, FileEntry> = Default::default();
|
// Cache algorithm
|
||||||
for vec_file_entry in loaded_hash_map.values() {
|
// - Load data from cache
|
||||||
for file_entry in vec_file_entry {
|
// - Convert from BT<u64,Vec<FileEntry>> to BT<String,FileEntry>
|
||||||
loaded_hash_map2.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
// - Save to proper values
|
||||||
|
if self.use_prehash_cache {
|
||||||
|
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.delete_outdated_cache, &self.hash_type, true) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut loaded_hash_map2: BTreeMap<String, FileEntry> = Default::default();
|
||||||
|
for vec_file_entry in loaded_hash_map.values() {
|
||||||
|
for file_entry in vec_file_entry {
|
||||||
|
loaded_hash_map2.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::if_same_then_else)]
|
||||||
|
for vec_file_entry in self.files_with_identical_size.values() {
|
||||||
|
for file_entry in vec_file_entry {
|
||||||
|
let name = file_entry.path.to_string_lossy().to_string();
|
||||||
|
if !loaded_hash_map2.contains_key(&name) {
|
||||||
|
// If loaded data doesn't contains current image info
|
||||||
|
non_cached_files_to_check.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
||||||
|
} else if file_entry.size != loaded_hash_map2.get(&name).unwrap().size || file_entry.modified_date != loaded_hash_map2.get(&name).unwrap().modified_date {
|
||||||
|
// When size or modification date of image changed, then it is clear that is different image
|
||||||
|
non_cached_files_to_check.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
||||||
|
} else {
|
||||||
|
// Checking may be omitted when already there is entry with same size and modification date
|
||||||
|
records_already_cached.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loaded_hash_map = Default::default();
|
||||||
|
mem::swap(&mut self.files_with_identical_size, &mut non_cached_files_to_check);
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::if_same_then_else)]
|
#[allow(clippy::type_complexity)]
|
||||||
for vec_file_entry in self.files_with_identical_size.values() {
|
let pre_hash_results: Vec<(u64, BTreeMap<String, Vec<FileEntry>>, Vec<String>)> = non_cached_files_to_check
|
||||||
for file_entry in vec_file_entry {
|
.par_iter()
|
||||||
let name = file_entry.path.to_string_lossy().to_string();
|
.map(|(size, vec_file_entry)| {
|
||||||
if !loaded_hash_map2.contains_key(&name) {
|
let mut hashmap_with_hash: BTreeMap<String, Vec<FileEntry>> = Default::default();
|
||||||
// If loaded data doesn't contains current image info
|
let mut errors: Vec<String> = Vec::new();
|
||||||
non_cached_files_to_check.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
let mut buffer = [0u8; 1024 * 2];
|
||||||
} else if file_entry.size != loaded_hash_map2.get(&name).unwrap().size || file_entry.modified_date != loaded_hash_map2.get(&name).unwrap().modified_date {
|
|
||||||
// When size or modification date of image changed, then it is clear that is different image
|
atomic_file_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed);
|
||||||
non_cached_files_to_check.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
for file_entry in vec_file_entry {
|
||||||
} else {
|
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
||||||
// Checking may be omitted when already there is entry with same size and modification date
|
check_was_stopped.store(true, Ordering::Relaxed);
|
||||||
records_already_cached.entry(file_entry.size).or_insert_with(Vec::new).push(file_entry.clone());
|
return None;
|
||||||
|
}
|
||||||
|
match hash_calculation(&mut buffer, file_entry, &check_type, 0) {
|
||||||
|
Ok(hash_string) => {
|
||||||
|
hashmap_with_hash.entry(hash_string.clone()).or_insert_with(Vec::new).push(file_entry.clone());
|
||||||
}
|
}
|
||||||
|
Err(s) => errors.push(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
Some((*size, hashmap_with_hash, errors))
|
||||||
loaded_hash_map = Default::default();
|
})
|
||||||
mem::swap(&mut self.files_with_identical_size, &mut non_cached_files_to_check);
|
.while_some()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// End thread which send info to gui
|
||||||
|
progress_thread_run.store(false, Ordering::Relaxed);
|
||||||
|
progress_thread_handle.join().unwrap();
|
||||||
|
|
||||||
|
// Check if user aborted search(only from GUI)
|
||||||
|
if check_was_stopped.load(Ordering::Relaxed) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data from cache
|
||||||
|
for (size, vec_file_entry) in &records_already_cached {
|
||||||
|
pre_checked_map.entry(*size).or_insert_with(Vec::new).append(&mut vec_file_entry.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check results
|
||||||
|
for (size, hash_map, errors) in &pre_hash_results {
|
||||||
|
self.text_messages.warnings.append(&mut errors.clone());
|
||||||
|
for vec_file_entry in hash_map.values() {
|
||||||
|
if vec_file_entry.len() > 1 {
|
||||||
|
pre_checked_map.entry(*size).or_insert_with(Vec::new).append(&mut vec_file_entry.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
if self.use_prehash_cache {
|
||||||
let pre_hash_results: Vec<(u64, BTreeMap<String, Vec<FileEntry>>, Vec<String>)> = non_cached_files_to_check
|
// All results = records already cached + computed results
|
||||||
.par_iter()
|
let mut save_cache_to_hashmap: BTreeMap<String, FileEntry> = Default::default();
|
||||||
.map(|(size, vec_file_entry)| {
|
|
||||||
let mut hashmap_with_hash: BTreeMap<String, Vec<FileEntry>> = Default::default();
|
|
||||||
let mut errors: Vec<String> = Vec::new();
|
|
||||||
let mut buffer = [0u8; 1024 * 2];
|
|
||||||
|
|
||||||
atomic_file_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed);
|
for (size, vec_file_entry) in loaded_hash_map {
|
||||||
|
if size >= self.minimal_prehash_cache_file_size {
|
||||||
for file_entry in vec_file_entry {
|
for file_entry in vec_file_entry {
|
||||||
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
|
save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
||||||
check_was_stopped.store(true, Ordering::Relaxed);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match hash_calculation(&mut buffer, file_entry, &check_type, 0) {
|
|
||||||
Ok(hash_string) => {
|
|
||||||
hashmap_with_hash.entry(hash_string.clone()).or_insert_with(Vec::new).push(file_entry.clone());
|
|
||||||
}
|
|
||||||
Err(s) => errors.push(s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some((*size, hashmap_with_hash, errors))
|
|
||||||
})
|
|
||||||
.while_some()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// End thread which send info to gui
|
|
||||||
progress_thread_run.store(false, Ordering::Relaxed);
|
|
||||||
progress_thread_handle.join().unwrap();
|
|
||||||
|
|
||||||
// Check if user aborted search(only from GUI)
|
|
||||||
if check_was_stopped.load(Ordering::Relaxed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add data from cache
|
|
||||||
for (size, vec_file_entry) in &records_already_cached {
|
|
||||||
pre_checked_map.entry(*size).or_insert_with(Vec::new).append(&mut vec_file_entry.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check results
|
|
||||||
for (size, hash_map, errors) in &pre_hash_results {
|
|
||||||
self.text_messages.warnings.append(&mut errors.clone());
|
|
||||||
for vec_file_entry in hash_map.values() {
|
|
||||||
if vec_file_entry.len() > 1 {
|
|
||||||
pre_checked_map.entry(*size).or_insert_with(Vec::new).append(&mut vec_file_entry.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.use_prehash_cache {
|
for (size, hash_map, _errors) in &pre_hash_results {
|
||||||
// All results = records already cached + computed results
|
if *size >= self.minimal_prehash_cache_file_size {
|
||||||
let mut save_cache_to_hashmap: BTreeMap<String, FileEntry> = Default::default();
|
for vec_file_entry in hash_map.values() {
|
||||||
|
|
||||||
for (size, vec_file_entry) in loaded_hash_map {
|
|
||||||
if size >= self.minimal_prehash_cache_file_size {
|
|
||||||
for file_entry in vec_file_entry {
|
for file_entry in vec_file_entry {
|
||||||
save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size, hash_map, _errors) in &pre_hash_results {
|
|
||||||
if *size >= self.minimal_prehash_cache_file_size {
|
|
||||||
for vec_file_entry in hash_map.values() {
|
|
||||||
for file_entry in vec_file_entry {
|
|
||||||
save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
save_hashes_to_file(&save_cache_to_hashmap, &mut self.text_messages, &self.hash_type, true, self.minimal_prehash_cache_file_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
save_hashes_to_file(&save_cache_to_hashmap, &mut self.text_messages, &self.hash_type, true, self.minimal_prehash_cache_file_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////// PREHASHING END
|
|
||||||
|
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_hash - prehash");
|
Common::print_time(start_time, SystemTime::now(), "check_files_hash - prehash");
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_hashing(
|
||||||
|
&mut self,
|
||||||
|
stop_receiver: Option<&Receiver<()>>,
|
||||||
|
progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>,
|
||||||
|
mut pre_checked_map: BTreeMap<u64, Vec<FileEntry>>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
|
||||||
|
|
||||||
|
let check_type = self.hash_type;
|
||||||
let start_time: SystemTime = SystemTime::now();
|
let start_time: SystemTime = SystemTime::now();
|
||||||
|
|
||||||
/////////////////////////
|
|
||||||
|
|
||||||
//// PROGRESS THREAD START
|
//// PROGRESS THREAD START
|
||||||
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
let progress_thread_run = Arc::new(AtomicBool::new(true));
|
||||||
|
|
||||||
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let progress_thread_handle = if let Some(progress_sender) = progress_sender {
|
let progress_thread_handle = self.prepare_hash_thread_handler(
|
||||||
let progress_send = progress_sender.clone();
|
progress_sender,
|
||||||
let progress_thread_run = progress_thread_run.clone();
|
progress_thread_run.clone(),
|
||||||
let atomic_file_counter = atomic_file_counter.clone();
|
atomic_file_counter.clone(),
|
||||||
let files_to_check = pre_checked_map.values().map(Vec::len).sum();
|
2,
|
||||||
let checking_method = self.check_method;
|
2,
|
||||||
thread::spawn(move || loop {
|
pre_checked_map.values().map(Vec::len).sum(),
|
||||||
progress_send
|
);
|
||||||
.unbounded_send(ProgressData {
|
|
||||||
checking_method,
|
|
||||||
current_stage: 2,
|
|
||||||
max_stage: 2,
|
|
||||||
entries_checked: atomic_file_counter.load(Ordering::Relaxed),
|
|
||||||
entries_to_check: files_to_check,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
if !progress_thread_run.load(Ordering::Relaxed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sleep(Duration::from_millis(LOOP_DURATION as u64));
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
thread::spawn(|| {})
|
|
||||||
};
|
|
||||||
|
|
||||||
//// PROGRESS THREAD END
|
//// PROGRESS THREAD END
|
||||||
|
|
||||||
|
@ -828,7 +961,7 @@ impl DuplicateFinder {
|
||||||
|
|
||||||
// Break if stop was clicked after saving to cache
|
// Break if stop was clicked after saving to cache
|
||||||
if check_was_stopped.load(Ordering::Relaxed) {
|
if check_was_stopped.load(Ordering::Relaxed) {
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size, hash_map, mut errors) in full_hash_results {
|
for (size, hash_map, mut errors) in full_hash_results {
|
||||||
|
@ -840,9 +973,11 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Common::print_time(start_time, SystemTime::now(), "delete_files");
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////// HASHING END
|
fn hash_reference_folders(&mut self) {
|
||||||
|
|
||||||
// Reference - only use in size, because later hash will be counted differently
|
// Reference - only use in size, because later hash will be counted differently
|
||||||
if self.use_reference_folders {
|
if self.use_reference_folders {
|
||||||
let mut btree_map = Default::default();
|
let mut btree_map = Default::default();
|
||||||
|
@ -897,8 +1032,24 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_hash - full hash");
|
/// The slowest checking type, which must be applied after checking for size
|
||||||
|
fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
|
||||||
|
assert_eq!(self.check_method, CheckingMethod::Hash);
|
||||||
|
|
||||||
|
let mut pre_checked_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
||||||
|
let ret = self.prehashing(stop_receiver, progress_sender, &mut pre_checked_map);
|
||||||
|
if ret.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = self.full_hashing(stop_receiver, progress_sender, pre_checked_map);
|
||||||
|
if ret.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hash_reference_folders();
|
||||||
|
|
||||||
// Clean unused data
|
// Clean unused data
|
||||||
self.files_with_identical_size = Default::default();
|
self.files_with_identical_size = Default::default();
|
||||||
|
@ -920,6 +1071,11 @@ impl DuplicateFinder {
|
||||||
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
|
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CheckingMethod::SizeName => {
|
||||||
|
for vector in self.files_with_identical_size_names.values() {
|
||||||
|
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
|
||||||
|
}
|
||||||
|
}
|
||||||
CheckingMethod::Hash => {
|
CheckingMethod::Hash => {
|
||||||
for vector_vectors in self.files_with_identical_hashes.values() {
|
for vector_vectors in self.files_with_identical_hashes.values() {
|
||||||
for vector in vector_vectors.iter() {
|
for vector in vector_vectors.iter() {
|
||||||
|
@ -1053,6 +1209,30 @@ impl SaveResults for DuplicateFinder {
|
||||||
write!(writer, "Not found any files with same names.").unwrap();
|
write!(writer, "Not found any files with same names.").unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CheckingMethod::SizeName => {
|
||||||
|
if !self.files_with_identical_names.is_empty() {
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"-------------------------------------------------Files with same size and names-------------------------------------------------"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
writer,
|
||||||
|
"Found {} files in {} groups with same size and name(may have different content)",
|
||||||
|
self.information.number_of_duplicated_files_by_size_name, self.information.number_of_groups_by_size_name,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
for ((size, name), vector) in self.files_with_identical_size_names.iter().rev() {
|
||||||
|
writeln!(writer, "Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len()).unwrap();
|
||||||
|
for j in vector {
|
||||||
|
writeln!(writer, "{}", j.path.display()).unwrap();
|
||||||
|
}
|
||||||
|
writeln!(writer).unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
write!(writer, "Not found any files with same size and names.").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
CheckingMethod::Size => {
|
CheckingMethod::Size => {
|
||||||
if !self.files_with_identical_size.is_empty() {
|
if !self.files_with_identical_size.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
|
@ -1137,6 +1317,20 @@ impl PrintResults for DuplicateFinder {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CheckingMethod::SizeName => {
|
||||||
|
for i in &self.files_with_identical_size_names {
|
||||||
|
number_of_files += i.1.len() as u64;
|
||||||
|
number_of_groups += 1;
|
||||||
|
}
|
||||||
|
println!("Found {number_of_files} files in {number_of_groups} groups with same size and name(may have different content)",);
|
||||||
|
for ((size, name), vector) in &self.files_with_identical_size_names {
|
||||||
|
println!("Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len());
|
||||||
|
for j in vector {
|
||||||
|
println!("{}", j.path.display());
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
CheckingMethod::Hash => {
|
CheckingMethod::Hash => {
|
||||||
for vector in self.files_with_identical_hashes.values() {
|
for vector in self.files_with_identical_hashes.values() {
|
||||||
for j in vector {
|
for j in vector {
|
||||||
|
|
|
@ -31,6 +31,7 @@ pub enum DeleteMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||||
pub struct MusicSimilarity : u32 {
|
pub struct MusicSimilarity : u32 {
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
|
|
||||||
|
|
|
@ -965,7 +965,7 @@ impl SimilarImages {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
for vec_file_entry in collected_similar_images.values() {
|
for vec_file_entry in collected_similar_images.values() {
|
||||||
if vec_file_entry.is_empty() {
|
if vec_file_entry.is_empty() {
|
||||||
println!("Empty Element {vec_file_entry:?}");
|
println!("Empty group");
|
||||||
found = true;
|
found = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -984,7 +984,7 @@ impl SimilarImages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert!(!found, "Found Invalid entries");
|
assert!(!found, "Found Invalid entries, verify errors before"); // TODO crashes with empty result with reference folder, verify why
|
||||||
}
|
}
|
||||||
self.similar_vectors = collected_similar_images.into_values().collect();
|
self.similar_vectors = collected_similar_images.into_values().collect();
|
||||||
|
|
||||||
|
|
|
@ -10,29 +10,29 @@ homepage = "https://github.com/qarmin/czkawka"
|
||||||
repository = "https://github.com/qarmin/czkawka"
|
repository = "https://github.com/qarmin/czkawka"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gdk4 = "0.6.2"
|
gdk4 = "0.6.3"
|
||||||
glib = "0.17.2"
|
glib = "0.17.5"
|
||||||
|
|
||||||
humansize = "2.1.3"
|
humansize = "2.1.3"
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.24"
|
||||||
|
|
||||||
# Used for sending stop signal across threads
|
# Used for sending stop signal across threads
|
||||||
crossbeam-channel = "0.5.7"
|
crossbeam-channel = "0.5.7"
|
||||||
|
|
||||||
# To get information about progress
|
# To get information about progress
|
||||||
futures = "0.3.26"
|
futures = "0.3.28"
|
||||||
|
|
||||||
# For saving/loading config files to specific directories
|
# For saving/loading config files to specific directories
|
||||||
directories-next = "2.0.0"
|
directories-next = "2.0.0"
|
||||||
|
|
||||||
# For opening files
|
# For opening files
|
||||||
open = "3.2.0"
|
open = "4.0.1"
|
||||||
|
|
||||||
# To get image preview
|
# To get image preview
|
||||||
image = "0.24.5"
|
image = "0.24.6"
|
||||||
|
|
||||||
# To be able to use custom select
|
# To be able to use custom select
|
||||||
regex = "1.7.1"
|
regex = "1.7.3"
|
||||||
|
|
||||||
# To get image_hasher types
|
# To get image_hasher types
|
||||||
image_hasher = "1.1.2"
|
image_hasher = "1.1.2"
|
||||||
|
@ -45,15 +45,15 @@ fs_extra = "1.3.0"
|
||||||
|
|
||||||
# Language
|
# Language
|
||||||
i18n-embed = { version = "0.13.8", features = ["fluent-system", "desktop-requester"] }
|
i18n-embed = { version = "0.13.8", features = ["fluent-system", "desktop-requester"] }
|
||||||
i18n-embed-fl = "0.6.5"
|
i18n-embed-fl = "0.6.6"
|
||||||
rust-embed = "6.6.0"
|
rust-embed = "6.6.1"
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3.9", features = ["combaseapi", "objbase", "shobjidl_core", "windef", "winerror", "wtypesbase", "winuser"] }
|
winapi = { version = "0.3.9", features = ["combaseapi", "objbase", "shobjidl_core", "windef", "winerror", "wtypesbase", "winuser"] }
|
||||||
|
|
||||||
[dependencies.gtk4]
|
[dependencies.gtk4]
|
||||||
version = "0.6.2"
|
version = "0.6.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["v4_6"]
|
features = ["v4_6"]
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ duplicate_case_sensitive_name_tooltip =
|
||||||
|
|
||||||
Disabling such option will group names without checking if each letter is same size e.g. żoŁD <-> Żołd
|
Disabling such option will group names without checking if each letter is same size e.g. żoŁD <-> Żołd
|
||||||
|
|
||||||
|
duplicate_mode_size_name_combo_box = Size and Name
|
||||||
duplicate_mode_name_combo_box = Name
|
duplicate_mode_name_combo_box = Name
|
||||||
duplicate_mode_size_combo_box = Size
|
duplicate_mode_size_combo_box = Size
|
||||||
duplicate_mode_hash_combo_box = Hash
|
duplicate_mode_hash_combo_box = Hash
|
||||||
|
@ -447,6 +448,7 @@ progress_scanning_music_tags_end = Comparing tags of {$file_checked}/{$all_files
|
||||||
progress_scanning_music_tags = Reading tags of {$file_checked}/{$all_files} music file
|
progress_scanning_music_tags = Reading tags of {$file_checked}/{$all_files} music file
|
||||||
progress_scanning_empty_folders = Scanning {$folder_number} folder
|
progress_scanning_empty_folders = Scanning {$folder_number} folder
|
||||||
progress_scanning_size = Scanning size of {$file_number} file
|
progress_scanning_size = Scanning size of {$file_number} file
|
||||||
|
progress_scanning_size_name = Scanning name and size of {$file_number} file
|
||||||
progress_scanning_name = Scanning name of {$file_number} file
|
progress_scanning_name = Scanning name of {$file_number} file
|
||||||
progress_analyzed_partial_hash = Analyzed partial hash of {$file_checked}/{$all_files} files
|
progress_analyzed_partial_hash = Analyzed partial hash of {$file_checked}/{$all_files} files
|
||||||
progress_analyzed_full_hash = Analyzed full hash of {$file_checked}/{$all_files} files
|
progress_analyzed_full_hash = Analyzed full hash of {$file_checked}/{$all_files} files
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -395,13 +395,13 @@ fn generate_cache_for_results(vector_with_path: Vec<(String, String, TreePath)>)
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
#[allow(clippy::never_loop)]
|
||||||
loop {
|
loop {
|
||||||
let Some(pixbuf_big) = resize_pixbuf_dimension(&pixbuf, (BIG_PREVIEW_SIZE, BIG_PREVIEW_SIZE), InterpType::Bilinear) else{
|
let Some(pixbuf_big) = resize_pixbuf_dimension(&pixbuf, (BIG_PREVIEW_SIZE, BIG_PREVIEW_SIZE), InterpType::Bilinear) else {
|
||||||
println!("Failed to resize image {full_path}.");
|
println!("Failed to resize image {full_path}.");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let Some(pixbuf_small) = resize_pixbuf_dimension(&pixbuf_big, (SMALL_PREVIEW_SIZE, SMALL_PREVIEW_SIZE), InterpType::Bilinear) else {
|
let Some(pixbuf_small) = resize_pixbuf_dimension(&pixbuf_big, (SMALL_PREVIEW_SIZE, SMALL_PREVIEW_SIZE), InterpType::Bilinear) else {
|
||||||
println!("Failed to resize image {full_path}.");
|
println!("Failed to resize image {full_path}.");
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
big_img.set_from_pixbuf(Some(&pixbuf_big));
|
big_img.set_from_pixbuf(Some(&pixbuf_big));
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
|
||||||
use crate::gui_structs::gui_data::GuiData;
|
use crate::gui_structs::gui_data::GuiData;
|
||||||
use crate::gui_structs::gui_popovers_sort::GuiSortPopovers;
|
use crate::gui_structs::gui_popovers_sort::GuiSortPopovers;
|
||||||
use crate::help_functions::PopoverTypes;
|
use crate::help_functions::PopoverTypes;
|
||||||
use crate::notebook_enums::{to_notebook_main_enum, NotebookMainEnum};
|
use crate::notebook_enums::{to_notebook_main_enum, NotebookMainEnum};
|
||||||
use crate::notebook_info::NOTEBOOKS_INFO;
|
use crate::notebook_info::NOTEBOOKS_INFO;
|
||||||
use gtk4::prelude::*;
|
|
||||||
|
|
||||||
pub fn connect_button_sort(gui_data: &GuiData) {
|
pub fn connect_button_sort(gui_data: &GuiData) {
|
||||||
let popovers_sort = gui_data.popovers_sort.clone();
|
let popovers_sort = gui_data.popovers_sort.clone();
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn connect_duplicate_combo_box(gui_data: &GuiData) {
|
||||||
label_duplicate_hash_type.set_visible(false);
|
label_duplicate_hash_type.set_visible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index as usize].check_method == CheckingMethod::Name {
|
if [CheckingMethod::Name, CheckingMethod::SizeName].contains(&DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index as usize].check_method) {
|
||||||
check_button_duplicate_case_sensitive_name.set_visible(true);
|
check_button_duplicate_case_sensitive_name.set_visible(true);
|
||||||
} else {
|
} else {
|
||||||
check_button_duplicate_case_sensitive_name.set_visible(false);
|
check_button_duplicate_case_sensitive_name.set_visible(false);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::{ListStore, TreeIter};
|
use gtk4::{ListStore, TreeIter};
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use crate::gui_structs::gui_data::GuiData;
|
use crate::gui_structs::gui_data::GuiData;
|
||||||
use crate::help_functions::*;
|
use crate::help_functions::*;
|
||||||
|
@ -118,10 +119,11 @@ pub fn connect_popover_sort(gui_data: &GuiData) {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::connect_things::connect_popovers_sort::{popover_sort_general, sort_iters};
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::{Popover, TreeView};
|
use gtk4::{Popover, TreeView};
|
||||||
|
|
||||||
|
use crate::connect_things::connect_popovers_sort::{popover_sort_general, sort_iters};
|
||||||
|
|
||||||
#[gtk4::test]
|
#[gtk4::test]
|
||||||
fn test_sort_iters() {
|
fn test_sort_iters() {
|
||||||
let columns_types: &[glib::types::Type] = &[glib::types::Type::U32, glib::types::Type::STRING];
|
let columns_types: &[glib::types::Type] = &[glib::types::Type::U32, glib::types::Type::STRING];
|
||||||
|
|
|
@ -109,6 +109,16 @@ pub fn connect_progress_window(
|
||||||
));
|
));
|
||||||
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
|
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
|
||||||
}
|
}
|
||||||
|
common_dir_traversal::CheckingMethod::SizeName => {
|
||||||
|
label_stage.show();
|
||||||
|
grid_progress_stages.hide();
|
||||||
|
|
||||||
|
label_stage.set_text(&flg!(
|
||||||
|
"progress_scanning_size_name",
|
||||||
|
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
|
||||||
|
));
|
||||||
|
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
|
||||||
|
}
|
||||||
common_dir_traversal::CheckingMethod::Size => {
|
common_dir_traversal::CheckingMethod::Size => {
|
||||||
label_stage.show();
|
label_stage.show();
|
||||||
grid_progress_stages.hide();
|
grid_progress_stages.hide();
|
||||||
|
|
|
@ -549,9 +549,8 @@ impl GuiMainNotebook {
|
||||||
CheckingMethod::Hash => flg!("duplicate_mode_hash_combo_box"),
|
CheckingMethod::Hash => flg!("duplicate_mode_hash_combo_box"),
|
||||||
CheckingMethod::Size => flg!("duplicate_mode_size_combo_box"),
|
CheckingMethod::Size => flg!("duplicate_mode_size_combo_box"),
|
||||||
CheckingMethod::Name => flg!("duplicate_mode_name_combo_box"),
|
CheckingMethod::Name => flg!("duplicate_mode_name_combo_box"),
|
||||||
_ => {
|
CheckingMethod::SizeName => flg!("duplicate_mode_size_name_combo_box"),
|
||||||
panic!()
|
CheckingMethod::None => panic!(),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
self.combo_box_duplicate_check_method.append_text(&text);
|
self.combo_box_duplicate_check_method.append_text(&text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub struct CheckMethodStruct {
|
||||||
pub check_method: CheckingMethod,
|
pub check_method: CheckingMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DUPLICATES_CHECK_METHOD_COMBO_BOX: [CheckMethodStruct; 3] = [
|
pub const DUPLICATES_CHECK_METHOD_COMBO_BOX: [CheckMethodStruct; 4] = [
|
||||||
CheckMethodStruct {
|
CheckMethodStruct {
|
||||||
eng_name: "Hash",
|
eng_name: "Hash",
|
||||||
check_method: CheckingMethod::Hash,
|
check_method: CheckingMethod::Hash,
|
||||||
|
@ -42,6 +42,10 @@ pub const DUPLICATES_CHECK_METHOD_COMBO_BOX: [CheckMethodStruct; 3] = [
|
||||||
eng_name: "Name",
|
eng_name: "Name",
|
||||||
check_method: CheckingMethod::Name,
|
check_method: CheckingMethod::Name,
|
||||||
},
|
},
|
||||||
|
CheckMethodStruct {
|
||||||
|
eng_name: "Size and Name",
|
||||||
|
check_method: CheckingMethod::SizeName,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|
|
@ -244,8 +244,8 @@ pub fn get_string_from_list_store(tree_view: &TreeView, column_full_path: i32, c
|
||||||
|
|
||||||
let mut string_vector: Vec<String> = Vec::new();
|
let mut string_vector: Vec<String> = Vec::new();
|
||||||
|
|
||||||
let Some(tree_iter) = list_store.iter_first() else {
|
let Some(tree_iter) = list_store.iter_first() else {
|
||||||
return string_vector;
|
return string_vector;
|
||||||
};
|
};
|
||||||
match column_selection {
|
match column_selection {
|
||||||
Some(column_selection) => loop {
|
Some(column_selection) => loop {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::help_functions::{
|
use crate::help_functions::{
|
||||||
ColumnsBadExtensions, ColumnsBigFiles, ColumnsBrokenFiles, ColumnsDuplicates, ColumnsEmptyFiles, ColumnsEmptyFolders, ColumnsInvalidSymlinks, ColumnsSameMusic,
|
BottomButtonsEnum, ColumnsBadExtensions, ColumnsBigFiles, ColumnsBrokenFiles, ColumnsDuplicates, ColumnsEmptyFiles, ColumnsEmptyFolders, ColumnsInvalidSymlinks,
|
||||||
ColumnsSimilarImages, ColumnsSimilarVideos, ColumnsTemporaryFiles, PopoverTypes,
|
ColumnsSameMusic, ColumnsSimilarImages, ColumnsSimilarVideos, ColumnsTemporaryFiles, PopoverTypes,
|
||||||
};
|
};
|
||||||
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
|
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub struct NotebookObject {
|
||||||
pub column_size_as_bytes: Option<i32>,
|
pub column_size_as_bytes: Option<i32>,
|
||||||
pub column_modification_as_secs: Option<i32>,
|
pub column_modification_as_secs: Option<i32>,
|
||||||
pub columns_types: &'static [glib::types::Type],
|
pub columns_types: &'static [glib::types::Type],
|
||||||
|
pub bottom_buttons: &'static [BottomButtonsEnum],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
|
@ -52,6 +53,15 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::BOOL, // IsHeader
|
glib::types::Type::BOOL, // IsHeader
|
||||||
glib::types::Type::STRING, // TextColor
|
glib::types::Type::STRING, // TextColor
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[
|
||||||
|
BottomButtonsEnum::Save,
|
||||||
|
BottomButtonsEnum::Delete,
|
||||||
|
BottomButtonsEnum::Select,
|
||||||
|
BottomButtonsEnum::Sort,
|
||||||
|
BottomButtonsEnum::Symlink,
|
||||||
|
BottomButtonsEnum::Hardlink,
|
||||||
|
BottomButtonsEnum::Move,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::EmptyDirectories,
|
notebook_type: NotebookMainEnum::EmptyDirectories,
|
||||||
|
@ -72,6 +82,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::BigFiles,
|
notebook_type: NotebookMainEnum::BigFiles,
|
||||||
|
@ -94,6 +105,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::U64, // SizeAsBytes
|
glib::types::Type::U64, // SizeAsBytes
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::EmptyFiles,
|
notebook_type: NotebookMainEnum::EmptyFiles,
|
||||||
|
@ -114,6 +126,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::Temporary,
|
notebook_type: NotebookMainEnum::Temporary,
|
||||||
|
@ -134,6 +147,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::SimilarImages,
|
notebook_type: NotebookMainEnum::SimilarImages,
|
||||||
|
@ -162,6 +176,16 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::BOOL, // IsHeader
|
glib::types::Type::BOOL, // IsHeader
|
||||||
glib::types::Type::STRING, // TextColor
|
glib::types::Type::STRING, // TextColor
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[
|
||||||
|
BottomButtonsEnum::Save,
|
||||||
|
BottomButtonsEnum::Delete,
|
||||||
|
BottomButtonsEnum::Select,
|
||||||
|
BottomButtonsEnum::Sort,
|
||||||
|
BottomButtonsEnum::Symlink,
|
||||||
|
BottomButtonsEnum::Hardlink,
|
||||||
|
BottomButtonsEnum::Move,
|
||||||
|
BottomButtonsEnum::Compare,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::SimilarVideos,
|
notebook_type: NotebookMainEnum::SimilarVideos,
|
||||||
|
@ -188,6 +212,15 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::BOOL, // IsHeader
|
glib::types::Type::BOOL, // IsHeader
|
||||||
glib::types::Type::STRING, // TextColor
|
glib::types::Type::STRING, // TextColor
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[
|
||||||
|
BottomButtonsEnum::Save,
|
||||||
|
BottomButtonsEnum::Delete,
|
||||||
|
BottomButtonsEnum::Select,
|
||||||
|
BottomButtonsEnum::Sort,
|
||||||
|
BottomButtonsEnum::Symlink,
|
||||||
|
BottomButtonsEnum::Hardlink,
|
||||||
|
BottomButtonsEnum::Move,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::SameMusic,
|
notebook_type: NotebookMainEnum::SameMusic,
|
||||||
|
@ -221,6 +254,15 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::BOOL, // IsHeader
|
glib::types::Type::BOOL, // IsHeader
|
||||||
glib::types::Type::STRING, // TextColor
|
glib::types::Type::STRING, // TextColor
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[
|
||||||
|
BottomButtonsEnum::Save,
|
||||||
|
BottomButtonsEnum::Delete,
|
||||||
|
BottomButtonsEnum::Select,
|
||||||
|
BottomButtonsEnum::Sort,
|
||||||
|
BottomButtonsEnum::Symlink,
|
||||||
|
BottomButtonsEnum::Hardlink,
|
||||||
|
BottomButtonsEnum::Move,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::Symlinks,
|
notebook_type: NotebookMainEnum::Symlinks,
|
||||||
|
@ -243,6 +285,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::BrokenFiles,
|
notebook_type: NotebookMainEnum::BrokenFiles,
|
||||||
|
@ -264,6 +307,7 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
NotebookObject {
|
NotebookObject {
|
||||||
notebook_type: NotebookMainEnum::BadExtensions,
|
notebook_type: NotebookMainEnum::BadExtensions,
|
||||||
|
@ -286,5 +330,6 @@ pub static NOTEBOOKS_INFO: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
|
||||||
glib::types::Type::STRING, // Modification
|
glib::types::Type::STRING, // Modification
|
||||||
glib::types::Type::U64, // ModificationAsSecs
|
glib::types::Type::U64, // ModificationAsSecs
|
||||||
],
|
],
|
||||||
|
bottom_buttons: &[BottomButtonsEnum::Save, BottomButtonsEnum::Delete, BottomButtonsEnum::Select, BottomButtonsEnum::Move],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -5,11 +5,11 @@ use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use czkawka_core::common::get_default_number_of_threads;
|
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::{ComboBoxText, ScrolledWindow, TextView, TreeView};
|
use gtk4::{ComboBoxText, ScrolledWindow, TextView, TreeView};
|
||||||
|
|
||||||
|
use czkawka_core::common::get_default_number_of_threads;
|
||||||
use czkawka_core::common_dir_traversal::CheckingMethod;
|
use czkawka_core::common_dir_traversal::CheckingMethod;
|
||||||
use czkawka_core::similar_images::SIMILAR_VALUES;
|
use czkawka_core::similar_images::SIMILAR_VALUES;
|
||||||
|
|
||||||
|
@ -938,7 +938,7 @@ pub fn load_configuration(
|
||||||
main_notebook.label_duplicate_hash_type.set_visible(false);
|
main_notebook.label_duplicate_hash_type.set_visible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if DUPLICATES_CHECK_METHOD_COMBO_BOX[combo_chosen_index as usize].check_method == CheckingMethod::Name {
|
if [CheckingMethod::Name, CheckingMethod::SizeName].contains(&DUPLICATES_CHECK_METHOD_COMBO_BOX[combo_chosen_index as usize].check_method) {
|
||||||
main_notebook.check_button_duplicate_case_sensitive_name.set_visible(true);
|
main_notebook.check_button_duplicate_case_sensitive_name.set_visible(true);
|
||||||
} else {
|
} else {
|
||||||
main_notebook.check_button_duplicate_case_sensitive_name.set_visible(false);
|
main_notebook.check_button_duplicate_case_sensitive_name.set_visible(false);
|
||||||
|
|
Loading…
Reference in a new issue