Add support for checking same file names (#84)

This commit is contained in:
Rafał Mikrut 2020-10-24 14:44:21 -04:00 committed by GitHub
parent d996c3c46b
commit 7112ff6b92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 451 additions and 74 deletions

View File

@ -130,7 +130,7 @@ DupeGuru after selecting files, froze at 45% for ~15 minutes, so I just kill it.
I used Mprof for checking memory usage FSlint and Dupeguru, for Czkawka I used Heaptrack.
To not get Dupeguru crash I checked smaller directory with 217986 files and 41883 folders.
| App| Idle Ram | Max Operational Ram Usage | Stabilized after search usage |
| App| Idle Ram | Max Operational Ram Usage | Stabilized after search |
|:----------:|:-------------:|:-------------:|:-------------:|
| FSlint 2.4.7 | 54 MB | 120 MB | 117 MB |
| Czkawka 1.2.2 | 8 MB | 42 MB | 41 MB |
@ -159,7 +159,7 @@ So still is a big room for improvements.
| Language | Rust| Python | Python/Objective C |
| OS | Linux, Windows, Mac(only CLI) | Linux | Linux, Windows, Mac|
| Framework | GTK 3 (Gtk-rs)| GTK 2 (PyGTK) | Qt 5 (PyQt)/Cocoa |
| Ram Usage | Low | Medium | |
| Ram Usage | Low | Medium | Very High |
| Duplicate finder | X | X | X |
| Empty files | X | X | |
| Empty folders | X | X | |

View File

@ -16,7 +16,7 @@ pub enum Commands {
minimal_file_size: u64,
#[structopt(flatten)]
allowed_extensions: AllowedExtensions,
#[structopt(short, long, default_value = "HASH", parse(try_from_str = parse_checking_method), help = "Search method (SIZE, HASH, HASHMB)", long_help = "Methods to search files.\nSIZE - The fastest method, checking by the file's size,\nHASHMB - More accurate but slower, checking by the hash of the file's first mibibyte or\nHASH - The slowest method, checking by the hash of the entire file")]
#[structopt(short, long, default_value = "HASH", parse(try_from_str = parse_checking_method), help = "Search method (NAME, SIZE, HASH, HASHMB)", long_help = "Methods to search files.\nNAME - Fast but but rarely usable,\nSIZE - Fast but not accurate, checking by the file's size,\nHASHMB - More accurate but slower, checking by the hash of the file's first mebibyte or\nHASH - The slowest method, checking by the hash of the entire file")]
search_method: CheckingMethod,
#[structopt(short = "D", long, default_value = "NONE", parse(try_from_str = parse_delete_method), help = "Delete method (AEN, AEO, ON, OO)", long_help = "Methods to delete the files.\nAEN - All files except the newest,\nAEO - All files except the oldest,\nON - Only 1 file, the newest,\nOO - Only 1 file, the oldest\nNONE - not delete files")]
delete_method: DeleteMethod,
@ -83,7 +83,7 @@ pub enum Commands {
#[structopt(flatten)]
not_recursive: NotRecursive,
},
#[structopt(name = "ima", about = "Finds similar images", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka ima -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt")]
#[structopt(name = "image", about = "Finds similar images", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka image -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt")]
SimilarImages {
#[structopt(flatten)]
directories: Directories,
@ -153,10 +153,11 @@ impl FileToSave {
fn parse_checking_method(src: &str) -> Result<CheckingMethod, &'static str> {
match src.to_ascii_lowercase().as_str() {
"name" => Ok(CheckingMethod::Name),
"size" => Ok(CheckingMethod::Size),
"hash" => Ok(CheckingMethod::Hash),
"hashmb" => Ok(CheckingMethod::HashMB),
_ => Err("Couldn't parse the search method (allowed: SIZE, HASH, HASHMB)"),
_ => Err("Couldn't parse the search method (allowed: NAME, SIZE, HASH, HASHMB)"),
}
}
@ -205,4 +206,5 @@ EXAMPLES:
{bin} empty-folders -d /home/rafal/rr /home/gateway -f results.txt
{bin} big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -x VIDEO -f results.txt
{bin} empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D"#;
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt"#;

View File

@ -19,11 +19,17 @@ const HASH_MB_LIMIT_BYTES: u64 = 1024 * 1024; // 1MB
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum CheckingMethod {
None,
Name,
Size,
Hash,
HashMB,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum HashType {
Blake3,
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum DeleteMethod {
None,
@ -51,8 +57,10 @@ pub struct Info {
pub number_of_duplicated_files_by_size: usize,
pub number_of_groups_by_hash: usize,
pub number_of_duplicated_files_by_hash: usize,
pub number_of_duplicated_files_after_pre_hash: usize,
pub number_of_groups_after_pre_hash: usize,
pub number_of_duplicated_files_after_pre_hash: usize,
pub number_of_groups_by_name: usize,
pub number_of_duplicated_files_by_name: usize,
pub lost_space_by_size: u64,
pub lost_space_after_pre_hash: u64,
pub lost_space_by_hash: u64,
@ -72,6 +80,7 @@ impl Info {
pub struct DuplicateFinder {
text_messages: Messages,
information: Info,
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_hashes: BTreeMap<u64, Vec<Vec<FileEntry>>>, // File Size, File Entry
directories: Directories,
@ -81,6 +90,7 @@ pub struct DuplicateFinder {
minimal_file_size: u64,
check_method: CheckingMethod,
delete_method: DeleteMethod,
hash_type: HashType,
stopped_search: bool,
}
@ -89,6 +99,7 @@ impl DuplicateFinder {
Self {
text_messages: Messages::new(),
information: Info::new(),
files_with_identical_names: Default::default(),
files_with_identical_size: Default::default(),
files_with_identical_hashes: Default::default(),
recursive_search: true,
@ -99,20 +110,38 @@ impl DuplicateFinder {
directories: Directories::new(),
excluded_items: ExcludedItems::new(),
stopped_search: false,
hash_type: HashType::Blake3,
}
}
pub fn find_duplicates(&mut self, rx: Option<&Receiver<()>>) {
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
if !self.check_files_size(rx) {
self.stopped_search = true;
return;
}
#[allow(clippy::collapsible_if)]
if self.check_method == CheckingMethod::Hash || self.check_method == CheckingMethod::HashMB {
if !self.check_files_hash(rx) {
self.stopped_search = true;
return;
match self.check_method {
CheckingMethod::Name => {
if !self.check_files_name(rx) {
self.stopped_search = true;
return;
}
}
CheckingMethod::Size => {
if !self.check_files_size(rx) {
self.stopped_search = true;
return;
}
}
CheckingMethod::HashMB | CheckingMethod::Hash => {
if !self.check_files_size(rx) {
self.stopped_search = true;
return;
}
if !self.check_files_hash(rx) {
self.stopped_search = true;
return;
}
}
CheckingMethod::None => {
panic!();
}
}
self.delete_files();
@ -127,6 +156,10 @@ impl DuplicateFinder {
self.stopped_search
}
pub const fn get_files_sorted_by_names(&self) -> &BTreeMap<String, Vec<FileEntry>> {
&self.files_with_identical_names
}
pub const fn get_files_sorted_by_size(&self) -> &BTreeMap<u64, Vec<FileEntry>> {
&self.files_with_identical_size
}
@ -177,10 +210,146 @@ impl DuplicateFinder {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
}
fn check_files_name(&mut self, rx: Option<&Receiver<()>>) -> bool {
// TODO maybe add multithreading checking files
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
// Add root folders for finding
for id in &self.directories.included_directories {
folders_to_check.push(id.clone());
}
self.information.number_of_checked_folders += folders_to_check.len();
while !folders_to_check.is_empty() {
if rx.is_some() && rx.unwrap().try_recv().is_ok() {
return false;
}
let current_folder = folders_to_check.pop().unwrap();
// Read current dir, if permission are denied just go to next
let read_dir = match fs::read_dir(&current_folder) {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display()));
continue;
} // Permissions denied
};
// Check every sub folder/file/link etc.
'dir: for entry in read_dir {
let entry_data = match entry {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display()));
continue 'dir;
} //Permissions denied
};
let metadata: Metadata = match entry_data.metadata() {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display()));
continue 'dir;
} //Permissions denied
};
if metadata.is_dir() {
self.information.number_of_checked_folders += 1;
if !self.recursive_search {
continue 'dir;
}
let next_folder = current_folder.join(entry_data.file_name());
if self.directories.is_excluded(&next_folder) {
continue 'dir;
}
if self.excluded_items.is_excluded(&next_folder) {
continue 'dir;
}
folders_to_check.push(next_folder);
} else if metadata.is_file() {
// let mut have_valid_extension: bool;
let file_name_lowercase: String = match entry_data.file_name().into_string() {
Ok(t) => t,
Err(_) => continue 'dir,
}
.to_lowercase();
// Checking allowed extensions
if !self.allowed_extensions.file_extensions.is_empty() {
let allowed = self.allowed_extensions.file_extensions.iter().any(|e| file_name_lowercase.ends_with((".".to_string() + e.to_lowercase().as_str()).as_str()));
if !allowed {
// Not an allowed extension, ignore it.
self.information.number_of_ignored_files += 1;
continue 'dir;
}
}
// Checking files
if metadata.len() >= self.minimal_file_size {
let current_file_name = current_folder.join(entry_data.file_name());
if self.excluded_items.is_excluded(&current_file_name) {
continue 'dir;
}
// Creating new file entry
let fe: FileEntry = FileEntry {
path: current_file_name.clone(),
size: metadata.len(),
modified_date: match metadata.modified() {
Ok(t) => match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(),
Err(_) => {
self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display()));
0
}
},
Err(_) => {
self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display()));
continue 'dir;
} // Permissions Denied
},
};
// Adding files to BTreeMap
self.files_with_identical_names.entry(entry_data.file_name().to_string_lossy().to_string()).or_insert_with(Vec::new);
self.files_with_identical_names.get_mut(&entry_data.file_name().to_string_lossy().to_string()).unwrap().push(fe);
self.information.number_of_checked_files += 1;
} else {
// Probably this is symbolic links so we are free to ignore this
self.information.number_of_ignored_files += 1;
}
} else {
// Probably this is symbolic links so we are free to ignore this
self.information.number_of_ignored_things += 1;
}
}
}
// Create new BTreeMap without single size entries(files have not duplicates)
let mut new_map: BTreeMap<String, Vec<FileEntry>> = Default::default();
self.information.number_of_duplicated_files_by_name = 0;
for (name, vector) in &self.files_with_identical_names {
if vector.len() > 1 {
self.information.number_of_duplicated_files_by_name += vector.len() - 1;
self.information.number_of_groups_by_name += 1;
new_map.insert(name.clone(), vector.clone());
}
}
self.files_with_identical_names = new_map;
Common::print_time(start_time, SystemTime::now(), "check_files_name".to_string());
true
}
/// Read file length and puts it to different boxes(each for different lengths)
/// If in box is only 1 result, then it is removed
fn check_files_size(&mut self, rx: Option<&Receiver<()>>) -> bool {
// TODO maybe add multithreading checking for file hash
// TODO maybe add multithreading checking files
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
@ -318,6 +487,10 @@ impl DuplicateFinder {
/// The slowest checking type, which must be applied after checking for size
fn check_files_hash(&mut self, rx: Option<&Receiver<()>>) -> bool {
if self.hash_type != HashType::Blake3 {
panic!(); // TODO Add more hash types
}
let start_time: SystemTime = SystemTime::now();
let mut file_handler: File;
let mut hashmap_with_hash: HashMap<String, Vec<FileEntry>>;
@ -442,7 +615,19 @@ impl DuplicateFinder {
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
if self.delete_method == DeleteMethod::None {
return;
}
match self.check_method {
CheckingMethod::Name => {
for vector in self.files_with_identical_names.values() {
let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages.warnings);
self.information.gained_space += tuple.0;
self.information.number_of_removed_files += tuple.1;
self.information.number_of_failed_to_remove_files += tuple.2;
}
}
CheckingMethod::Hash | CheckingMethod::HashMB => {
for vector_vectors in self.files_with_identical_hashes.values() {
for vector in vector_vectors.iter() {
@ -507,6 +692,10 @@ impl DebugPrint for DuplicateFinder {
"Number of duplicated files by hash(in groups) - {} ({})",
self.information.number_of_duplicated_files_by_hash, self.information.number_of_groups_by_hash
);
println!(
"Number of duplicated files by name(in groups) - {} ({})",
self.information.number_of_duplicated_files_by_name, self.information.number_of_groups_by_name
);
println!("Lost space by size - {} ({} bytes)", self.information.lost_space_by_size.file_size(options::BINARY).unwrap(), self.information.lost_space_by_size);
println!(
"Lost space after pre hash - {} ({} bytes)",
@ -568,45 +757,74 @@ impl SaveResults for DuplicateFinder {
self.text_messages.errors.push(format!("Failed to save results to file {}", file_name));
return false;
}
if !self.files_with_identical_size.is_empty() {
writeln!(file, "-------------------------------------------------Files with same size-------------------------------------------------").unwrap();
writeln!(
file,
"Found {} duplicated files which in {} groups which takes {}.",
self.information.number_of_duplicated_files_by_size,
self.information.number_of_groups_by_size,
self.information.lost_space_by_size.file_size(options::BINARY).unwrap()
)
.unwrap();
for (size, vector) in self.files_with_identical_size.iter().rev() {
write!(file, "\n---- Size {} ({}) - {} files \n", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap();
for file_entry in vector {
writeln!(file, "{}", file_entry.path.display()).unwrap();
match self.check_method {
CheckingMethod::Name => {
if !self.files_with_identical_size.is_empty() {
writeln!(file, "-------------------------------------------------Files with same names-------------------------------------------------").unwrap();
writeln!(
file,
"Found {} files in {} groups with same name(may have different content)",
self.information.number_of_duplicated_files_by_name, self.information.number_of_groups_by_name,
)
.unwrap();
for (name, vector) in self.files_with_identical_names.iter().rev() {
writeln!(file, "Name - {} - {} files ", name, vector.len()).unwrap();
for j in vector {
writeln!(file, "{}", j.path.display()).unwrap();
}
writeln!(file).unwrap();
}
} else {
write!(file, "Not found any files with same names.").unwrap();
}
}
if !self.files_with_identical_hashes.is_empty() {
writeln!(file, "-------------------------------------------------Files with same hashes-------------------------------------------------").unwrap();
writeln!(
file,
"Found {} duplicated files which in {} groups which takes {}.",
self.information.number_of_duplicated_files_by_hash,
self.information.number_of_groups_by_hash,
self.information.lost_space_by_hash.file_size(options::BINARY).unwrap()
)
.unwrap();
for (size, vectors_vector) in self.files_with_identical_hashes.iter().rev() {
for vector in vectors_vector {
writeln!(file, "\n---- Size {} ({}) - {} files", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap();
CheckingMethod::Size => {
if !self.files_with_identical_size.is_empty() {
writeln!(file, "-------------------------------------------------Files with same size-------------------------------------------------").unwrap();
writeln!(
file,
"Found {} duplicated files which in {} groups which takes {}.",
self.information.number_of_duplicated_files_by_size,
self.information.number_of_groups_by_size,
self.information.lost_space_by_size.file_size(options::BINARY).unwrap()
)
.unwrap();
for (size, vector) in self.files_with_identical_size.iter().rev() {
write!(file, "\n---- Size {} ({}) - {} files \n", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap();
for file_entry in vector {
writeln!(file, "{}", file_entry.path.display()).unwrap();
}
}
} else {
write!(file, "Not found any duplicates.").unwrap();
}
}
} else {
write!(file, "Not found any duplicates.").unwrap();
CheckingMethod::Hash | CheckingMethod::HashMB => {
if !self.files_with_identical_hashes.is_empty() {
writeln!(file, "-------------------------------------------------Files with same hashes-------------------------------------------------").unwrap();
writeln!(
file,
"Found {} duplicated files which in {} groups which takes {}.",
self.information.number_of_duplicated_files_by_hash,
self.information.number_of_groups_by_hash,
self.information.lost_space_by_hash.file_size(options::BINARY).unwrap()
)
.unwrap();
for (size, vectors_vector) in self.files_with_identical_hashes.iter().rev() {
for vector in vectors_vector {
writeln!(file, "\n---- Size {} ({}) - {} files", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap();
for file_entry in vector {
writeln!(file, "{}", file_entry.path.display()).unwrap();
}
}
}
} else {
write!(file, "Not found any duplicates.").unwrap();
}
}
CheckingMethod::None => {
panic!();
}
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
true
@ -621,6 +839,20 @@ impl PrintResults for DuplicateFinder {
let mut number_of_groups: u64 = 0;
match self.check_method {
CheckingMethod::Name => {
for i in &self.files_with_identical_names {
number_of_files += i.1.len() as u64;
number_of_groups += 1;
}
println!("Found {} files in {} groups with same name(may have different content)", number_of_files, number_of_groups,);
for (name, vector) in &self.files_with_identical_names {
println!("Name - {} - {} files ", name, vector.len());
for j in vector {
println!("{}", j.path.display());
}
println!();
}
}
CheckingMethod::Hash | CheckingMethod::HashMB => {
for (_size, vector) in self.files_with_identical_hashes.iter() {
for j in vector {

View File

@ -744,6 +744,21 @@ Author: Rafał Mikrut
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioButton" id="radio_button_name">
<property name="label" translatable="yes">Name(very fast)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">radio_button_hash</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_size">
<property name="label" translatable="yes">Size(very fast)</property>
@ -756,7 +771,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
@ -771,7 +786,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -786,7 +801,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
@ -796,6 +811,43 @@ Author: Rafał Mikrut
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hash type:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_hash_type_blake3">
<property name="label" translatable="yes">Blake3</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window_duplicate_finder">
<property name="visible">True</property>
@ -808,7 +860,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>

View File

@ -165,6 +165,7 @@ fn main() {
let check_button_recursive: gtk::CheckButton = builder.get_object("check_button_recursive").unwrap();
//// Radio Buttons
let radio_button_name: gtk::RadioButton = builder.get_object("radio_button_name").unwrap();
let radio_button_size: gtk::RadioButton = builder.get_object("radio_button_size").unwrap();
let radio_button_hashmb: gtk::RadioButton = builder.get_object("radio_button_hashmb").unwrap();
let radio_button_hash: gtk::RadioButton = builder.get_object("radio_button_hash").unwrap();
@ -499,7 +500,9 @@ fn main() {
match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
let check_method;
if radio_button_size.get_active() {
if radio_button_name.get_active() {
check_method = duplicate::CheckingMethod::Name;
} else if radio_button_size.get_active() {
check_method = duplicate::CheckingMethod::Size;
} else if radio_button_hashmb.get_active() {
check_method = duplicate::CheckingMethod::HashMB;
@ -1542,15 +1545,23 @@ fn main() {
let duplicates_group: usize;
match df.get_check_method() {
CheckingMethod::Name => {
duplicates_number = information.number_of_duplicated_files_by_name;
duplicates_size = 0;
duplicates_group = information.number_of_groups_by_name;
entry_info.set_text(format!("Found {} files in {} groups which have same names.", duplicates_number, duplicates_group).as_str());
}
CheckingMethod::Hash | CheckingMethod::HashMB => {
duplicates_number = information.number_of_duplicated_files_by_hash;
duplicates_size = information.lost_space_by_hash;
duplicates_group = information.number_of_groups_by_hash;
entry_info.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str());
}
CheckingMethod::Size => {
duplicates_number = information.number_of_duplicated_files_by_size;
duplicates_size = information.lost_space_by_size;
duplicates_group = information.number_of_groups_by_size;
entry_info.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str());
}
CheckingMethod::None => {
panic!();
@ -1577,6 +1588,33 @@ fn main() {
let col_indices = [0, 1, 2, 3, 4, 5];
match df.get_check_method() {
CheckingMethod::Name => {
let btreemap = df.get_files_sorted_by_names();
for (name, vector) in btreemap.iter().rev() {
let values: [&dyn ToValue; 6] = [
&name,
&(format!("{} results", vector.len())),
&"".to_string(), // No text in 3 column
&(0), // Not used here
&(HEADER_ROW_COLOR.to_string()),
&(TEXT_COLOR.to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
for entry in vector {
let (directory, file) = split_path(&entry.path);
let values: [&dyn ToValue; 6] = [
&file,
&directory,
&(format!("{} - ({})", NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string(), entry.size.file_size(options::BINARY).unwrap())),
&(entry.modified_date),
&(MAIN_ROW_COLOR.to_string()),
&(TEXT_COLOR.to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
}
}
CheckingMethod::Hash | CheckingMethod::HashMB => {
let btreemap = df.get_files_sorted_by_hash();
@ -1645,7 +1683,7 @@ fn main() {
{
*shared_duplication_state.borrow_mut() = df;
if duplicates_size > 0 {
if duplicates_number > 0 {
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("save").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("delete").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("select").unwrap() = true;

View File

@ -14,7 +14,19 @@
<property name="restartRequiresConfirmation" value="false" />
<property name="settings.editor.selected.configurable" value="language.rust" />
</component>
<component name="RunManager" selected="Cargo.Format Code">
<component name="RunManager" selected="Cargo.Audit">
<configuration name="Audit" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="audit" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Build All Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="build --all" />
@ -77,7 +89,7 @@
</configuration>
<configuration name="Run CLI Big Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- --b -i $USER_HOME$/ -f &quot;results.txt&quot; -p 37" />
<option name="command" value="run --bin czkawka_cli -- big -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -89,7 +101,7 @@
</configuration>
<configuration name="Run CLI Big Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- --b -i $USER_HOME$/ -f &quot;results.txt&quot; -p 37" />
<option name="command" value="run --bin czkawka_cli --release -- big -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -101,7 +113,7 @@
</configuration>
<configuration name="Run CLI Duplicates Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- --d -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli -- dup -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -113,7 +125,7 @@
</configuration>
<configuration name="Run CLI Duplicates Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- --d -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli --release -- dup -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -125,7 +137,7 @@
</configuration>
<configuration name="Run CLI Empty Files Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- --y -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli -- empty-files -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -137,7 +149,7 @@
</configuration>
<configuration name="Run CLI Empty Files Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- --y -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli --release -- empty-files -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -149,7 +161,7 @@
</configuration>
<configuration name="Run CLI Empty Folders Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- --e -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli -- empty-folders -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -161,7 +173,7 @@
</configuration>
<configuration name="Run CLI Empty Folders Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- --e -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli --release -- empty-folders -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -183,9 +195,33 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run CLI Similar Images Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- image -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run CLI Similar Images Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- image -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run CLI Temporary Files Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli -- --y -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli -- temp -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -197,7 +233,7 @@
</configuration>
<configuration name="Run CLI Temporary Files Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_cli --release -- --t -i $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="command" value="run --bin czkawka_cli --release -- temp -d $USER_HOME$/ -f &quot;results.txt&quot;" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
@ -207,7 +243,7 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run GUI GTK" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration name="Run GUI GTK Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_gui" />
<option name="allFeatures" value="false" />
@ -219,7 +255,19 @@
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run GUI Orbtk" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration name="Run GUI GTK Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_gui --release" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Run GUI Orbtk Release" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --bin czkawka_gui_orbtk --release" />
<option name="allFeatures" value="false" />
@ -268,30 +316,35 @@
</method>
</configuration>
<list>
<item itemvalue="Cargo.Audit" />
<item itemvalue="Cargo.Build All Debug" />
<item itemvalue="Cargo.Build All Release" />
<item itemvalue="Cargo.Clippy" />
<item itemvalue="Cargo.Doc" />
<item itemvalue="Cargo.Format Code" />
<item itemvalue="Cargo.Run CLI Duplicates Debug" />
<item itemvalue="Cargo.Run CLI Duplicates Release" />
<item itemvalue="Cargo.Run CLI Empty Folders Debug" />
<item itemvalue="Cargo.Run CLI Empty Folders Release" />
<item itemvalue="Cargo.Run CLI Big Debug" />
<item itemvalue="Cargo.Run CLI Big Release" />
<item itemvalue="Cargo.Run CLI Duplicates Debug" />
<item itemvalue="Cargo.Run CLI Duplicates Release" />
<item itemvalue="Cargo.Run CLI Empty Files Debug" />
<item itemvalue="Cargo.Run CLI Empty Files Release" />
<item itemvalue="Cargo.Run CLI Empty Folders Debug" />
<item itemvalue="Cargo.Run CLI Empty Folders Release" />
<item itemvalue="Cargo.Run CLI Info" />
<item itemvalue="Cargo.Run CLI Similar Images Debug" />
<item itemvalue="Cargo.Run CLI Similar Images Release" />
<item itemvalue="Cargo.Run CLI Temporary Files Debug" />
<item itemvalue="Cargo.Run CLI Temporary Files Release" />
<item itemvalue="Cargo.Run CLI Info" />
<item itemvalue="Cargo.Run GUI GTK" />
<item itemvalue="Cargo.Run GUI GTK Debug" />
<item itemvalue="Cargo.Run GUI GTK Release" />
<item itemvalue="Cargo.Run GUI Orbtk Release" />
<item itemvalue="Cargo.Run GUI Version" />
<item itemvalue="Cargo.Run GUI Orbtk" />
<item itemvalue="Cargo.Test" />
<item itemvalue="Cargo.Update" />
</list>
</component>
<component name="RustProjectSettings">
<option name="runRustfmtOnSave" value="true" />
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
<option name="version" value="2" />
</component>