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:
parent
5db5d17afb
commit
c4f26883b1
|
@ -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)
|
||||
|
|
62
README.md
62
README.md
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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: >k::TreeView,
|
||||
column_color: i32,
|
||||
column_selection: i32,
|
||||
column_path: i32,
|
||||
window_main: >k::Window,
|
||||
check_button_settings_confirm_group_deletion: >k::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());
|
||||
}
|
||||
|
|
|
@ -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: >k::Window) -> Dialog {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: >k::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: >k::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: >k::TreeView) {
|
||||
let model = get_list_store(tree_view);
|
||||
|
||||
|
@ -25,6 +65,17 @@ pub fn create_tree_view_duplicates(tree_view: >k::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: >k::TreeView) {
|
|||
tree_view.set_vexpand(true);
|
||||
}
|
||||
|
||||
pub fn create_tree_view_directories(tree_view: >k::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: >k::TreeView) {
|
||||
let model = get_list_store(tree_view);
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: >k::TreeView) -> Vec<String> {
|
||||
pub fn get_string_from_list_store(tree_view: >k::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: >k::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: >k::TreeView) -> NotebookMa
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_notebook_upper_enum_from_tree_view(tree_view: >k::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: >k::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: >k::ListStore, column_color: i32) {
|
||||
pub fn clean_invalid_headers(model: >k::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: >k::ListStore, column_color: i32) {
|
|||
|
||||
let mut next_iter;
|
||||
let mut next_next_iter;
|
||||
'main: loop {
|
||||
if model.value(¤t_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(¤t_iter).unwrap());
|
||||
break 'main;
|
||||
}
|
||||
// Empty means default check type
|
||||
if model.value(¤t_iter, column_path).get::<String>().unwrap().is_empty() {
|
||||
'main: loop {
|
||||
if model.value(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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: >k::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: >k::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: >k::TreeView, event: &gdk::Eve
|
|||
gtk::Inhibit(false)
|
||||
}
|
||||
|
||||
pub fn opening_double_click_function_directories(tree_view: >k::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: >k4::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: >k::TreeView, column_name: i32) {
|
||||
fn common_mark_function(tree_view: >k::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: >k::TreeView, column_name: i32, column_path
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_tree_keypress(tree_view: >k::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32) {
|
||||
fn common_open_function_upper_directories(tree_view: >k::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: >k::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: >k::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: >k::TreeSelection, tree
|
|||
|
||||
true
|
||||
}
|
||||
pub fn select_function_always_true(_tree_selection: >k::TreeSelection, _tree_model: >k::TreeModel, _tree_path: >k::TreePath, _is_path_currently_selected: bool) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -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, ¤t_dir)];
|
||||
let values: [(u32, &dyn ToValue); 2] = [
|
||||
(ColumnsIncludedDirectory::Path as u32, ¤t_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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue