1
0
Fork 0
mirror of synced 2024-05-03 03:52:58 +12:00

Add reference folders (#516)

* Add reference folder

* Basic tree view cleaning

* Computations

* Similar Images

* Muzyka i Wideo

* Podwójne dwa razy, cztery razy!

* Rozmiar i Nazwa

* Spanko i działanko(przynajmniej zdaje mi się że działa)

* Translatanko
This commit is contained in:
Rafał Mikrut 2021-12-24 09:18:55 +01:00 committed by GitHub
parent 5db5d17afb
commit c4f26883b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1623 additions and 594 deletions

View file

@ -11,7 +11,7 @@
- Option to not remove cache from non existent files(e.g. from unplugged pendrive) - [#472](https://github.com/qarmin/czkawka/pull/472)
- Add multiple tooltips with helpful messages - [#472](https://github.com/qarmin/czkawka/pull/472)
- Allow to cache prehash - [#477](https://github.com/qarmin/czkawka/pull/477)
- Improve custom selecting of records(allows to use Rust red regex) - [#489](https://github.com/qarmin/czkawka/pull/478)
- Improve custom selecting of records(allows to use Rust regex) - [#489](https://github.com/qarmin/czkawka/pull/478)
- Remove support for finding zeroed files - [#461](https://github.com/qarmin/czkawka/pull/461)
- Remove HashMB mode - [#476](https://github.com/qarmin/czkawka/pull/476)
- Approximate comparison of music - [#483](https://github.com/qarmin/czkawka/pull/483)

View file

@ -11,7 +11,7 @@
- CLI frontend - for easy automation
- GUI frontend - uses modern GTK 3 and looks similar to FSlint
- No spying - Czkawka does not have access to the Internet, nor does it collect any user information or statistics
- Multilingual - app support multiple languages
- Multilingual - support multiple languages like Polish, English or Italian
- Multiple tools to use:
- Duplicates - Finds duplicates based on file name, size or hash
- Empty Folders - Finds empty folders with the help of an advanced algorithm
@ -50,12 +50,12 @@ I set the minimal file size to check to 1KB on all programs.
| App | Executing Time |
|:---------------------------:|:--------------:|
| FSlint 2.4.7 (First Run) | 86s |
| FSlint 2.4.7 (Second Run) | 43s |
| Czkawka 3.0.0 (First Run) | 8s |
| Czkawka 3.0.0 (Second Run) | 7s |
| DupeGuru 4.1.1 (First Run) | 22s |
| DupeGuru 4.1.1 (Second Run) | 21s |
| FSlint 2.4.7 (First Run) | 86s |
| FSlint 2.4.7 (Second Run) | 43s |
| Czkawka 3.0.0 (First Run) | 8s |
| Czkawka 3.0.0 (Second Run) | 7s |
| DupeGuru 4.1.1 (First Run) | 22s |
| DupeGuru 4.1.1 (Second Run) | 21s |
I used Mprof for checking memory usage of FSlint and DupeGuru, and Heaptrack for Czkawka.
@ -91,31 +91,31 @@ Similar images which check 349 image files that occupied 1.7 GB
Bleachbit is a master at finding and removing temporary files, while Czkawka only finds the most basic ones. So these two apps shouldn't be compared directly or be considered as an alternative to one another.
| | Czkawka | FSlint | DupeGuru | Bleachbit |
| | Czkawka | FSlint | DupeGuru | Bleachbit |
|:----------------------:|:-----------:|:----------:|:-----------------:|:-----------:|
| Language | Rust | Python | Python/Obj-C | Python |
| OS | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win |
| Framework | GTK 3 | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 |
| Duplicate finder | • | | | |
| Empty files | • | | | |
| Empty folders | • | | | |
| Temporary files | • | | | |
| Big files | • | | | |
| Similar images | • | | | |
| Similar videos | • | | | |
| Music duplicates(tags) | • | | | |
| Invalid symlinks | • | | | |
| Broken files | • | | | |
| Names conflict | • | | | |
| Installed packages | | | | |
| Invalid names | | | | |
| Bad ID | | | | |
| Non stripped binaries | | | | |
| Redundant whitespace | | | | |
| Overwriting files | | | | |
| Multiple languages(po) | • | • | • | • |
| Cache support | • | | | |
| In active development | Yes | No | Yes | Yes |
| Language | Rust | Python | Python/Obj-C | Python |
| OS | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win |
| Framework | GTK 3 | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 |
| Duplicate finder | • | | | |
| Empty files | • | | | |
| Empty folders | • | | | |
| Temporary files | • | | | |
| Big files | • | | | |
| Similar images | • | | | |
| Similar videos | • | | | |
| Music duplicates(tags) | • | | | |
| Invalid symlinks | • | | | |
| Broken files | • | | | |
| Names conflict | • | | | |
| Installed packages | | | | |
| Invalid names | | | | |
| Bad ID | | | | |
| Non stripped binaries | | | | |
| Redundant whitespace | | | | |
| Overwriting files | | | | |
| Multiple languages | • | • | • | • |
| Cache support | • | | | |
| In active development | Yes | No | Yes | Yes |
## Other apps
There are many similar applications to Czkawka on the Internet, which do some things better and some things worse.

View file

@ -44,7 +44,6 @@ pub enum DeleteMethod {
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub taken_space: u64,
pub number_of_real_files: usize,
}
@ -302,7 +301,6 @@ impl BigFile {
if self.information.number_of_real_files < self.number_of_files_to_check {
new_map.entry(*size).or_insert_with(Vec::new);
new_map.get_mut(size).unwrap().push(file.clone());
self.information.taken_space += size;
self.information.number_of_real_files += 1;
} else {
break;
@ -445,11 +443,6 @@ impl SaveResults for BigFile {
impl PrintResults for BigFile {
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!(
"Found {} files which take {}:",
self.information.number_of_real_files,
self.information.taken_space.file_size(options::BINARY).unwrap()
);
for (size, vector) in self.big_files.iter().rev() {
// TODO Align all to same width
for entry in vector {

View file

@ -60,8 +60,6 @@ pub enum TypeOfFile {
#[derive(Default)]
pub struct Info {
pub number_of_broken_files: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
@ -550,8 +548,6 @@ impl DebugPrint for BrokenFiles {
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");

View file

@ -8,6 +8,7 @@ use crate::common_messages::Messages;
pub struct Directories {
pub excluded_directories: Vec<PathBuf>,
pub included_directories: Vec<PathBuf>,
pub reference_directories: Vec<PathBuf>,
}
impl Directories {
@ -15,6 +16,10 @@ impl Directories {
Default::default()
}
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
self.reference_directories = reference_directory
}
/// Setting included directories, at least one must be provided
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>, text_messages: &mut Messages) -> bool {
let start_time: SystemTime = SystemTime::now();
@ -146,15 +151,18 @@ impl Directories {
if cfg!(target_family = "windows") {
self.included_directories = self.included_directories.iter().map(Common::normalize_windows_path).collect();
self.excluded_directories = self.excluded_directories.iter().map(Common::normalize_windows_path).collect();
self.reference_directories = self.reference_directories.iter().map(Common::normalize_windows_path).collect();
}
// Remove duplicated entries like: "/", "/"
self.excluded_directories.sort();
self.included_directories.sort();
self.reference_directories.sort();
self.excluded_directories.dedup();
self.included_directories.dedup();
self.reference_directories.dedup();
// Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/"
if recursive_search {
@ -251,6 +259,20 @@ impl Directories {
self.excluded_directories = optimized_excluded;
// Selecting Reference folders
{
let mut ref_folders = Vec::new();
for folder in &self.reference_directories {
if self.included_directories.iter().any(|e| folder.starts_with(&e)) {
ref_folders.push(folder.clone());
// println!("REF: VALID reference folder {:?}", folder);
} else {
// println!("REF: Invalid reference folder {:?}", folder);
}
}
self.reference_directories = ref_folders;
}
if self.included_directories.is_empty() {
text_messages
.errors

View file

@ -18,7 +18,8 @@ impl Messages {
if !self.messages.is_empty() {
text_to_return += "-------------------------------MESSAGES--------------------------------\n";
for i in &self.messages {
text_to_return += format!("{}\n", i).as_str();
text_to_return += i;
text_to_return += "\n";
}
text_to_return += "---------------------------END OF MESSAGES-----------------------------\n";
}
@ -27,7 +28,8 @@ impl Messages {
text_to_return += "-------------------------------WARNINGS--------------------------------\n";
for i in &self.warnings {
text_to_return += format!("{}\n", i).as_str();
text_to_return += i;
text_to_return += "\n";
}
text_to_return += "---------------------------END OF WARNINGS-----------------------------\n";
}
@ -36,7 +38,8 @@ impl Messages {
text_to_return += "--------------------------------ERRORS---------------------------------\n";
for i in &self.errors {
text_to_return += format!("{}\n", i).as_str();
text_to_return += i;
text_to_return += "\n";
}
text_to_return += "----------------------------END OF ERRORS------------------------------\n";
}

View file

@ -92,7 +92,6 @@ pub struct Info {
pub number_of_duplicated_files_by_name: usize,
pub lost_space_by_size: u64,
pub lost_space_by_hash: u64,
pub gained_space: u64,
}
impl Info {
@ -105,9 +104,12 @@ 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
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, next grouped by file size, next grouped by hash
files_with_identical_names_referenced: BTreeMap<String, (FileEntry, Vec<FileEntry>)>, // File Size, 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
directories: Directories,
allowed_extensions: Extensions,
excluded_items: ExcludedItems,
@ -125,6 +127,7 @@ pub struct DuplicateFinder {
minimal_cache_file_size: u64,
minimal_prehash_cache_file_size: u64,
delete_outdated_cache: bool,
use_reference_folders: bool,
}
impl DuplicateFinder {
@ -135,6 +138,9 @@ impl DuplicateFinder {
files_with_identical_names: Default::default(),
files_with_identical_size: Default::default(),
files_with_identical_hashes: Default::default(),
files_with_identical_names_referenced: Default::default(),
files_with_identical_size_referenced: Default::default(),
files_with_identical_hashes_referenced: Default::default(),
recursive_search: true,
allowed_extensions: Extensions::new(),
check_method: CheckingMethod::None,
@ -152,11 +158,13 @@ impl DuplicateFinder {
minimal_cache_file_size: 1024 * 1024 / 4, // By default cache only >= 256 KB files
minimal_prehash_cache_file_size: 0,
delete_outdated_cache: true,
use_reference_folders: false,
}
}
pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
self.use_reference_folders = !self.directories.reference_directories.is_empty();
match self.check_method {
CheckingMethod::Name => {
@ -270,23 +278,43 @@ impl DuplicateFinder {
};
}
pub fn get_use_reference(&self) -> bool {
self.use_reference_folders
}
pub fn set_recursive_search(&mut self, recursive_search: bool) {
self.recursive_search = recursive_search;
}
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) -> bool {
self.directories.set_included_directory(included_directory, &mut self.text_messages)
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) {
self.directories.set_included_directory(included_directory, &mut self.text_messages);
}
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
self.directories.set_reference_directory(reference_directory);
}
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
}
pub fn set_excluded_items(&mut self, excluded_items: Vec<String>) {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
}
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
}
pub fn set_excluded_items(&mut self, excluded_items: Vec<String>) {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
pub fn get_files_with_identical_hashes_referenced(&self) -> &BTreeMap<u64, Vec<(FileEntry, Vec<FileEntry>)>> {
&self.files_with_identical_hashes_referenced
}
pub fn get_files_with_identical_name_referenced(&self) -> &BTreeMap<String, (FileEntry, Vec<FileEntry>)> {
&self.files_with_identical_names_referenced
}
pub fn get_files_with_identical_size_referenced(&self) -> &BTreeMap<u64, (FileEntry, Vec<FileEntry>)> {
&self.files_with_identical_size_referenced
}
fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
@ -473,13 +501,53 @@ impl DuplicateFinder {
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;
// 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_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_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe));
}
}
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".to_string());
true
}
@ -684,10 +752,51 @@ impl DuplicateFinder {
let vector = if self.ignore_hard_links { filter_hard_links(&vec) } else { vec };
if vector.len() > 1 {
self.files_with_identical_size.insert(size, vector);
}
}
// Reference - only use in size, because later hash will be counted differently
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);
}
}
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;
self.files_with_identical_size.insert(size, vector);
}
}
@ -1036,9 +1145,57 @@ impl DuplicateFinder {
}
}
}
}
/////////////////////////
///////////////////////////////////////////////////////////////////////////// HASHING END
// 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_hashes, &mut btree_map);
let reference_directories = self.directories.reference_directories.clone();
let vec = btree_map
.into_iter()
.filter_map(|(_size, vec_vec_file_entry)| {
let mut all_results_with_same_size = Vec::new();
for vec_file_entry in vec_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() {
continue;
} else {
all_results_with_same_size.push((files_from_referenced_folders.pop().unwrap(), normal_files))
}
}
if all_results_with_same_size.is_empty() {
None
} else {
Some(all_results_with_same_size)
}
})
.collect::<Vec<Vec<(FileEntry, Vec<FileEntry>)>>>();
for vec_of_vec in vec {
self.files_with_identical_hashes_referenced.insert(vec_of_vec[0].0.size, vec_of_vec);
}
}
if self.use_reference_folders {
for (size, vector_vectors) in &self.files_with_identical_hashes_referenced {
for (_fe, vector) in vector_vectors {
self.information.number_of_duplicated_files_by_hash += vector.len();
self.information.number_of_groups_by_hash += 1;
self.information.lost_space_by_hash += (vector.len() as u64) * size;
}
}
} else {
for (size, vector_vectors) in &self.files_with_identical_hashes {
for vector in vector_vectors {
self.information.number_of_duplicated_files_by_hash += vector.len() - 1;
@ -1048,8 +1205,6 @@ impl DuplicateFinder {
}
}
///////////////////////////////////////////////////////////////////////////// HASHING END
Common::print_time(start_time, SystemTime::now(), "check_files_hash - full hash".to_string());
// Clean unused data
@ -1069,22 +1224,19 @@ impl DuplicateFinder {
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, self.dryrun);
self.information.gained_space += tuple.0;
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
}
}
CheckingMethod::Hash => {
for vector_vectors in self.files_with_identical_hashes.values() {
for vector in vector_vectors.iter() {
let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
self.information.gained_space += tuple.0;
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
}
}
}
CheckingMethod::Size => {
for vector in self.files_with_identical_size.values() {
let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
self.information.gained_space += tuple.0;
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
}
}
CheckingMethod::None => {
@ -1140,11 +1292,6 @@ impl DebugPrint for DuplicateFinder {
self.information.lost_space_by_hash.file_size(options::BINARY).unwrap(),
self.information.lost_space_by_hash
);
println!(
"Gained space by removing duplicated entries - {} ({} bytes)",
self.information.gained_space.file_size(options::BINARY).unwrap(),
self.information.gained_space
);
println!("### Other");

View file

@ -43,8 +43,6 @@ pub struct FileEntry {
#[derive(Default)]
pub struct Info {
pub number_of_empty_files: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
@ -350,8 +348,6 @@ impl DebugPrint for EmptyFiles {
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");

View file

@ -53,8 +53,6 @@ pub struct FileEntry {
#[derive(Default)]
pub struct Info {
pub number_of_invalid_symlinks: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
@ -400,8 +398,6 @@ impl DebugPrint for InvalidSymlinks {
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");

View file

@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread::sleep;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{fs, thread};
use std::{fs, mem, thread};
use audiotags::Tag;
use crossbeam_channel::Receiver;
@ -71,10 +71,8 @@ pub struct FileEntry {
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub number_of_music_entries: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
pub number_of_duplicates_music_files: usize,
pub number_of_duplicates: usize,
pub number_of_groups: u64,
}
impl Info {
@ -90,6 +88,7 @@ pub struct SameMusic {
music_to_check: Vec<FileEntry>,
music_entries: Vec<FileEntry>,
duplicated_music_entries: Vec<Vec<FileEntry>>,
duplicated_music_entries_referenced: Vec<(FileEntry, Vec<FileEntry>)>,
directories: Directories,
allowed_extensions: Extensions,
excluded_items: ExcludedItems,
@ -100,6 +99,7 @@ pub struct SameMusic {
music_similarity: MusicSimilarity,
stopped_search: bool,
approximate_comparison: bool,
use_reference_folders: bool,
}
impl SameMusic {
@ -120,11 +120,14 @@ impl SameMusic {
duplicated_music_entries: vec![],
music_to_check: Vec::with_capacity(2048),
approximate_comparison: true,
use_reference_folders: false,
duplicated_music_entries_referenced: vec![],
}
}
pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
self.use_reference_folders = !self.directories.reference_directories.is_empty();
if !self.check_files(stop_receiver, progress_sender) {
self.stopped_search = true;
return;
@ -172,8 +175,13 @@ impl SameMusic {
self.recursive_search = recursive_search;
}
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) -> bool {
self.directories.set_included_directory(included_directory, &mut self.text_messages)
/// Set included dir which needs to be relative, exists etc.
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>) {
self.directories.set_included_directory(included_directory, &mut self.text_messages);
}
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
self.directories.set_reference_directory(reference_directory);
}
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
@ -183,6 +191,7 @@ impl SameMusic {
pub fn set_excluded_items(&mut self, excluded_items: Vec<String>) {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
}
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
}
@ -198,6 +207,22 @@ impl SameMusic {
};
}
pub fn get_similar_music_referenced(&self) -> &Vec<(FileEntry, Vec<FileEntry>)> {
&self.duplicated_music_entries_referenced
}
pub fn get_number_of_base_duplicated_files(&self) -> usize {
if self.use_reference_folders {
self.duplicated_music_entries_referenced.len()
} else {
self.duplicated_music_entries.len()
}
}
pub fn get_use_reference(&self) -> bool {
self.use_reference_folders
}
/// Check files for any with size == 0
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
@ -382,7 +407,6 @@ impl SameMusic {
// End thread which send info to gui
progress_thread_run.store(false, Ordering::Relaxed);
progress_thread_handle.join().unwrap();
self.information.number_of_music_entries = self.music_entries.len();
Common::print_time(start_time, SystemTime::now(), "check_files".to_string());
true
@ -669,15 +693,50 @@ impl SameMusic {
// new_duplicates = Vec::new();
}
self.duplicated_music_entries = old_duplicates;
for vec in &self.duplicated_music_entries {
self.information.number_of_duplicates_music_files += vec.len() - 1;
}
// End thread which send info to gui
progress_thread_run.store(false, Ordering::Relaxed);
progress_thread_handle.join().unwrap();
self.duplicated_music_entries = old_duplicates;
if self.use_reference_folders {
let mut similars_vector = Default::default();
mem::swap(&mut self.duplicated_music_entries, &mut similars_vector);
let reference_directories = self.directories.reference_directories.clone();
self.duplicated_music_entries_referenced = similars_vector
.into_iter()
.filter_map(|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>)>>();
}
if self.use_reference_folders {
for (_fe, vector) in &self.duplicated_music_entries_referenced {
self.information.number_of_duplicates += vector.len();
self.information.number_of_groups += 1;
}
} else {
for vector in &self.duplicated_music_entries {
self.information.number_of_duplicates += vector.len() - 1;
self.information.number_of_groups += 1;
}
}
Common::print_time(start_time, SystemTime::now(), "check_for_duplicates".to_string());
// Clear unused data
@ -735,9 +794,6 @@ impl DebugPrint for SameMusic {
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("Number of duplicated music files - {}", self.information.number_of_duplicates_music_files);
println!("### Other");
@ -780,7 +836,7 @@ impl SaveResults for SameMusic {
}
if !self.music_entries.is_empty() {
writeln!(writer, "Found {} same music files.", self.information.number_of_music_entries).unwrap();
writeln!(writer, "Found {} same music files.", self.information.number_of_duplicates).unwrap();
for file_entry in self.music_entries.iter() {
writeln!(writer, "{}", file_entry.path.display()).unwrap();
}

View file

@ -93,6 +93,7 @@ pub struct SimilarImages {
excluded_items: ExcludedItems,
bktree: BKTree<Vec<u8>, Hamming>,
similar_vectors: Vec<Vec<FileEntry>>,
similar_referenced_vectors: Vec<(FileEntry, Vec<FileEntry>)>,
recursive_search: bool,
minimal_file_size: u64,
maximal_file_size: u64,
@ -106,14 +107,14 @@ pub struct SimilarImages {
use_cache: bool,
delete_outdated_cache: bool,
exclude_images_with_same_size: bool,
use_reference_folders: bool,
}
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
pub gained_space: u64,
pub number_of_duplicates: usize,
pub number_of_groups: u64,
}
impl Info {
@ -134,6 +135,7 @@ impl SimilarImages {
allowed_extensions: Extensions::new(),
bktree: BKTree::new(Hamming),
similar_vectors: vec![],
similar_referenced_vectors: Default::default(),
recursive_search: true,
minimal_file_size: 1024 * 16, // 16 KB should be enough to exclude too small images from search
maximal_file_size: u64::MAX,
@ -147,6 +149,7 @@ impl SimilarImages {
use_cache: true,
delete_outdated_cache: true,
exclude_images_with_same_size: false,
use_reference_folders: false,
}
}
@ -187,6 +190,14 @@ impl SimilarImages {
&self.similar_vectors
}
pub fn get_similar_images_referenced(&self) -> &Vec<(FileEntry, Vec<FileEntry>)> {
&self.similar_referenced_vectors
}
pub fn get_use_reference(&self) -> bool {
self.use_reference_folders
}
pub const fn get_information(&self) -> &Info {
&self.information
}
@ -221,6 +232,7 @@ impl SimilarImages {
/// Public function used by CLI to search for empty folders
pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
self.directories.optimize_directories(true, &mut self.text_messages);
self.use_reference_folders = !self.directories.reference_directories.is_empty();
if !self.check_for_similar_images(stop_receiver, progress_sender) {
self.stopped_search = true;
return;
@ -695,8 +707,46 @@ impl SimilarImages {
}
}
if self.use_reference_folders {
let mut similars_vector = Default::default();
mem::swap(&mut self.similar_vectors, &mut similars_vector);
let reference_directories = self.directories.reference_directories.clone();
self.similar_referenced_vectors = similars_vector
.into_iter()
.filter_map(|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>)>>();
}
Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - selecting data from BtreeMap".to_string());
if self.use_reference_folders {
for (_fe, vector) in &self.similar_referenced_vectors {
self.information.number_of_duplicates += vector.len();
self.information.number_of_groups += 1;
}
} else {
for vector in &self.similar_vectors {
self.information.number_of_duplicates += vector.len() - 1;
self.information.number_of_groups += 1;
}
}
// Clean unused data
self.image_hashes = Default::default();
self.images_to_check = Default::default();
@ -710,6 +760,10 @@ impl SimilarImages {
self.directories.set_included_directory(included_directory, &mut self.text_messages);
}
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
self.directories.set_reference_directory(reference_directory);
}
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
}

View file

@ -68,6 +68,7 @@ pub struct SimilarVideos {
excluded_items: ExcludedItems,
allowed_extensions: Extensions,
similar_vectors: Vec<Vec<FileEntry>>,
similar_referenced_vectors: Vec<(FileEntry, Vec<FileEntry>)>,
recursive_search: bool,
minimal_file_size: u64,
maximal_file_size: u64,
@ -78,14 +79,14 @@ pub struct SimilarVideos {
tolerance: i32,
delete_outdated_cache: bool,
exclude_videos_with_same_size: bool,
use_reference_folders: bool,
}
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
pub gained_space: u64,
pub number_of_duplicates: usize,
pub number_of_groups: u64,
}
impl Info {
@ -115,6 +116,8 @@ impl SimilarVideos {
tolerance: 10,
delete_outdated_cache: false,
exclude_videos_with_same_size: false,
use_reference_folders: false,
similar_referenced_vectors: vec![],
}
}
@ -171,6 +174,21 @@ impl SimilarVideos {
t => t,
};
}
pub fn get_similar_videos_referenced(&self) -> &Vec<(FileEntry, Vec<FileEntry>)> {
&self.similar_referenced_vectors
}
pub fn get_number_of_base_duplicated_files(&self) -> usize {
if self.use_reference_folders {
self.similar_referenced_vectors.len()
} else {
self.similar_vectors.len()
}
}
pub fn get_use_reference(&self) -> bool {
self.use_reference_folders
}
/// Public function used by CLI to search for empty folders
pub fn find_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender<ProgressData>>) {
@ -178,6 +196,7 @@ impl SimilarVideos {
self.text_messages.errors.push("Cannot find proper installation of FFmpeg.".to_string());
} else {
self.directories.optimize_directories(true, &mut self.text_messages);
self.use_reference_folders = !self.directories.reference_directories.is_empty();
if !self.check_for_similar_videos(stop_receiver, progress_sender) {
self.stopped_search = true;
return;
@ -539,6 +558,44 @@ impl SimilarVideos {
self.similar_vectors = collected_similar_videos;
if self.use_reference_folders {
let mut similars_vector = Default::default();
mem::swap(&mut self.similar_vectors, &mut similars_vector);
let reference_directories = self.directories.reference_directories.clone();
self.similar_referenced_vectors = similars_vector
.into_iter()
.filter_map(|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>)>>();
}
if self.use_reference_folders {
for (_fe, vector) in &self.similar_referenced_vectors {
self.information.number_of_duplicates += vector.len();
self.information.number_of_groups += 1;
}
} else {
for vector in &self.similar_vectors {
self.information.number_of_duplicates += vector.len() - 1;
self.information.number_of_groups += 1;
}
}
Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - selecting data from BtreeMap".to_string());
// Clean unused data
@ -553,6 +610,10 @@ impl SimilarVideos {
self.directories.set_included_directory(included_directory, &mut self.text_messages);
}
pub fn set_reference_directory(&mut self, reference_directory: Vec<PathBuf>) {
self.directories.set_reference_directory(reference_directory);
}
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>) {
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
}

View file

@ -42,8 +42,6 @@ pub struct FileEntry {
#[derive(Default)]
pub struct Info {
pub number_of_temporary_files: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
@ -357,8 +355,6 @@ impl DebugPrint for Temporary {
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");

File diff suppressed because it is too large Load diff

View file

@ -63,6 +63,7 @@ pub async fn delete_things(gui_data: GuiData) {
tree_view,
column_color,
nb_object.column_selection,
nb_object.column_path,
&window_main,
&check_button_settings_confirm_group_deletion,
)
@ -201,6 +202,7 @@ pub async fn check_if_deleting_all_files_in_group(
tree_view: &gtk::TreeView,
column_color: i32,
column_selection: i32,
column_path: i32,
window_main: &gtk::Window,
check_button_settings_confirm_group_deletion: &gtk::CheckButton,
) -> bool {
@ -211,6 +213,11 @@ pub async fn check_if_deleting_all_files_in_group(
if let Some(iter) = model.iter_first() {
assert_eq!(model.value(&iter, column_color).get::<String>().unwrap(), HEADER_ROW_COLOR); // First element should be header
// It is safe to remove any number of files in reference mode
if !model.value(&iter, column_path).get::<String>().unwrap().is_empty() {
return false;
}
loop {
if !model.iter_next(&iter) {
break;
@ -523,7 +530,7 @@ pub fn tree_remove(
}
}
clean_invalid_headers(&model, column_color);
clean_invalid_headers(&model, column_color, column_path);
text_view_errors.buffer().unwrap().set_text(messages.as_str());
}

View file

@ -253,7 +253,7 @@ pub fn hardlink_symlink(
model.remove(&model.iter(tree_path).unwrap());
}
clean_invalid_headers(&model, column_color);
clean_invalid_headers(&model, column_color, column_path);
}
fn create_dialog_non_group(window_main: &gtk::Window) -> Dialog {

View file

@ -175,7 +175,7 @@ fn move_with_tree(
move_files_common(&selected_rows, &model, column_file_name, column_path, &destination_folder, entry_info, text_view_errors);
clean_invalid_headers(&model, column_color);
clean_invalid_headers(&model, column_color, column_path);
}
fn move_with_list(

View file

@ -72,6 +72,7 @@ pub fn connect_button_search(
let entry_settings_prehash_cache_file_minimal_size = gui_data.settings.entry_settings_prehash_cache_file_minimal_size.clone();
let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone();
let image_preview_similar_images = gui_data.main_notebook.image_preview_similar_images.clone();
let image_preview_duplicates = gui_data.main_notebook.image_preview_duplicates.clone();
let label_stage = gui_data.progress_window.label_stage.clone();
let notebook_main = gui_data.main_notebook.notebook_main.clone();
let notebook_upper = gui_data.upper_notebook.notebook_upper.clone();
@ -102,8 +103,13 @@ pub fn connect_button_search(
let check_button_music_approximate_comparison = gui_data.main_notebook.check_button_music_approximate_comparison.clone();
buttons_search_clone.connect_clicked(move |_| {
let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories));
let excluded_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_excluded_directories));
let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories, ColumnsIncludedDirectory::Path as i32, None));
let excluded_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_excluded_directories, ColumnsExcludedDirectory::Path as i32, None));
let reference_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(
&tree_view_included_directories,
ColumnsIncludedDirectory::Path as i32,
Some(ColumnsIncludedDirectory::ReferenceButton as i32),
));
let recursive_search = check_button_recursive.is_active();
let excluded_items = entry_excluded_items.text().as_str().to_string().split(',').map(|e| e.to_string()).collect::<Vec<String>>();
let allowed_extensions = entry_allowed_extensions.text().as_str().to_string();
@ -111,6 +117,9 @@ pub fn connect_button_search(
let use_cache = check_button_settings_use_cache.is_active();
let minimal_cache_file_size = entry_settings_cache_file_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 / 4);
let minimal_file_size = entry_general_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 8);
let maximal_file_size = entry_general_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
let show_dialog = Arc::new(AtomicBool::new(true));
hide_all_buttons(&buttons_array);
@ -133,6 +142,8 @@ pub fn connect_button_search(
match to_notebook_main_enum(notebook_main.current_page().unwrap()) {
NotebookMainEnum::Duplicate => {
image_preview_duplicates.hide();
label_stage.show();
grid_progress_stages.show_all();
window_progress.resize(1, 1);
@ -145,9 +156,6 @@ pub fn connect_button_search(
let hash_type_index = combo_box_duplicate_hash_type.active().unwrap() as usize;
let hash_type = DUPLICATES_HASH_TYPE_COMBO_BOX[hash_type_index].hash_type;
let minimal_file_size = entry_general_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 8);
let maximal_file_size = entry_general_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
let use_prehash_cache = check_button_duplicates_use_prehash_cache.is_active();
let minimal_prehash_cache_file_size = entry_settings_prehash_cache_file_minimal_size.text().as_str().parse::<u64>().unwrap_or(0);
@ -159,6 +167,7 @@ pub fn connect_button_search(
let mut df = DuplicateFinder::new();
df.set_included_directory(included_directories);
df.set_excluded_directory(excluded_directories);
df.set_reference_directory(reference_directories);
df.set_recursive_search(recursive_search);
df.set_excluded_items(excluded_items);
df.set_allowed_extensions(allowed_extensions);
@ -277,9 +286,6 @@ pub fn connect_button_search(
let hash_alg_index = combo_box_image_hash_algorithm.active().unwrap() as usize;
let hash_alg = IMAGES_HASH_TYPE_COMBO_BOX[hash_alg_index].hash_alg;
let minimal_file_size = entry_general_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 16);
let maximal_file_size = entry_general_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
let ignore_same_size = check_button_image_ignore_same_size.is_active();
let similarity = similar_images::Similarity::Similar(scale_similarity_similar_images.value() as u32);
@ -293,6 +299,7 @@ pub fn connect_button_search(
sf.set_included_directory(included_directories);
sf.set_excluded_directory(excluded_directories);
sf.set_reference_directory(reference_directories);
sf.set_recursive_search(recursive_search);
sf.set_excluded_items(excluded_items);
sf.set_minimal_file_size(minimal_file_size);
@ -316,9 +323,6 @@ pub fn connect_button_search(
get_list_store(&tree_view_similar_videos_finder).clear();
let minimal_file_size = entry_general_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 16);
let maximal_file_size = entry_general_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
let tolerance = scale_similarity_similar_videos.value() as i32;
let delete_outdated_cache = check_button_settings_similar_videos_delete_outdated_cache.is_active();
@ -332,6 +336,7 @@ pub fn connect_button_search(
sf.set_included_directory(included_directories);
sf.set_excluded_directory(excluded_directories);
sf.set_reference_directory(reference_directories);
sf.set_recursive_search(recursive_search);
sf.set_excluded_items(excluded_items);
sf.set_minimal_file_size(minimal_file_size);
@ -352,8 +357,6 @@ pub fn connect_button_search(
get_list_store(&tree_view_same_music_finder).clear();
let minimal_file_size = entry_general_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 8);
let maximal_file_size = entry_general_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
let approximate_comparison = check_button_music_approximate_comparison.is_active();
let mut music_similarity: MusicSimilarity = MusicSimilarity::NONE;
@ -382,6 +385,7 @@ pub fn connect_button_search(
mf.set_included_directory(included_directories);
mf.set_excluded_directory(excluded_directories);
mf.set_reference_directory(reference_directories);
mf.set_excluded_items(excluded_items);
mf.set_minimal_file_size(minimal_file_size);
mf.set_maximal_file_size(maximal_file_size);

View file

@ -45,18 +45,18 @@ pub fn load_system_language(gui_data: &GuiData) {
break;
}
}
let mut found: bool = false;
// let mut found: bool = false;
for (index, lang) in LANGUAGES_ALL.iter().enumerate() {
if lang.short_text == short_lang {
found = true;
// found = true;
gui_data.settings.combo_box_settings_language.set_active(Some(index as u32));
break;
}
}
if found {
println!("INFO: Default system language {} is available, so choosing them", short_lang);
} else {
println!("INFO: Default system language {} is not available, using English(en) instead", short_lang);
}
// if found {
// println!("INFO: Default system language {} is available, so choosing them", short_lang);
// } else {
// println!("INFO: Default system language {} is not available, using English(en) instead", short_lang);
// }
}
}

View file

@ -9,11 +9,13 @@ pub fn connect_duplicate_combo_box(gui_data: &GuiData) {
let combo_box_duplicate_check_method = gui_data.main_notebook.combo_box_duplicate_check_method.clone();
let combo_box_duplicate_hash_type = gui_data.main_notebook.combo_box_duplicate_hash_type.clone();
combo_box_duplicate_check_method.connect_changed(move |combo_box_duplicate_check_method| {
let chosen_index = combo_box_duplicate_check_method.active().unwrap() as usize;
if DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index].check_method == CheckingMethod::Hash {
combo_box_duplicate_hash_type.set_sensitive(true);
} else {
combo_box_duplicate_hash_type.set_sensitive(false);
// None active can be if when adding elements(this signal is activated when e.g. adding new fields or removing them)
if let Some(chosen_index) = combo_box_duplicate_check_method.active() {
if DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index as usize].check_method == CheckingMethod::Hash {
combo_box_duplicate_hash_type.set_sensitive(true);
} else {
combo_box_duplicate_hash_type.set_sensitive(false);
}
}
});
}

View file

@ -8,7 +8,7 @@ use czkawka_core::common::Common;
use czkawka_core::fl;
use crate::gui_data::GuiData;
use crate::help_functions::{get_dialog_box_child, get_list_store, ColumnsDirectory};
use crate::help_functions::{get_dialog_box_child, get_list_store, ColumnsIncludedDirectory};
pub fn connect_selection_of_directories(gui_data: &GuiData) {
// Add manually directory
@ -120,7 +120,10 @@ fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_i
let list_store = get_list_store(&tree_view);
for file_entry in &folders {
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &file_entry.to_string_lossy().to_string())];
let values: [(u32, &dyn ToValue); 2] = [
(ColumnsIncludedDirectory::Path as u32, &file_entry.to_string_lossy().to_string()),
(ColumnsIncludedDirectory::ReferenceButton as u32, &false),
];
list_store.set(&list_store.append(), &values);
}
}
@ -154,7 +157,7 @@ fn add_manually_directories(window_main: &Window, tree_view: &TreeView) {
if !text.is_empty() {
let list_store = get_list_store(&tree_view);
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &text)];
let values: [(u32, &dyn ToValue); 2] = [(ColumnsIncludedDirectory::Path as u32, &text), (ColumnsIncludedDirectory::ReferenceButton as u32, &false)];
list_store.set(&list_store.append(), &values);
}
}

View file

@ -3,6 +3,46 @@ use gtk::TreeViewColumn;
use crate::help_functions::*;
// When adding new column do not forget to update translations
pub fn create_tree_view_included_directories(tree_view: &gtk::TreeView) {
let model = get_list_store(tree_view);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.set_title("Folders to check");
column.pack_start(&renderer, true);
column.add_attribute(&renderer, "text", ColumnsIncludedDirectory::Path as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererToggle::new();
renderer.connect_toggled(move |_r, path| {
let iter = model.iter(&path).unwrap();
let mut fixed = model
.value(&iter, ColumnsIncludedDirectory::ReferenceButton as i32)
.get::<bool>()
.unwrap_or_else(|err| panic!("ListStore value missing at path {:?}: {}", path, err));
fixed = !fixed;
model.set_value(&iter, ColumnsIncludedDirectory::ReferenceButton as u32, &fixed.to_value());
});
renderer.set_activatable(true);
let column = gtk::TreeViewColumn::new();
column.set_title("Reference folder");
column.pack_start(&renderer, true);
column.add_attribute(&renderer, "active", ColumnsIncludedDirectory::ReferenceButton as i32);
tree_view.append_column(&column);
}
pub fn create_tree_view_excluded_directories(tree_view: &gtk::TreeView) {
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.add_attribute(&renderer, "text", ColumnsExcludedDirectory::Path as i32);
tree_view.append_column(&column);
tree_view.set_headers_visible(false);
}
pub fn create_tree_view_duplicates(tree_view: &gtk::TreeView) {
let model = get_list_store(tree_view);
@ -25,6 +65,17 @@ pub fn create_tree_view_duplicates(tree_view: &gtk::TreeView) {
column.add_attribute(&renderer, "cell-background", ColumnsDuplicates::Color as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Size");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsDuplicates::Size as i32);
column.add_attribute(&renderer, "background", ColumnsDuplicates::Color as i32);
column.add_attribute(&renderer, "foreground", ColumnsDuplicates::TextColor as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
@ -443,16 +494,6 @@ pub fn create_tree_view_similar_videos(tree_view: &gtk::TreeView) {
tree_view.set_vexpand(true);
}
pub fn create_tree_view_directories(tree_view: &gtk::TreeView) {
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.add_attribute(&renderer, "text", ColumnsDirectory::Path as i32);
tree_view.append_column(&column);
tree_view.set_headers_visible(false);
}
pub fn create_tree_view_same_music(tree_view: &gtk::TreeView) {
let model = get_list_store(tree_view);

View file

@ -1,10 +1,11 @@
use czkawka_core::duplicate::CheckingMethod;
use gtk::prelude::*;
use gtk::{EventControllerKey, TreeView};
use czkawka_core::similar_images::{get_string_from_similarity, Similarity, SIMILAR_VALUES};
use crate::fl;
use crate::help_combo_box::IMAGES_HASH_SIZE_COMBO_BOX;
use crate::help_combo_box::{DUPLICATES_CHECK_METHOD_COMBO_BOX, IMAGES_HASH_SIZE_COMBO_BOX};
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
#[derive(Clone)]
@ -406,6 +407,7 @@ impl GuiMainNotebook {
// Change names of columns
let names_of_columns = [
vec![
fl!("main_tree_view_column_size"),
fl!("main_tree_view_column_file_name"),
fl!("main_tree_view_column_path"),
fl!("main_tree_view_column_modification"),
@ -479,5 +481,22 @@ impl GuiMainNotebook {
column.set_title(&names_of_columns[notebook_index][column_index - 1]);
}
}
{
let active = self.combo_box_duplicate_check_method.active().unwrap_or(0);
self.combo_box_duplicate_check_method.remove_all();
for i in &DUPLICATES_CHECK_METHOD_COMBO_BOX {
let text = match i.check_method {
CheckingMethod::Hash => fl!("duplicate_mode_hash_combo_box"),
CheckingMethod::Size => fl!("duplicate_mode_size_combo_box"),
CheckingMethod::Name => fl!("duplicate_mode_name_combo_box"),
_ => {
panic!()
}
};
self.combo_box_duplicate_check_method.append_text(&text);
}
self.combo_box_duplicate_check_method.set_active(Some(active));
}
}
}

View file

@ -164,5 +164,15 @@ impl GuiUpperNotebook {
.unwrap()
.set_text(&fl_thing);
}
let names_of_columns = [
vec![fl!("upper_tree_view_included_folder_column_title"), fl!("upper_tree_view_included_reference_column_title")], // Included folders
];
for (notebook_index, tree_view) in [self.tree_view_included_directories.clone()].iter().enumerate() {
for (column_index, column) in tree_view.columns().iter().enumerate() {
column.set_title(&names_of_columns[notebook_index][column_index]);
}
}
}
}

View file

@ -17,7 +17,7 @@ use czkawka_core::similar_videos::SimilarVideos;
use czkawka_core::temporary::Temporary;
use czkawka_core::{fl, invalid_symlinks};
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
use crate::notebook_enums::{NotebookMainEnum, NotebookUpperEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
#[cfg(not(target_family = "windows"))]
pub const CHARACTER: char = '/';
@ -69,8 +69,8 @@ pub static NOTEBOOKS_INFOS: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [
column_selection: ColumnsDuplicates::SelectionButton as i32,
column_color: Some(ColumnsDuplicates::Color as i32),
column_dimensions: None,
column_size: None,
column_size_as_bytes: None,
column_size: None, // Do not add, useless in hash and size mode
column_size_as_bytes: None, // Do not add, useless in hash and size mode
column_modification_as_secs: Some(ColumnsDuplicates::ModificationAsSecs as i32),
},
NotebookObject {
@ -209,6 +209,7 @@ pub enum ColumnsDuplicates {
// Columns for duplicate treeview
ActivatableSelectButton = 0,
SelectionButton,
Size,
Name,
Path,
Modification,
@ -226,8 +227,13 @@ pub enum ColumnsEmptyFolders {
ModificationAsSecs,
}
pub enum ColumnsDirectory {
// Columns for Included and Excluded Directories in upper Notebook
pub enum ColumnsIncludedDirectory {
// Columns for Included Directories in upper Notebook
Path = 0,
ReferenceButton,
}
pub enum ColumnsExcludedDirectory {
// Columns for Excluded Directories in upper Notebook
Path = 0,
}
@ -328,7 +334,7 @@ pub const HEADER_ROW_COLOR: &str = "#272727";
//pub const MAIN_ROW_COLOR: &str = "#f4f434"; // TEST
//pub const HEADER_ROW_COLOR: &str = "#010101"; // TEST
pub fn get_string_from_list_store(tree_view: &gtk::TreeView) -> Vec<String> {
pub fn get_string_from_list_store(tree_view: &gtk::TreeView, column_full_path: i32, column_selection: Option<i32>) -> Vec<String> {
let list_store: gtk::ListStore = get_list_store(tree_view);
let mut string_vector: Vec<String> = Vec::new();
@ -339,11 +345,21 @@ pub fn get_string_from_list_store(tree_view: &gtk::TreeView) -> Vec<String> {
return string_vector;
}
};
loop {
string_vector.push(list_store.value(&tree_iter, 0).get::<String>().unwrap());
if !list_store.iter_next(&tree_iter) {
return string_vector;
}
match column_selection {
Some(column_selection) => loop {
if list_store.value(&tree_iter, column_selection).get::<bool>().unwrap() {
string_vector.push(list_store.value(&tree_iter, column_full_path).get::<String>().unwrap());
}
if !list_store.iter_next(&tree_iter) {
return string_vector;
}
},
None => loop {
string_vector.push(list_store.value(&tree_iter, column_full_path).get::<String>().unwrap());
if !list_store.iter_next(&tree_iter) {
return string_vector;
}
},
}
}
@ -470,6 +486,16 @@ pub fn get_notebook_enum_from_tree_view(tree_view: &gtk::TreeView) -> NotebookMa
}
}
pub fn get_notebook_upper_enum_from_tree_view(tree_view: &gtk::TreeView) -> NotebookUpperEnum {
match (*tree_view).widget_name().to_string().as_str() {
"tree_view_upper_included_directories" => NotebookUpperEnum::IncludedDirectories,
"tree_view_upper_excluded_directories" => NotebookUpperEnum::ExcludedDirectories,
e => {
panic!("{}", e)
}
}
}
pub fn get_notebook_object_from_tree_view(tree_view: &gtk::TreeView) -> &NotebookObject {
let nb_enum = get_notebook_enum_from_tree_view(tree_view);
&NOTEBOOKS_INFOS[nb_enum as usize]
@ -484,7 +510,7 @@ pub fn get_full_name_from_path_name(path: &str, name: &str) -> String {
}
// After e.g. deleting files, header may become orphan or have one child, so should be deleted in this case
pub fn clean_invalid_headers(model: &gtk::ListStore, column_color: i32) {
pub fn clean_invalid_headers(model: &gtk::ListStore, column_color: i32, column_path: i32) {
// Remove only child from header
if let Some(first_iter) = model.iter_first() {
let mut vec_tree_path_to_delete: Vec<gtk::TreePath> = Vec::new();
@ -495,55 +521,109 @@ pub fn clean_invalid_headers(model: &gtk::ListStore, column_color: i32) {
let mut next_iter;
let mut next_next_iter;
'main: loop {
if model.value(&current_iter, column_color).get::<String>().unwrap() != HEADER_ROW_COLOR {
panic!("First deleted element, should be a header"); // First element should be header
};
next_iter = current_iter.clone();
if !model.iter_next(&next_iter) {
// There is only single header left (H1 -> END) -> (NOTHING)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
break 'main;
}
// Empty means default check type
if model.value(&current_iter, column_path).get::<String>().unwrap().is_empty() {
'main: loop {
if model.value(&current_iter, column_color).get::<String>().unwrap() != HEADER_ROW_COLOR {
panic!("First deleted element, should be a header"); // First element should be header
};
if model.value(&next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
current_iter = next_iter.clone();
continue 'main;
}
next_next_iter = next_iter.clone();
if !model.iter_next(&next_next_iter) {
// There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
vec_tree_path_to_delete.push(model.path(&next_iter).unwrap());
break 'main;
}
if model.value(&next_next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
vec_tree_path_to_delete.push(model.path(&next_iter).unwrap());
current_iter = next_next_iter.clone();
continue 'main;
}
loop {
// (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD)
if !model.iter_next(&next_next_iter) {
next_iter = current_iter.clone();
if !model.iter_next(&next_iter) {
// There is only single header left (H1 -> END) -> (NOTHING)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
break 'main;
}
// Move to next header
if model.value(&next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
current_iter = next_iter.clone();
continue 'main;
}
next_next_iter = next_iter.clone();
if !model.iter_next(&next_next_iter) {
// There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
vec_tree_path_to_delete.push(model.path(&next_iter).unwrap());
break 'main;
}
if model.value(&next_next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
vec_tree_path_to_delete.push(model.path(&next_iter).unwrap());
current_iter = next_next_iter.clone();
continue 'main;
}
loop {
// (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD)
if !model.iter_next(&next_next_iter) {
break 'main;
}
// Move to next header
if model.value(&next_next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
current_iter = next_next_iter.clone();
continue 'main;
}
}
}
for tree_path in vec_tree_path_to_delete.iter().rev() {
model.remove(&model.iter(tree_path).unwrap());
}
}
for tree_path in vec_tree_path_to_delete.iter().rev() {
model.remove(&model.iter(tree_path).unwrap());
// Non empty means that header points at reference folder
else {
// TODO verify how it works
'reference: loop {
if model.value(&current_iter, column_color).get::<String>().unwrap() != HEADER_ROW_COLOR {
panic!("First deleted element, should be a header"); // First element should be header
};
next_iter = current_iter.clone();
if !model.iter_next(&next_iter) {
// There is only single header left (H1 -> END) -> (NOTHING)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
break 'reference;
}
if model.value(&next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2)
vec_tree_path_to_delete.push(model.path(&current_iter).unwrap());
current_iter = next_iter.clone();
continue 'reference;
}
next_next_iter = next_iter.clone();
if !model.iter_next(&next_next_iter) {
// There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING)
break 'reference;
}
if model.value(&next_next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
// One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2)
current_iter = next_next_iter.clone();
continue 'reference;
}
loop {
// (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD)
if !model.iter_next(&next_next_iter) {
break 'reference;
}
// Move to next header
if model.value(&next_next_iter, column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
current_iter = next_next_iter.clone();
continue 'reference;
}
}
}
for tree_path in vec_tree_path_to_delete.iter().rev() {
model.remove(&model.iter(tree_path).unwrap());
}
}
}

View file

@ -101,8 +101,8 @@ pub fn initialize_gui(gui_data: &mut GuiData) {
// Set step increment
{
let scale_similarity_similar_images = gui_data.main_notebook.scale_similarity_similar_images.clone();
scale_similarity_similar_images.set_range(0_f64, SIMILAR_VALUES[1][5] as f64); // This defaults to value of minimal size of hash 8
scale_similarity_similar_images.set_fill_level(SIMILAR_VALUES[1][5] as f64);
scale_similarity_similar_images.set_range(0_f64, SIMILAR_VALUES[0][5] as f64); // This defaults to value of minimal size of hash 8
scale_similarity_similar_images.set_fill_level(SIMILAR_VALUES[0][5] as f64);
scale_similarity_similar_images.adjustment().set_step_increment(1_f64);
}
// Set step increment
@ -124,9 +124,10 @@ pub fn initialize_gui(gui_data: &mut GuiData) {
let image_preview = gui_data.main_notebook.image_preview_duplicates.clone();
image_preview.hide();
let col_types: [glib::types::Type; 8] = [
let col_types: [glib::types::Type; 9] = [
glib::types::Type::BOOL, // ActivatableSelectButton
glib::types::Type::BOOL, // SelectionButton
glib::types::Type::STRING, // Size
glib::types::Type::STRING, // Name
glib::types::Type::STRING, // Path
glib::types::Type::STRING, // Modification
@ -397,17 +398,23 @@ pub fn initialize_gui(gui_data: &mut GuiData) {
let tree_view = gui_data.upper_notebook.tree_view_included_directories.clone();
let evk = gui_data.upper_notebook.evk_tree_view_included_directories.clone();
let col_types: [glib::types::Type; 1] = [glib::types::Type::STRING];
let col_types: [glib::types::Type; 2] = [
glib::types::Type::STRING, // Path
glib::types::Type::BOOL, // ReferenceButton
];
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
tree_view.set_model(Some(&list_store));
tree_view.selection().set_mode(SelectionMode::Multiple);
create_tree_view_directories(&tree_view);
create_tree_view_included_directories(&tree_view);
tree_view.set_widget_name("tree_view_upper_included_directories");
scrolled_window.add(&tree_view);
scrolled_window.show_all();
tree_view.connect_button_press_event(opening_double_click_function_directories);
evk.connect_key_pressed(opening_enter_function_ported_upper_directories);
evk.connect_key_released(move |_event_controller_key, _key_value, key_code, _modifier_type| {
if key_code == KEY_DELETE {
let list_store = get_list_store(&tree_view);
@ -433,11 +440,14 @@ pub fn initialize_gui(gui_data: &mut GuiData) {
tree_view.set_model(Some(&list_store));
tree_view.selection().set_mode(SelectionMode::Multiple);
create_tree_view_directories(&tree_view);
create_tree_view_excluded_directories(&tree_view);
tree_view.set_widget_name("tree_view_upper_excluded_directories");
scrolled_window.add(&tree_view);
scrolled_window.show_all();
tree_view.connect_button_press_event(opening_double_click_function_directories);
evk.connect_key_pressed(opening_enter_function_ported_upper_directories);
evk.connect_key_released(move |_event_controller_key, _key_value, key_code, _modifier_type| {
if key_code == KEY_DELETE {
let list_store = get_list_store(&tree_view);
@ -516,7 +526,6 @@ fn connect_event_mouse(gui_data: &GuiData) {
let preview_path = gui_data.preview_path.clone();
let image_preview = gui_data.main_notebook.image_preview_similar_images.clone();
tree_view.connect_button_press_event(opening_double_click_function);
tree_view.connect_button_release_event(move |tree_view, _event| {
let nb_object = &NOTEBOOKS_INFOS[NotebookMainEnum::SimilarImages as usize];
let preview_path = preview_path.clone();

View file

@ -2,6 +2,7 @@ use gdk::ModifierType;
use gtk::prelude::*;
use crate::help_functions::*;
use crate::notebook_enums::NotebookUpperEnum;
// TODO add option to open files and folders from context menu activated by pressing ONCE with right mouse button
@ -13,7 +14,40 @@ pub fn opening_enter_function_ported(event_controller: &gtk::EventControllerKey,
}
let nt_object = get_notebook_object_from_tree_view(&tree_view);
handle_tree_keypress(&tree_view, key_code, nt_object.column_name, nt_object.column_path, nt_object.column_selection);
handle_tree_keypress(
&tree_view,
key_code,
nt_object.column_name,
nt_object.column_path,
nt_object.column_selection,
nt_object.column_color,
);
false // True catches signal, and don't send it to function, e.g. up button is catched and don't move selection
}
pub fn opening_enter_function_ported_upper_directories(event_controller: &gtk::EventControllerKey, _key_value: u32, key_code: u32, _modifier_type: ModifierType) -> bool {
let tree_view = event_controller.widget().unwrap().downcast::<gtk::TreeView>().unwrap();
#[cfg(debug_assertions)]
{
println!("key_code {}", key_code);
}
match get_notebook_upper_enum_from_tree_view(&tree_view) {
NotebookUpperEnum::IncludedDirectories => {
handle_tree_keypress_upper_directories(
&tree_view,
key_code,
ColumnsIncludedDirectory::Path as i32,
Some(ColumnsIncludedDirectory::ReferenceButton as i32),
);
}
NotebookUpperEnum::ExcludedDirectories => {
handle_tree_keypress_upper_directories(&tree_view, key_code, ColumnsExcludedDirectory::Path as i32, None);
}
_ => {
panic!()
}
}
false // True catches signal, and don't send it to function, e.g. up button is catched and don't move selection
}
@ -27,6 +61,23 @@ pub fn opening_double_click_function(tree_view: &gtk::TreeView, event: &gdk::Eve
gtk::Inhibit(false)
}
pub fn opening_double_click_function_directories(tree_view: &gtk::TreeView, event: &gdk::EventButton) -> gtk::Inhibit {
if event.event_type() == gdk::EventType::DoubleButtonPress && (event.button() == 1 || event.button() == 3) {
match get_notebook_upper_enum_from_tree_view(tree_view) {
NotebookUpperEnum::IncludedDirectories => {
common_open_function_upper_directories(tree_view, ColumnsIncludedDirectory::Path as i32);
}
NotebookUpperEnum::ExcludedDirectories => {
common_open_function_upper_directories(tree_view, ColumnsExcludedDirectory::Path as i32);
}
_ => {
panic!()
}
}
}
gtk::Inhibit(false)
}
// // GTK 4
// pub fn opening_enter_function_ported(event_controller: &gtk4::EventControllerKey, _key: gdk4::keys::Key, key_code: u32, _modifier_type: ModifierType) -> gtk4::Inhibit {
// let tree_view = event_controller.widget().unwrap().downcast::<gtk4::TreeView>().unwrap();
@ -58,15 +109,20 @@ enum OpenMode {
PathAndName,
}
fn common_mark_function(tree_view: &gtk::TreeView, column_name: i32) {
fn common_mark_function(tree_view: &gtk::TreeView, column_selection: i32, column_color: Option<i32>) {
let selection = tree_view.selection();
let (selected_rows, tree_model) = selection.selected_rows();
let model = get_list_store(tree_view);
for tree_path in selected_rows.iter().rev() {
let value = !tree_model.value(&tree_model.iter(tree_path).unwrap(), column_name).get::<bool>().unwrap();
model.set_value(&tree_model.iter(tree_path).unwrap(), column_name as u32, &value.to_value());
if let Some(column_color) = column_color {
if model.value(&model.iter(tree_path).unwrap(), column_color).get::<String>().unwrap() == HEADER_ROW_COLOR {
continue;
}
}
let value = !tree_model.value(&tree_model.iter(tree_path).unwrap(), column_selection).get::<bool>().unwrap();
model.set_value(&tree_model.iter(tree_path).unwrap(), column_selection as u32, &value.to_value());
}
}
@ -91,13 +147,38 @@ fn common_open_function(tree_view: &gtk::TreeView, column_name: i32, column_path
}
}
fn handle_tree_keypress(tree_view: &gtk::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32) {
fn common_open_function_upper_directories(tree_view: &gtk::TreeView, column_full_path: i32) {
let selection = tree_view.selection();
let (selected_rows, tree_model) = selection.selected_rows();
for tree_path in selected_rows.iter().rev() {
let full_path = tree_model.value(&tree_model.iter(tree_path).unwrap(), column_full_path).get::<String>().unwrap();
open::that_in_background(&full_path);
}
}
fn handle_tree_keypress_upper_directories(tree_view: &gtk::TreeView, key_code: u32, full_path_column: i32, mark_column: Option<i32>) {
match key_code {
KEY_ENTER => {
common_open_function_upper_directories(tree_view, full_path_column);
}
KEY_SPACE => {
if let Some(mark_column) = mark_column {
common_mark_function(tree_view, mark_column, None);
}
}
_ => {}
}
}
fn handle_tree_keypress(tree_view: &gtk::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32, column_color: Option<i32>) {
match key_code {
KEY_ENTER => {
common_open_function(tree_view, name_column, path_column, OpenMode::PathAndName);
}
KEY_SPACE => {
common_mark_function(tree_view, mark_column);
common_mark_function(tree_view, mark_column, column_color);
}
_ => {}
}
@ -154,3 +235,6 @@ pub fn select_function_similar_videos(_tree_selection: &gtk::TreeSelection, tree
true
}
pub fn select_function_always_true(_tree_selection: &gtk::TreeSelection, _tree_model: &gtk::TreeModel, _tree_path: &gtk::TreePath, _is_path_currently_selected: bool) -> bool {
true
}

View file

@ -57,7 +57,8 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
let list_store = get_list_store(&tree_view_included_directories);
if let Some(iter) = list_store.iter_first() {
loop {
data_to_save.push(list_store.value(&iter, ColumnsDirectory::Path as i32).get::<String>().unwrap());
// TODO maybe save also here reference directories?
data_to_save.push(list_store.value(&iter, ColumnsIncludedDirectory::Path as i32).get::<String>().unwrap());
if !list_store.iter_next(&iter) {
break;
}
@ -70,7 +71,7 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
let list_store = get_list_store(&tree_view_excluded_directories);
if let Some(iter) = list_store.iter_first() {
loop {
data_to_save.push(list_store.value(&iter, ColumnsDirectory::Path as i32).get::<String>().unwrap());
data_to_save.push(list_store.value(&iter, ColumnsExcludedDirectory::Path as i32).get::<String>().unwrap());
if !list_store.iter_next(&iter) {
break;
}
@ -213,12 +214,26 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
}
}
if data_saved {
add_text_to_text_view(&text_view_errors, format!("{} {}", fl!("saving_loading_saving_success"), config_file.display()).as_str());
add_text_to_text_view(
&text_view_errors,
fl!(
"saving_loading_saving_success",
generate_translation_hashmap(vec![("name", config_file.display().to_string())])
)
.as_str(),
);
} else {
add_text_to_text_view(&text_view_errors, format!("Failed to save configuration data to file {}", config_file.display()).as_str());
add_text_to_text_view(
&text_view_errors,
fl!(
"saving_loading_saving_failure",
generate_translation_hashmap(vec![("name", config_file.display().to_string())])
)
.as_str(),
);
}
} else {
add_text_to_text_view(&text_view_errors, "Failed to get home directory, so can't save file.");
add_text_to_text_view(&text_view_errors, fl!("saving_loading_failed_to_get_home_directory").as_str());
}
}
@ -682,7 +697,10 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
list_store.clear();
for directory in included_directories {
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &directory)];
let values: [(u32, &dyn ToValue); 2] = [
(ColumnsIncludedDirectory::Path as u32, &directory),
(ColumnsIncludedDirectory::ReferenceButton as u32, &false),
];
list_store.set(&list_store.append(), &values);
}
@ -692,7 +710,7 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
list_store.clear();
for directory in excluded_directories {
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &directory)];
let values: [(u32, &dyn ToValue); 1] = [(ColumnsExcludedDirectory::Path as u32, &directory)];
list_store.set(&list_store.append(), &values);
}
@ -782,7 +800,10 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb
}
};
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &current_dir)];
let values: [(u32, &dyn ToValue); 2] = [
(ColumnsIncludedDirectory::Path as u32, &current_dir),
(ColumnsIncludedDirectory::ReferenceButton as u32, &false),
];
list_store.set(&list_store.append(), &values);
}
// Resetting excluded directories
@ -792,7 +813,7 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb
list_store.clear();
if cfg!(target_family = "unix") {
for i in ["/proc", "/dev", "/sys", "/run", "/snap"].iter() {
let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &i)];
let values: [(u32, &dyn ToValue); 1] = [(ColumnsExcludedDirectory::Path as u32, &i)];
list_store.set(&list_store.append(), &values);
}
}

View file

@ -117,8 +117,11 @@ check_button_general_same_size_tooltip = Ignore from results, files which have i
main_label_size_bytes_tooltip = Size of files which will be used in scan
# Upper window
upper_tree_view_included_folder_column_title = Folders to Search
upper_tree_view_included_reference_column_title = Reference Folders
upper_recursive_button = Recursive
upper_recursive_button_tooltip = If selected, search also for files which are not placed directly under chosen folders
upper_recursive_button_tooltip = If selected, search also for files which are not placed directly under chosen folders.
upper_manual_add_included_button = Manual Add
upper_add_included_button = Add
@ -127,19 +130,19 @@ upper_manual_add_excluded_button = Manual Add
upper_add_excluded_button = Add
upper_remove_excluded_button = Remove
upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand
upper_add_included_button_tooltip = Add new directory to search
upper_remove_included_button_tooltip = Delete directory from search
upper_manual_add_excluded_button_tooltip = Allows to add excluded directory name by hand
upper_add_excluded_button_tooltip = Add directory to be excluded in search
upper_remove_excluded_button_tooltip = Delete directory from excluded
upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand.
upper_add_included_button_tooltip = Add new directory to search.
upper_remove_included_button_tooltip = Delete directory from search.
upper_manual_add_excluded_button_tooltip = Allows to add excluded directory name by hand.
upper_add_excluded_button_tooltip = Add directory to be excluded in search.
upper_remove_excluded_button_tooltip = Delete directory from excluded.
upper_notebook_items_configuration = Items Configuration
upper_notebook_excluded_directories = Excluded Directories
upper_notebook_included_directories = Included Directories
upper_allowed_extensions_tooltip =
Allowed extensions must be separated by commas(by default all are available)
Allowed extensions must be separated by commas(by default all are available).
Macros IMAGE, VIDEO, MUSIC, TEXT which adds multiple extensions at once are also available.
@ -181,7 +184,7 @@ popover_custom_name_check_button_entry_tooltip =
popover_custom_regex_check_button_entry_tooltip =
Allows to select records by specified Regex.
With this mode, searched text is Path with Name
With this mode, searched text is Path with Name.
Example usage:
/usr/bin/ziemniak.txt can be found with /ziem[a-z]+
@ -216,9 +219,9 @@ bottom_symlink_button = Symlink
bottom_hardlink_button = Hardlink
bottom_move_button = Move
bottom_search_button_tooltip = Start to search for files/folders
bottom_search_button_tooltip = Start to search for files/folders.
bottom_select_button_tooltip = Selects records. Only selected files/folders can be later processed.
bottom_delete_button_tooltip = Delete selected files/folders
bottom_delete_button_tooltip = Delete selected files/folders.
bottom_save_button_tooltip = Save data about search to file
bottom_symlink_button_tooltip =
Creates symbolic links.
@ -362,21 +365,17 @@ settings_folder_settings_open = Open settings folder
# Compute results
compute_stopped_by_user = Searching was stopped by user
compute_found = Found
compute_duplicated_files_in = duplicated files in
compute_groups_which_took = groups which took
compute_groups = groups
compute_duplicates_for = duplicates for
compute_empty_folders = empty folders
compute_empty_files = empty files
compute_biggest_files = biggest files
compute_temporary_files = temporary files
compute_similar_image = images
compute_similar_videos = videos
compute_music_files = music files
compute_symlinks = invalid symlinks
compute_broken_files = broken files
compute_found_duplicates_hash_size = Found { $number_files } duplicates in { $number_groups } groups which took { $size }
compute_found_duplicates_name = Found { $number_files } duplicates in { $number_groups } groups
compute_found_empty_folders = Found { $number_files } empty folders
compute_found_empty_files = Found { $number_files } empty files
compute_found_big_files = Found { $number_files } big files
compute_found_temporary_files = Found { $number_files } temporary files
compute_found_images = Found { $number_files } similar images in { $number_groups } groups
compute_found_videos = Found { $number_files } similar videos in { $number_groups } groups
compute_found_music = Found { $number_files } similar music files in { $number_groups } groups
compute_found_invalid_symlinks = Found { $number_files } invalid symlinks
compute_found_broken_files = Found { $number_files } broken files
# Progress window
progress_scanning_general_file = Scanning {$file_number} file
@ -396,9 +395,12 @@ progress_current_stage = Current Stage:{" "}
progress_all_stages = All Stages:{" "}
# Saving loading
saving_loading_saving_success = Saved configuration to file
saving_loading_saving_success = Saved configuration to file { $name }.
saving_loading_saving_failure = Failed to save configuration data to file { $name }.
saving_loading_reset_configuration = Current configuration was cleared.
saving_loading_loading_success = Properly loaded configuration from file
saving_loading_loading_success = Properly loaded configuration from file.
saving_loading_failed_to_get_home_directory = Failed to get home directory to open/save config file.
# Invalid symlinks
invalid_symlink_infinite_recursion = Infinite recursion
@ -438,7 +440,7 @@ hard_sym_link_label = Are you sure that you want to link this files?
move_folder_failed = Failed to move folder {$name}, reason {$reason}
move_file_failed = Failed to move file {$name}, reason {$reason}
move_files_title_dialog = Choose folder to which you want to move duplicated files
move_files_choose_more_than_1_path = Only 1 path must be selected to be able to copy there duplicated files, selected {$path_number}
move_files_choose_more_than_1_path = Only 1 path must be selected to be able to copy there duplicated files, selected {$path_number}.
move_stats = Properly moved {$num_files}/{$all_files} items
save_results_to_file = Saved results to file {$name}
@ -460,8 +462,8 @@ cache_clear_message_label_3 = This may speedup a little loading/saving to cache.
cache_clear_message_label_4 = WARNING: Operation will remove all cached data from unplugged external drives, so hash will need to be generated again.
# Show preview
preview_temporary_file = Failed to open temporary image file {$name}, reason {$reason}
preview_0_size = Cannot create preview of image {$name}, with 0 width or height
preview_temporary_image_save = Failed to save temporary image file to {$name}, reason {$reason}
preview_temporary_image_remove = Failed to delete temporary image file {$name}, reason {$reason}
preview_failed_to_create_cache_dir = Failed to create dir {$name} needed by image preview, reason {$reason}
preview_temporary_file = Failed to open temporary image file {$name}, reason {$reason}.
preview_0_size = Cannot create preview of image {$name}, with 0 width or height.
preview_temporary_image_save = Failed to save temporary image file to {$name}, reason {$reason}.
preview_temporary_image_remove = Failed to delete temporary image file {$name}, reason {$reason}.
preview_failed_to_create_cache_dir = Failed to create dir {$name} needed by image preview, reason {$reason}.

View file

@ -97,6 +97,8 @@ check_button_general_same_size = Ignoruj identyczny rozmiar
check_button_general_same_size_tooltip = Wyrzuca z wyników skanowania pliki, które posiadają identyczny rozmiar, po to by w wynikach zostały tylko niemal identyczne rekordy.
main_label_size_bytes_tooltip = Rozmiar plików które będą zawarte przy przeszukiwaniu
# Upper window
upper_tree_view_included_folder_column_title = Foldery do Przeszukania
upper_tree_view_included_reference_column_title = Źródłowy Folder
upper_recursive_button = Rekursywnie
upper_recursive_button_tooltip = Jeśli zaznaczony, szuka plików i folderów również w katalogach wewnątrz, nawet jeśli nie znajdują się one bezpośrednio w tym folderze.
upper_manual_add_included_button = Ręcznie Dodaj