1
0
Fork 0
mirror of synced 2024-05-11 16:02:44 +12:00

Add finding similar audio by content (#970)

* In prehashing check for user clicks, less often

* Similar audio

* Remove ugly time checking

* Fix using cache

* Fix cache and improve performance of validating items

* Remove cache type - cache should be saved to two different cache files(because )

* Working

* Simple multithreading

* Basic Generalization

* Reference folder and swap cleaning

* Split into multiple files

* Commons, improved GUI message

* Simplifying thread run

* Check was stopped

* Fix checking same files

* Make read single file tag more general

* Remove unnnecessary clone

* Reading tags

* Base

* Search

* Gui Fix

* Gui Fix

* Tooltip
This commit is contained in:
Rafał Mikrut 2023-05-07 20:54:05 +02:00 committed by GitHub
parent 14ccb49e45
commit 78d00eeb99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1900 additions and 1612 deletions

52
Cargo.lock generated
View file

@ -389,9 +389,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.2.6"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d70680e56dc65cb226c361aaa4e4a16d1f7e082bfed9ffceaee39c2012384ec"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
dependencies = [
"clap_builder",
"clap_derive",
@ -400,9 +400,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.2.6"
version = "4.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fad499d5e07338414687350c5fdb82b1ab0001e9b26aa6275deccb684b14164"
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
dependencies = [
"anstream",
"anstyle",
@ -744,13 +744,13 @@ dependencies = [
[[package]]
name = "displaydoc"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
@ -1780,9 +1780,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.142"
version = "0.2.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
checksum = "edc207893e85c5d6be840e969b496b53d94cec8be2d501b214f50daa97fa8024"
[[package]]
name = "libheif-rs"
@ -1827,9 +1827,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c"
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
[[package]]
name = "locale_config"
@ -2293,9 +2293,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.26"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "png"
@ -2633,9 +2633,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.18"
version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -2708,18 +2708,18 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.160"
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
dependencies = [
"proc-macro2",
"quote",
@ -3178,9 +3178,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.20"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc"
dependencies = [
"serde",
"time-core",
@ -3188,9 +3188,9 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "tinystr"
@ -3770,9 +3770,9 @@ dependencies = [
[[package]]
name = "zip"
version = "0.6.4"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef"
checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125"
dependencies = [
"aes 0.7.5",
"byteorder",
@ -3784,7 +3784,7 @@ dependencies = [
"hmac",
"pbkdf2",
"sha1",
"time 0.3.20",
"time 0.3.21",
]
[[package]]

View file

@ -38,7 +38,7 @@ pdf = "0.8"
# Needed by audio similarity feature
rusty-chromaprint = "0.1"
symphonia = { version = "0.5", features = ["mp3", "aac", "alac", "flac", "isomp4", "mkv", "ogg", "pcm", "vorbis", "wav"] }
symphonia = { version = "0.5", features = ["all"] }
# Hashes for duplicate files
blake3 = "1.3"
@ -74,10 +74,10 @@ num_cpus = "1.15"
# Heif/Heic
libheif-rs = { version = "0.18.0", optional = true } # Do not upgrade now, since Ubuntu 22.04 not works with newer version
anyhow = { version = "1.0", optional = true }
anyhow = { version = "1.0" }
state = "0.5"
[features]
default = []
heif = ["dep:libheif-rs", "dep:anyhow"]
heif = ["dep:libheif-rs"]

View file

@ -6,14 +6,13 @@ use std::mem;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
use mime_guess::get_mime_extensions;
use rayon::prelude::*;
use crate::common::{prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, Common};
use crate::common::{prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads};
use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
@ -298,16 +297,12 @@ impl BadExtensions {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
if let Some(files_to_check) = grouped_file_entries.get(&()) {
self.files_to_check = files_to_check.clone();
}
self.text_messages.warnings.extend(warnings);
Common::print_time(start_time, SystemTime::now(), "check_files");
true
}
DirTraversalResult::SuccessFolders { .. } => {
@ -318,24 +313,10 @@ impl BadExtensions {
}
fn look_for_bad_extensions_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let system_time = SystemTime::now();
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 1, 1, self.files_to_check.len(), CheckingMethod::None);
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
1,
1,
self.files_to_check.len(),
CheckingMethod::None,
);
let mut files_to_check = Default::default();
mem::swap(&mut files_to_check, &mut self.files_to_check);
let files_to_check = mem::take(&mut self.files_to_check);
let mut hashmap_workarounds: HashMap<&str, Vec<&str>> = Default::default();
for (proper, found) in WORKAROUNDS {
@ -357,8 +338,6 @@ impl BadExtensions {
self.information.number_of_files_with_bad_extension = self.bad_extensions_files.len();
Common::print_time(system_time, SystemTime::now(), "bad extension finding");
// Clean unused data
self.files_to_check = Default::default();
@ -525,7 +504,6 @@ impl DebugPrint for BadExtensions {
impl SaveResults for BadExtensions {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -557,7 +535,7 @@ impl SaveResults for BadExtensions {
} else {
write!(writer, "Not found any files with invalid extension.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
@ -566,12 +544,9 @@ impl PrintResults for BadExtensions {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} files with invalid extension.\n", self.information.number_of_files_with_bad_extension);
for file_entry in &self.bad_extensions_files {
println!("{} ----- {}", file_entry.path.display(), file_entry.proper_extensions);
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

View file

@ -3,10 +3,9 @@ use std::fs;
use std::fs::{DirEntry, File, Metadata};
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
@ -14,7 +13,6 @@ use humansize::format_size;
use humansize::BINARY;
use rayon::prelude::*;
use crate::common::Common;
use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, split_path};
use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData};
use crate::common_directory::Directories;
@ -142,7 +140,6 @@ impl BigFile {
}
fn look_for_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
let mut old_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
@ -151,9 +148,7 @@ impl BigFile {
folders_to_check.push(id.clone());
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 0, 0, 0, CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 0, 0, CheckingMethod::None);
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -213,7 +208,6 @@ impl BigFile {
self.extract_n_biggest_files(old_map);
Common::print_time(start_time, SystemTime::now(), "look_for_big_files");
true
}
@ -308,8 +302,6 @@ impl BigFile {
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for (_, file_entry) in &self.big_files {
@ -322,8 +314,6 @@ impl BigFile {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
@ -365,7 +355,6 @@ impl DebugPrint for BigFile {
impl SaveResults for BigFile {
/// Saving results to provided file
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -401,14 +390,13 @@ impl SaveResults for BigFile {
} else {
write!(writer, "Not found any files.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
impl PrintResults for BigFile {
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
if self.search_mode == SearchMode::BiggestFiles {
println!("{} the biggest files.\n\n", self.information.number_of_real_files);
} else {
@ -417,6 +405,5 @@ impl PrintResults for BigFile {
for (size, file_entry) in &self.big_files {
println!("{} ({}) - {}", format_size(*size, BINARY), size, file_entry.path.display());
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

View file

@ -3,9 +3,9 @@ use std::fs::{DirEntry, File, Metadata};
use std::io::prelude::*;
use std::io::{BufReader, BufWriter};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use std::{fs, mem, panic};
use crossbeam_channel::Receiver;
@ -18,7 +18,7 @@ use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::common::{
check_folder_children, create_crash_message, open_cache_folder, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, Common, PDF_FILES_EXTENSIONS,
check_folder_children, create_crash_message, open_cache_folder, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, PDF_FILES_EXTENSIONS,
};
use crate::common::{AUDIO_FILES_EXTENSIONS, IMAGE_RS_BROKEN_FILES_EXTENSIONS, ZIP_FILES_EXTENSIONS};
use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData};
@ -193,7 +193,6 @@ impl BrokenFiles {
}
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
// Add root folders for finding
@ -201,9 +200,7 @@ impl BrokenFiles {
folders_to_check.push(id.clone());
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 0, 1, 0, CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 1, 0, CheckingMethod::None);
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -263,7 +260,6 @@ impl BrokenFiles {
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
Common::print_time(start_time, SystemTime::now(), "check_files");
true
}
fn get_file_entry(
@ -405,14 +401,11 @@ impl BrokenFiles {
}
fn look_for_broken_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let system_time = SystemTime::now();
let loaded_hash_map;
let mut records_already_cached: BTreeMap<String, FileEntry> = Default::default();
let mut non_cached_files_to_check: BTreeMap<String, FileEntry> = Default::default();
let mut files_to_check = Default::default();
mem::swap(&mut self.files_to_check, &mut files_to_check);
let files_to_check = mem::take(&mut self.files_to_check);
if self.use_cache {
loaded_hash_map = match load_cache_from_file(&mut self.text_messages, self.delete_outdated_cache) {
@ -442,17 +435,8 @@ impl BrokenFiles {
non_cached_files_to_check = files_to_check;
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
1,
1,
non_cached_files_to_check.len(),
CheckingMethod::None,
);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) =
prepare_thread_handler_common(progress_sender, 1, 1, non_cached_files_to_check.len(), CheckingMethod::None);
let mut vec_file_entry: Vec<FileEntry> = non_cached_files_to_check
.into_par_iter()
@ -479,9 +463,7 @@ impl BrokenFiles {
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
// Just connect loaded results with already calculated
for (_name, file_entry) in records_already_cached {
vec_file_entry.push(file_entry.clone());
}
vec_file_entry.extend(records_already_cached.into_values());
if self.use_cache {
// Must save all results to file, old loaded from file with all currently counted results
@ -503,8 +485,6 @@ impl BrokenFiles {
self.information.number_of_broken_files = self.broken_files.len();
Common::print_time(system_time, SystemTime::now(), "sort_images - reading data from files in parallel");
// Clean unused data
self.files_to_check = Default::default();
@ -513,8 +493,6 @@ impl BrokenFiles {
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for file_entry in &self.broken_files {
@ -527,8 +505,6 @@ impl BrokenFiles {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
@ -569,7 +545,6 @@ impl DebugPrint for BrokenFiles {
impl SaveResults for BrokenFiles {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -601,7 +576,7 @@ impl SaveResults for BrokenFiles {
} else {
write!(writer, "Not found any broken files.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
@ -610,13 +585,10 @@ impl PrintResults for BrokenFiles {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} broken files.\n", self.information.number_of_broken_files);
for file_entry in &self.broken_files {
println!("{} - {}", file_entry.path.display(), file_entry.error_string);
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

View file

@ -22,6 +22,7 @@ use libheif_rs::{ColorSpace, HeifContext, RgbChroma};
use crate::common_dir_traversal::{CheckingMethod, ProgressData};
use crate::common_directory::Directories;
use crate::common_items::ExcludedItems;
use crate::common_traits::ResultEntry;
static NUMBER_OF_THREADS: state::Storage<usize> = state::Storage::new();
@ -364,16 +365,38 @@ pub fn check_folder_children(
dir_result.push(next_folder);
}
#[must_use]
pub fn filter_reference_folders_generic<T>(entries_to_check: Vec<Vec<T>>, directories: &Directories) -> Vec<(T, Vec<T>)>
where
T: ResultEntry,
{
entries_to_check
.into_iter()
.filter_map(|vec_file_entry| {
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
} else {
Some((files_from_referenced_folders.pop().unwrap(), normal_files))
}
})
.collect::<Vec<(T, Vec<T>)>>()
}
#[must_use]
pub fn prepare_thread_handler_common(
progress_sender: Option<&UnboundedSender<ProgressData>>,
progress_thread_run: &Arc<AtomicBool>,
atomic_counter: &Arc<AtomicUsize>,
current_stage: u8,
max_stage: u8,
max_value: usize,
checking_method: CheckingMethod,
) -> JoinHandle<()> {
if let Some(progress_sender) = progress_sender {
) -> (JoinHandle<()>, Arc<AtomicBool>, Arc<AtomicUsize>, AtomicBool) {
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let check_was_stopped = AtomicBool::new(false);
let progress_thread_sender = if let Some(progress_sender) = progress_sender {
let progress_send = progress_sender.clone();
let progress_thread_run = progress_thread_run.clone();
let atomic_counter = atomic_counter.clone();
@ -394,7 +417,8 @@ pub fn prepare_thread_handler_common(
})
} else {
thread::spawn(|| {})
}
};
(progress_thread_sender, progress_thread_run, atomic_counter, check_was_stopped)
}
pub fn send_info_and_wait_for_ending_all_threads(progress_thread_run: &Arc<AtomicBool>, progress_thread_handle: JoinHandle<()>) {

View file

@ -2,9 +2,9 @@ use std::collections::BTreeMap;
use std::fs;
use std::fs::{DirEntry, Metadata, ReadDir};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use std::sync::atomic::Ordering;
use std::time::UNIX_EPOCH;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
@ -14,6 +14,7 @@ use crate::common::{prepare_thread_handler_common, send_info_and_wait_for_ending
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
use crate::common_items::ExcludedItems;
use crate::common_traits::ResultEntry;
use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
@ -33,6 +34,8 @@ pub enum CheckingMethod {
SizeName,
Size,
Hash,
AudioTags,
AudioContent,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
@ -43,6 +46,11 @@ pub struct FileEntry {
pub hash: String,
pub symlink_info: Option<SymlinkInfo>,
}
impl ResultEntry for FileEntry {
fn get_path(&self) -> &Path {
&self.path
}
}
// Symlinks
@ -280,12 +288,10 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> {
pub enum DirTraversalResult<T: Ord + PartialOrd> {
SuccessFiles {
start_time: SystemTime,
warnings: Vec<String>,
grouped_file_entries: BTreeMap<T, Vec<FileEntry>>,
},
SuccessFolders {
start_time: SystemTime,
warnings: Vec<String>,
folder_entries: BTreeMap<PathBuf, FolderEntry>, // Path, FolderEntry
},
@ -314,7 +320,6 @@ where
let mut all_warnings = vec![];
let mut grouped_file_entries: BTreeMap<T, Vec<FileEntry>> = BTreeMap::new();
let mut folder_entries: BTreeMap<PathBuf, FolderEntry> = BTreeMap::new();
let start_time: SystemTime = SystemTime::now();
// Add root folders into result (only for empty folder collection)
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
@ -333,9 +338,8 @@ where
// Add root folders for finding
folders_to_check.extend(self.root_dirs);
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(self.progress_sender, &progress_thread_run, &atomic_counter, 0, self.max_stage, 0, self.checking_method);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) =
prepare_thread_handler_common(self.progress_sender, 0, self.max_stage, 0, self.checking_method);
let DirTraversal {
collect,
@ -468,12 +472,10 @@ where
match collect {
Collect::Files | Collect::InvalidSymlinks => DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings: all_warnings,
},
Collect::EmptyFolders => DirTraversalResult::SuccessFolders {
start_time,
folder_entries,
warnings: all_warnings,
},

View file

@ -1,5 +1,5 @@
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[cfg(target_family = "unix")]
use std::{fs, os::unix::fs::MetadataExt};
@ -30,8 +30,6 @@ impl Directories {
/// Setting included directories, at least one must be provided or scan won't start
pub fn set_included_directory(&mut self, included_directory: Vec<PathBuf>, text_messages: &mut Messages) -> bool {
let start_time: SystemTime = SystemTime::now();
if included_directory.is_empty() {
text_messages.errors.push(flc!("core_missing_no_chosen_included_directory"));
return false;
@ -90,13 +88,11 @@ impl Directories {
self.included_directories = checked_directories;
Common::print_time(start_time, SystemTime::now(), "set_included_directory");
true
}
/// Setting absolute path to exclude from search
pub fn set_excluded_directory(&mut self, excluded_directory: Vec<PathBuf>, text_messages: &mut Messages) {
let start_time: SystemTime = SystemTime::now();
if excluded_directory.is_empty() {
return;
}
@ -148,8 +144,6 @@ impl Directories {
checked_directories.push(directory);
}
self.excluded_directories = checked_directories;
Common::print_time(start_time, SystemTime::now(), "set_excluded_directory");
}
#[cfg(target_family = "unix")]
@ -159,8 +153,6 @@ impl Directories {
/// Remove unused entries when included or excluded overlaps with each other or are duplicated etc.
pub fn optimize_directories(&mut self, recursive_search: bool, text_messages: &mut Messages) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut optimized_included: Vec<PathBuf> = Vec::new();
let mut optimized_excluded: Vec<PathBuf> = Vec::new();
@ -295,7 +287,6 @@ impl Directories {
// Not needed, but better is to have sorted everything
self.excluded_directories.sort_unstable();
self.included_directories.sort_unstable();
Common::print_time(start_time, SystemTime::now(), "optimize_directories");
// Get device IDs for included directories
#[cfg(target_family = "unix")]
@ -314,6 +305,11 @@ impl Directories {
true
}
#[must_use]
pub fn is_in_referenced_directory(&self, path: &Path) -> bool {
self.reference_directories.iter().any(|e| path.starts_with(e))
}
/// Checks whether a specified directory is excluded from searching
pub fn is_excluded(&self, path: impl AsRef<Path>) -> bool {
let path = path.as_ref();

View file

@ -1,6 +1,3 @@
use std::time::SystemTime;
use crate::common::Common;
use crate::common_messages::Messages;
#[derive(Clone, Default)]
@ -16,7 +13,6 @@ impl Extensions {
/// List of allowed extensions, only files with this extensions will be checking if are duplicates
/// After, extensions cannot contains any dot, commas etc.
pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String, text_messages: &mut Messages) {
let start_time: SystemTime = SystemTime::now();
if allowed_extensions.trim().is_empty() {
return;
}
@ -57,7 +53,6 @@ impl Extensions {
.messages
.push("No valid extensions were provided, so allowing all extensions by default.".to_string());
}
Common::print_time(start_time, SystemTime::now(), "set_allowed_extensions");
}
#[must_use]

View file

@ -1,5 +1,4 @@
use std::path::Path;
use std::time::SystemTime;
use crate::common::Common;
use crate::common_messages::Messages;
@ -17,8 +16,6 @@ impl ExcludedItems {
/// Setting excluded items which needs to contains * wildcard
/// Are a lot of slower than absolute path, so it should be used to heavy
pub fn set_excluded_items(&mut self, excluded_items: Vec<String>, text_messages: &mut Messages) {
let start_time: SystemTime = SystemTime::now();
if excluded_items.is_empty() {
return;
}
@ -55,7 +52,6 @@ impl ExcludedItems {
checked_expressions.push(expression);
}
self.items = checked_expressions;
Common::print_time(start_time, SystemTime::now(), "set_excluded_items");
}
/// Checks whether a specified path is excluded from searching

View file

@ -1,3 +1,5 @@
use std::path::Path;
pub trait DebugPrint {
fn debug_print(&self);
}
@ -9,3 +11,7 @@ pub trait SaveResults {
pub trait PrintResults {
fn print_results(&self);
}
pub trait ResultEntry {
fn get_path(&self) -> &Path;
}

View file

@ -9,9 +9,8 @@ use std::io::{BufReader, BufWriter};
#[cfg(target_family = "unix")]
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use std::sync::atomic::Ordering;
use std::{fs, mem};
use crossbeam_channel::Receiver;
@ -21,7 +20,7 @@ use humansize::BINARY;
use rayon::prelude::*;
use xxhash_rust::xxh3::Xxh3;
use crate::common::{open_cache_folder, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, Common};
use crate::common::{open_cache_folder, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads};
use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
@ -182,9 +181,7 @@ impl DuplicateFinder {
return;
}
}
CheckingMethod::None => {
panic!();
}
_ => panic!(),
}
self.delete_files();
self.debug_print();
@ -364,11 +361,7 @@ impl DuplicateFinder {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
self.files_with_identical_names = grouped_file_entries;
self.text_messages.warnings.extend(warnings);
@ -384,21 +377,11 @@ impl DuplicateFinder {
// 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
let vec = mem::take(&mut self.files_with_identical_names)
.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);
}
}
.filter_map(|(_name, vec_file_entry)| {
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
@ -413,7 +396,6 @@ impl DuplicateFinder {
}
self.calculate_name_stats();
Common::print_time(start_time, SystemTime::now(), "check_files_name");
true
}
DirTraversalResult::SuccessFolders { .. } => {
@ -459,11 +441,7 @@ impl DuplicateFinder {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
self.files_with_identical_size_names = grouped_file_entries;
self.text_messages.warnings.extend(warnings);
@ -479,21 +457,11 @@ impl DuplicateFinder {
// Reference - only use in size, because later hash will be counted differently
if self.use_reference_folders {
let mut btree_map = Default::default();
mem::swap(&mut self.files_with_identical_size_names, &mut btree_map);
let reference_directories = self.directories.reference_directories.clone();
let vec = btree_map
let vec = mem::take(&mut self.files_with_identical_size_names)
.into_iter()
.filter_map(|(_size, vec_file_entry)| {
let mut files_from_referenced_folders = Vec::new();
let mut normal_files = Vec::new();
for file_entry in vec_file_entry {
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
files_from_referenced_folders.push(file_entry);
} else {
normal_files.push(file_entry);
}
}
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
@ -509,7 +477,6 @@ impl DuplicateFinder {
}
self.calculate_size_name_stats();
Common::print_time(start_time, SystemTime::now(), "check_files_size_name");
true
}
DirTraversalResult::SuccessFolders { .. } => {
@ -559,17 +526,12 @@ impl DuplicateFinder {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
self.files_with_identical_size = grouped_file_entries;
self.text_messages.warnings.extend(warnings);
// Create new BTreeMap without single size entries(files have not duplicates)
let mut old_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
mem::swap(&mut old_map, &mut self.files_with_identical_size);
let old_map: BTreeMap<u64, Vec<FileEntry>> = mem::take(&mut self.files_with_identical_size);
for (size, vec) in old_map {
if vec.len() <= 1 {
@ -586,7 +548,6 @@ impl DuplicateFinder {
self.filter_reference_folders_by_size();
self.calculate_size_stats();
Common::print_time(start_time, SystemTime::now(), "check_files_size");
true
}
DirTraversalResult::SuccessFolders { .. } => {
@ -616,21 +577,11 @@ impl DuplicateFinder {
/// This is needed, because later reference folders looks for hashes, not size
fn filter_reference_folders_by_size(&mut self) {
if self.use_reference_folders && self.check_method == CheckingMethod::Size {
let mut btree_map = Default::default();
mem::swap(&mut self.files_with_identical_size, &mut btree_map);
let reference_directories = self.directories.reference_directories.clone();
let vec = btree_map
let vec = mem::take(&mut self.files_with_identical_size)
.into_iter()
.filter_map(|(_size, vec_file_entry)| {
let mut files_from_referenced_folders = Vec::new();
let mut normal_files = Vec::new();
for file_entry in vec_file_entry {
if reference_directories.iter().any(|e| file_entry.path.starts_with(e)) {
files_from_referenced_folders.push(file_entry);
} else {
normal_files.push(file_entry);
}
}
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
@ -723,21 +674,9 @@ impl DuplicateFinder {
progress_sender: Option<&UnboundedSender<ProgressData>>,
pre_checked_map: &mut BTreeMap<u64, Vec<FileEntry>>,
) -> Option<()> {
let start_time: SystemTime = SystemTime::now();
let check_type = self.hash_type;
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
1,
2,
self.files_with_identical_size.values().map(Vec::len).sum(),
self.check_method,
);
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 1, 2, self.files_with_identical_size.values().map(Vec::len).sum(), self.check_method);
let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.prehash_load_cache_at_start();
@ -750,11 +689,11 @@ impl DuplicateFinder {
let mut buffer = [0u8; 1024 * 2];
atomic_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed);
for file_entry in vec_file_entry {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
check_was_stopped.store(true, Ordering::Relaxed);
return None;
}
for file_entry in vec_file_entry {
match hash_calculation(&mut buffer, file_entry, &check_type, 0) {
Ok(hash_string) => {
hashmap_with_hash.entry(hash_string.clone()).or_insert_with(Vec::new).push(file_entry.clone());
@ -791,8 +730,6 @@ impl DuplicateFinder {
self.prehash_save_cache_at_exit(loaded_hash_map, &pre_hash_results);
Common::print_time(start_time, SystemTime::now(), "check_files_hash - prehash");
Some(())
}
@ -891,23 +828,10 @@ impl DuplicateFinder {
progress_sender: Option<&UnboundedSender<ProgressData>>,
pre_checked_map: BTreeMap<u64, Vec<FileEntry>>,
) -> Option<()> {
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let check_type = self.hash_type;
let start_time: SystemTime = SystemTime::now();
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
2,
2,
pre_checked_map.values().map(Vec::len).sum(),
self.check_method,
);
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 2, 2, pre_checked_map.values().map(Vec::len).sum(), self.check_method);
///////////////////////////////////////////////////////////////////////////// HASHING START
{
@ -958,30 +882,20 @@ impl DuplicateFinder {
}
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
Some(())
}
fn hash_reference_folders(&mut self) {
// 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
let vec = mem::take(&mut self.files_with_identical_hashes)
.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);
}
}
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
continue;
@ -1045,7 +959,6 @@ impl DuplicateFinder {
/// Function to delete files, from filed before `BTreeMap`
/// Using another function to delete files to avoid duplicates data
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
if self.delete_method == DeleteMethod::None {
return;
}
@ -1073,14 +986,9 @@ impl DuplicateFinder {
let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun);
}
}
CheckingMethod::None => {
//Just do nothing
panic!("Checking method should never be none.");
_ => panic!(),
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
impl Default for DuplicateFinder {
@ -1146,7 +1054,6 @@ impl DebugPrint for DuplicateFinder {
impl SaveResults for DuplicateFinder {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -1270,11 +1177,9 @@ impl SaveResults for DuplicateFinder {
write!(writer, "Not found any duplicates.").unwrap();
}
}
CheckingMethod::None => {
panic!();
_ => panic!(),
}
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
@ -1283,7 +1188,6 @@ impl PrintResults for DuplicateFinder {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
let mut number_of_files: u64 = 0;
let mut number_of_groups: u64 = 0;
@ -1359,12 +1263,9 @@ impl PrintResults for DuplicateFinder {
println!();
}
}
CheckingMethod::None => {
panic!("Checking Method shouldn't be ever set to None");
_ => panic!(),
}
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}
/// Functions to remove slice(vector) of files with provided method

View file

@ -3,12 +3,10 @@ use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::PathBuf;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
use crate::common::Common;
use crate::common_dir_traversal::{DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
@ -141,17 +139,13 @@ impl EmptyFiles {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
if let Some(empty_files) = grouped_file_entries.get(&()) {
self.empty_files = empty_files.clone();
}
self.information.number_of_empty_files = self.empty_files.len();
self.text_messages.warnings.extend(warnings);
Common::print_time(start_time, SystemTime::now(), "check_files_name");
true
}
DirTraversalResult::SuccessFolders { .. } => {
@ -163,8 +157,6 @@ impl EmptyFiles {
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for file_entry in &self.empty_files {
@ -177,8 +169,6 @@ impl EmptyFiles {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
@ -220,7 +210,6 @@ impl DebugPrint for EmptyFiles {
impl SaveResults for EmptyFiles {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -252,7 +241,7 @@ impl SaveResults for EmptyFiles {
} else {
write!(writer, "Not found any empty files.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
@ -261,12 +250,9 @@ impl PrintResults for EmptyFiles {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} empty files.\n", self.information.number_of_empty_files);
for file_entry in &self.empty_files {
println!("{}", file_entry.path.display());
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

View file

@ -3,12 +3,10 @@ use std::fs;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::PathBuf;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
use crate::common::Common;
use crate::common_dir_traversal::{Collect, DirTraversalBuilder, DirTraversalResult, FolderEmptiness, FolderEntry, ProgressData};
use crate::common_directory::Directories;
use crate::common_items::ExcludedItems;
@ -145,11 +143,7 @@ impl EmptyFolder {
DirTraversalResult::SuccessFiles { .. } => {
unreachable!()
}
DirTraversalResult::SuccessFolders {
start_time,
folder_entries,
warnings,
} => {
DirTraversalResult::SuccessFolders { folder_entries, warnings } => {
// We need to set empty folder list
#[allow(unused_mut)] // Used is later by Windows build
for (mut name, folder_entry) in folder_entries {
@ -160,7 +154,6 @@ impl EmptyFolder {
self.text_messages.warnings.extend(warnings);
Common::print_time(start_time, SystemTime::now(), "check_for_empty_folder");
true
}
DirTraversalResult::Stopped => false,
@ -169,7 +162,6 @@ impl EmptyFolder {
/// Deletes earlier found empty folders
fn delete_empty_folders(&mut self) {
let start_time: SystemTime = SystemTime::now();
// Folders may be deleted or require too big privileges
for name in self.empty_folder_list.keys() {
match fs::remove_dir_all(name) {
@ -177,8 +169,6 @@ impl EmptyFolder {
Err(e) => self.text_messages.warnings.push(format!("Failed to remove folder {}, reason {}", name.display(), e)),
};
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
/// Set included dir which needs to be relative, exists etc.
@ -211,7 +201,6 @@ impl DebugPrint for EmptyFolder {
impl SaveResults for EmptyFolder {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -248,7 +237,7 @@ impl SaveResults for EmptyFolder {
} else {
write!(writer, "Not found any empty folders.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}

View file

@ -3,12 +3,10 @@ use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::PathBuf;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
use crate::common::Common;
use crate::common_dir_traversal::{Collect, DirTraversalBuilder, DirTraversalResult, ErrorType, FileEntry, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
@ -139,17 +137,12 @@ impl InvalidSymlinks {
.build()
.run();
match result {
DirTraversalResult::SuccessFiles {
start_time,
grouped_file_entries,
warnings,
} => {
DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => {
if let Some(((), invalid_symlinks)) = grouped_file_entries.into_iter().next() {
self.invalid_symlinks = invalid_symlinks;
}
self.information.number_of_invalid_symlinks = self.invalid_symlinks.len();
self.text_messages.warnings.extend(warnings);
Common::print_time(start_time, SystemTime::now(), "check_files_name");
true
}
DirTraversalResult::SuccessFolders { .. } => unreachable!(),
@ -159,8 +152,6 @@ impl InvalidSymlinks {
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for file_entry in &self.invalid_symlinks {
@ -173,8 +164,6 @@ impl InvalidSymlinks {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
@ -216,7 +205,6 @@ impl DebugPrint for InvalidSymlinks {
impl SaveResults for InvalidSymlinks {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -258,7 +246,6 @@ impl SaveResults for InvalidSymlinks {
} else {
write!(writer, "Not found any invalid symlinks.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
@ -267,7 +254,6 @@ impl PrintResults for InvalidSymlinks {
/// Print information's about duplicated entries
/// Only needed for CLI
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} invalid symlinks.\n", self.information.number_of_invalid_symlinks);
for file_entry in &self.invalid_symlinks {
println!(
@ -280,7 +266,5 @@ impl PrintResults for InvalidSymlinks {
}
);
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

File diff suppressed because it is too large Load diff

View file

@ -23,14 +23,14 @@ use serde::{Deserialize, Serialize};
use crate::common::get_dynamic_image_from_heic;
use crate::common::{
check_folder_children, create_crash_message, get_dynamic_image_from_raw_image, get_number_of_threads, open_cache_folder, prepare_thread_handler_common,
send_info_and_wait_for_ending_all_threads, Common, HEIC_EXTENSIONS, IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS,
send_info_and_wait_for_ending_all_threads, HEIC_EXTENSIONS, IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS,
};
use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
use crate::common_items::ExcludedItems;
use crate::common_messages::Messages;
use crate::common_traits::{DebugPrint, PrintResults, SaveResults};
use crate::common_traits::{DebugPrint, PrintResults, ResultEntry, SaveResults};
use crate::flc;
type ImHash = Vec<u8>;
@ -51,6 +51,11 @@ pub struct FileEntry {
pub hash: ImHash,
pub similarity: u32,
}
impl ResultEntry for FileEntry {
fn get_path(&self) -> &Path {
&self.path
}
}
/// Used by CLI tool when we cannot use directly values
#[derive(Clone, Debug, Copy)]
@ -275,7 +280,6 @@ impl SimilarImages {
/// Function to check if folder are empty.
/// Parameter `initial_checking` for second check before deleting to be sure that checked folder is still empty
fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
if !self.allowed_extensions.using_custom_extensions() {
@ -296,9 +300,7 @@ impl SimilarImages {
folders_to_check.push(id.clone());
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 0, 2, 0, CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 2, 0, CheckingMethod::None);
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -355,7 +357,7 @@ impl SimilarImages {
}
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
Common::print_time(start_time, SystemTime::now(), "check_for_similar_images");
true
}
@ -401,16 +403,18 @@ impl SimilarImages {
};
for (name, file_entry) in &self.images_to_check {
#[allow(clippy::if_same_then_else)]
if !loaded_hash_map.contains_key(name) {
// If loaded data doesn't contains current image info
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
} else if file_entry.size != loaded_hash_map.get(name).unwrap().size || file_entry.modified_date != loaded_hash_map.get(name).unwrap().modified_date {
} else {
let loaded_item = loaded_hash_map.get(name).unwrap();
if file_entry.size != loaded_item.size || file_entry.modified_date != loaded_item.modified_date {
// When size or modification date of image changed, then it is clear that is different image
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
} else {
// Checking may be omitted when already there is entry with same size and modification date
records_already_cached.insert(name.clone(), loaded_hash_map.get(name).unwrap().clone());
records_already_cached.insert(name.clone(), loaded_item.clone());
}
}
}
} else {
@ -422,31 +426,16 @@ impl SimilarImages {
// Cache algorithm:
// - Load data from file
// - Remove from data to search, already loaded entries from cache(size and modified datamust match)
// - Remove from data to search, already loaded entries from cache(size and modified date must match)
// - Check hash of files which doesn't have saved entry
// - Join already read hashes with hashes which were read from file
// - Join all hashes and save it to file
fn hash_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let hash_map_modification = SystemTime::now();
let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.hash_images_load_cache();
Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - reading data from cache and preparing them");
let hash_map_modification = SystemTime::now();
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
1,
2,
non_cached_files_to_check.len(),
CheckingMethod::None,
);
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 1, 2, non_cached_files_to_check.len(), CheckingMethod::None);
let mut vec_file_entry: Vec<(FileEntry, ImHash)> = non_cached_files_to_check
.into_par_iter()
@ -465,11 +454,8 @@ impl SimilarImages {
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - reading data from files in parallel");
let hash_map_modification = SystemTime::now();
// Just connect loaded results with already calculated hashes
for (_name, file_entry) in records_already_cached {
for file_entry in records_already_cached.into_values() {
vec_file_entry.push((file_entry.clone(), file_entry.hash));
}
@ -502,7 +488,6 @@ impl SimilarImages {
return false;
}
Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - saving data to files");
true
}
fn collect_image_file_entry(&self, mut file_entry: FileEntry) -> (FileEntry, ImHash) {
@ -813,14 +798,12 @@ impl SimilarImages {
return true;
}
let hash_map_modification = SystemTime::now();
let tolerance = self.similarity;
// Results
let mut collected_similar_images: HashMap<ImHash, Vec<FileEntry>> = Default::default();
let mut all_hashed_images = Default::default();
mem::swap(&mut all_hashed_images, &mut self.image_hashes);
let all_hashed_images = mem::take(&mut self.image_hashes);
let all_hashes: Vec<_> = all_hashed_images.clone().into_keys().collect();
@ -832,10 +815,8 @@ impl SimilarImages {
}
}
} else {
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 2, 2, all_hashes.len(), CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 2, 2, all_hashes.len(), CheckingMethod::None);
// Don't use hashes with multiple images in bktree, because they will always be master of group and cannot be find by other hashes
@ -874,8 +855,6 @@ impl SimilarImages {
self.check_for_reference_folders();
Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - selecting data from HashMap");
if self.use_reference_folders {
for (_fe, vector) in &self.similar_referenced_vectors {
self.information.number_of_duplicates += vector.len();
@ -888,7 +867,7 @@ impl SimilarImages {
}
}
// Clean unused data
// Clean unused data to save ram
self.image_hashes = Default::default();
self.images_to_check = Default::default();
self.bktree = BKTree::new(Hamming);
@ -898,9 +877,7 @@ impl SimilarImages {
fn exclude_items_with_same_size(&mut self) {
if self.exclude_images_with_same_size {
let mut new_vector = Default::default();
mem::swap(&mut self.similar_vectors, &mut new_vector);
for vec_file_entry in new_vector {
for vec_file_entry in mem::take(&mut self.similar_vectors) {
let mut bt_sizes: BTreeSet<u64> = Default::default();
let mut vec_values = Vec::new();
for file_entry in vec_file_entry {
@ -918,21 +895,11 @@ impl SimilarImages {
fn check_for_reference_folders(&mut self) {
if self.use_reference_folders {
let mut similar_vector = Default::default();
mem::swap(&mut self.similar_vectors, &mut similar_vector);
let reference_directories = self.directories.reference_directories.clone();
self.similar_referenced_vectors = similar_vector
self.similar_referenced_vectors = mem::take(&mut self.similar_vectors)
.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);
}
}
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
@ -1077,7 +1044,6 @@ impl DebugPrint for SimilarImages {
impl SaveResults for SimilarImages {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -1123,7 +1089,6 @@ impl SaveResults for SimilarImages {
write!(writer, "Not found any similar images.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}

View file

@ -4,9 +4,7 @@ use std::io::Write;
use std::io::*;
use std::mem;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use std::sync::atomic::Ordering;
use crossbeam_channel::Receiver;
use ffmpeg_cmdline_utils::FfmpegErrorKind::FfmpegNotFound;
@ -18,14 +16,14 @@ use serde::{Deserialize, Serialize};
use vid_dup_finder_lib::HashCreationErrorKind::DetermineVideo;
use vid_dup_finder_lib::{NormalizedTolerance, VideoHash};
use crate::common::open_cache_folder;
use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, VIDEO_FILES_EXTENSIONS};
use crate::common::{open_cache_folder, Common};
use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData};
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
use crate::common_items::ExcludedItems;
use crate::common_messages::Messages;
use crate::common_traits::{DebugPrint, PrintResults, SaveResults};
use crate::common_traits::{DebugPrint, PrintResults, ResultEntry, SaveResults};
use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
@ -39,6 +37,11 @@ pub struct FileEntry {
pub vhash: VideoHash,
pub error: String,
}
impl ResultEntry for FileEntry {
fn get_path(&self) -> &Path {
&self.path
}
}
/// Distance metric to use with the BK-tree.
struct Hamming;
@ -242,7 +245,6 @@ impl SimilarVideos {
/// Function to check if folder are empty.
/// Parameter `initial_checking` for second check before deleting to be sure that checked folder is still empty
fn check_for_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
if !self.allowed_extensions.using_custom_extensions() {
@ -259,9 +261,7 @@ impl SimilarVideos {
folders_to_check.push(id.clone());
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 0, 1, 0, CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 1, 0, CheckingMethod::None);
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -319,7 +319,7 @@ impl SimilarVideos {
}
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
Common::print_time(start_time, SystemTime::now(), "check_for_similar_videos");
true
}
@ -365,16 +365,18 @@ impl SimilarVideos {
};
for (name, file_entry) in &self.videos_to_check {
#[allow(clippy::if_same_then_else)]
if !loaded_hash_map.contains_key(name) {
// If loaded data doesn't contains current videos info
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
} else if file_entry.size != loaded_hash_map.get(name).unwrap().size || file_entry.modified_date != loaded_hash_map.get(name).unwrap().modified_date {
} else {
let loaded_item = loaded_hash_map.get(name).unwrap();
if file_entry.size != loaded_item.size || file_entry.modified_date != loaded_item.modified_date {
// When size or modification date of video changed, then it is clear that is different video
non_cached_files_to_check.insert(name.clone(), file_entry.clone());
} else {
// Checking may be omitted when already there is entry with same size and modification date
records_already_cached.insert(name.clone(), loaded_hash_map.get(name).unwrap().clone());
records_already_cached.insert(name.clone(), loaded_item.clone());
}
}
}
} else {
@ -385,26 +387,10 @@ impl SimilarVideos {
}
fn sort_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let hash_map_modification = SystemTime::now();
let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache_at_start();
Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - reading data from cache and preparing them");
let hash_map_modification = SystemTime::now();
let check_was_stopped = AtomicBool::new(false); // Used for breaking from GUI and ending check thread
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(
progress_sender,
&progress_thread_run,
&atomic_counter,
1,
1,
non_cached_files_to_check.len(),
CheckingMethod::None,
);
let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) =
prepare_thread_handler_common(progress_sender, 1, 1, non_cached_files_to_check.len(), CheckingMethod::None);
let mut vec_file_entry: Vec<FileEntry> = non_cached_files_to_check
.par_iter()
@ -435,13 +421,8 @@ impl SimilarVideos {
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - reading data from files in parallel");
let hash_map_modification = SystemTime::now();
// Just connect loaded results with already calculated hashes
for (_name, file_entry) in records_already_cached {
vec_file_entry.push(file_entry.clone());
}
vec_file_entry.extend(records_already_cached.into_values());
let mut hashmap_with_file_entries: HashMap<String, FileEntry> = Default::default();
let mut vector_of_hashes: Vec<VideoHash> = Vec::new();
@ -469,9 +450,6 @@ impl SimilarVideos {
return false;
}
Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - saving data to files");
let hash_map_modification = SystemTime::now();
self.match_groups_of_videos(vector_of_hashes, &hashmap_with_file_entries);
self.remove_from_reference_folders();
@ -487,8 +465,6 @@ impl SimilarVideos {
}
}
Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - selecting data from BtreeMap");
// Clean unused data
self.videos_hashes = Default::default();
self.videos_to_check = Default::default();
@ -523,21 +499,11 @@ impl SimilarVideos {
fn remove_from_reference_folders(&mut self) {
if self.use_reference_folders {
let mut similar_vector = Default::default();
mem::swap(&mut self.similar_vectors, &mut similar_vector);
let reference_directories = self.directories.reference_directories.clone();
self.similar_referenced_vectors = similar_vector
self.similar_referenced_vectors = mem::take(&mut self.similar_vectors)
.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);
}
}
let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) =
vec_file_entry.into_iter().partition(|e| self.directories.is_in_referenced_directory(e.get_path()));
if files_from_referenced_folders.is_empty() || normal_files.is_empty() {
None
@ -590,7 +556,6 @@ impl DebugPrint for SimilarVideos {
impl SaveResults for SimilarVideos {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -628,7 +593,6 @@ impl SaveResults for SimilarVideos {
write!(writer, "Not found any similar videos.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}

View file

@ -3,15 +3,14 @@ use std::fs::{DirEntry, File, Metadata};
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::SystemTime;
use crossbeam_channel::Receiver;
use futures::channel::mpsc::UnboundedSender;
use rayon::prelude::*;
use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, Common};
use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads};
use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData};
use crate::common_directory::Directories;
use crate::common_items::ExcludedItems;
@ -143,7 +142,6 @@ impl Temporary {
}
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
// Add root folders for finding
@ -151,9 +149,7 @@ impl Temporary {
folders_to_check.push(id.clone());
}
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle = prepare_thread_handler_common(progress_sender, &progress_thread_run, &atomic_counter, 0, 0, 0, CheckingMethod::None);
let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 0, 0, CheckingMethod::None);
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -214,7 +210,6 @@ impl Temporary {
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
self.information.number_of_temporary_files = self.temporary_files.len();
Common::print_time(start_time, SystemTime::now(), "check_files_size");
true
}
pub fn get_file_entry(
@ -248,8 +243,6 @@ impl Temporary {
/// Function to delete files, from filed Vector
fn delete_files(&mut self) {
let start_time: SystemTime = SystemTime::now();
match self.delete_method {
DeleteMethod::Delete => {
for file_entry in &self.temporary_files {
@ -262,8 +255,6 @@ impl Temporary {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files");
}
}
@ -304,7 +295,6 @@ impl DebugPrint for Temporary {
impl SaveResults for Temporary {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
let start_time: SystemTime = SystemTime::now();
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
@ -336,19 +326,16 @@ impl SaveResults for Temporary {
} else {
write!(writer, "Not found any temporary files.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file");
true
}
}
impl PrintResults for Temporary {
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
println!("Found {} temporary files.\n", self.information.number_of_temporary_files);
for file_entry in &self.temporary_files {
println!("{}", file_entry.path.display());
}
Common::print_time(start_time, SystemTime::now(), "print_entries");
}
}

View file

@ -16,6 +16,23 @@ music_bitrate_checkbox = Bitrate
music_genre_checkbox = Genre
music_length_checkbox = Length
music_comparison_checkbox = Approximate Comparison
music_checking_by_tags = Tags
music_checking_by_content = Content
same_music_seconds_label = Minimal fragment second duration
same_music_similarity_label = Maximum difference
same_music_tooltip =
Searching for similar music files by its content can be configured by setting:
- The minimum fragment time after which music files can be identified as similar
- The maximum difference difference between two tested fragments
The key to good results is to find sensible combinations of these parameters, for provided.
Setting the minimum time to 5s and the maximum difference to 1.0, will look for almost identical fragments in the files.
A time of 20s and a maximum difference of 6.0, on the other hand, works well for finding remixes/live versions etc.
By default, each music file is compared to each other and this can take a lot of time when testing many files, so it is usually better to use reference folders and specifying which files are to be compared with each other(with same amount of files, comparing fingerprints will be faster at least 4x than without reference folders).
music_comparison_checkbox_tooltip =
It searches for similar music files using AI, which uses machine learning to remove parentheses from a phrase. For example, with this option enabled, the files in question will be considered duplicates:
@ -446,6 +463,8 @@ progress_scanning_image = Hashing of {$file_checked}/{$all_files} image
progress_comparing_image_hashes = Comparing {$file_checked}/{$all_files} image hash
progress_scanning_music_tags_end = Comparing tags of {$file_checked}/{$all_files} music file
progress_scanning_music_tags = Reading tags of {$file_checked}/{$all_files} music file
progress_scanning_music_content_end = Comparing fingerprint of {$file_checked}/{$all_files} music file
progress_scanning_music_content = Calculating fingerprint of {$file_checked}/{$all_files} music file
progress_scanning_empty_folders = Scanning {$folder_number} folder
progress_scanning_size = Scanning size of {$file_number} file
progress_scanning_size_name = Scanning name and size of {$file_number} file

View file

@ -550,7 +550,7 @@ fn computer_same_music(
} else {
let vector = mf.get_duplicated_music_entries();
let text: &str = "-----";
let text: &str = if mf.get_check_type() == CheckingMethod::AudioTags { "-----" } else { "" };
for vec_file_entry in vector {
// Sort
@ -1164,9 +1164,7 @@ fn computer_duplicate_finder(
duplicates_size = information.lost_space_by_size;
duplicates_group = information.number_of_groups_by_size_name;
}
CheckingMethod::None => {
panic!();
}
_ => panic!(),
}
if duplicates_size == 0 {
entry_info.set_text(
@ -1251,9 +1249,7 @@ fn computer_duplicate_finder(
}
}
}
CheckingMethod::None => {
panic!();
}
_ => panic!(),
}
} else {
match df.get_check_method() {
@ -1310,9 +1306,7 @@ fn computer_duplicate_finder(
}
}
}
CheckingMethod::None => {
panic!();
}
_ => panic!(),
}
}
print_text_messages_to_text_view(text_messages, text_view_errors);

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,14 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use futures::channel::mpsc::UnboundedReceiver;
use futures::StreamExt;
use glib::MainContext;
use gtk4::prelude::*;
use gtk4::ProgressBar;
use common_dir_traversal::CheckingMethod;
use czkawka_core::common_dir_traversal;
use czkawka_core::common_dir_traversal::ProgressData;
@ -9,151 +16,50 @@ use crate::flg;
use crate::gui_structs::gui_data::GuiData;
use crate::localizer_core::generate_translation_hashmap;
use crate::taskbar_progress::tbp_flags::TBPF_INDETERMINATE;
use crate::taskbar_progress::TaskbarProgress;
#[allow(clippy::too_many_arguments)]
pub fn connect_progress_window(
gui_data: &GuiData,
mut futures_receiver_duplicate_files: UnboundedReceiver<ProgressData>,
mut futures_receiver_empty_files: UnboundedReceiver<ProgressData>,
mut futures_receiver_empty_folder: UnboundedReceiver<ProgressData>,
mut futures_receiver_big_files: UnboundedReceiver<ProgressData>,
mut futures_receiver_same_music: UnboundedReceiver<ProgressData>,
mut futures_receiver_similar_images: UnboundedReceiver<ProgressData>,
mut futures_receiver_similar_videos: UnboundedReceiver<ProgressData>,
mut futures_receiver_temporary: UnboundedReceiver<ProgressData>,
mut futures_receiver_invalid_symlinks: UnboundedReceiver<ProgressData>,
mut futures_receiver_broken_files: UnboundedReceiver<ProgressData>,
mut futures_receiver_bad_extensions: UnboundedReceiver<ProgressData>,
futures_receiver_duplicate_files: UnboundedReceiver<ProgressData>,
futures_receiver_empty_files: UnboundedReceiver<ProgressData>,
futures_receiver_empty_folder: UnboundedReceiver<ProgressData>,
futures_receiver_big_files: UnboundedReceiver<ProgressData>,
futures_receiver_same_music: UnboundedReceiver<ProgressData>,
futures_receiver_similar_images: UnboundedReceiver<ProgressData>,
futures_receiver_similar_videos: UnboundedReceiver<ProgressData>,
futures_receiver_temporary: UnboundedReceiver<ProgressData>,
futures_receiver_invalid_symlinks: UnboundedReceiver<ProgressData>,
futures_receiver_broken_files: UnboundedReceiver<ProgressData>,
futures_receiver_bad_extensions: UnboundedReceiver<ProgressData>,
) {
let main_context = glib::MainContext::default();
let main_context = MainContext::default();
let _guard = main_context.acquire().unwrap();
{
// Duplicate Files
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_duplicate_files.next().await {
match item.checking_method {
common_dir_traversal::CheckingMethod::Hash => {
label_stage.show();
match item.current_stage {
// Checking Size
0 => {
progress_bar_current_stage.hide();
// progress_bar_all_stages.hide();
progress_bar_all_stages.set_fraction(0 as f64);
label_stage.set_text(&flg!(
"progress_scanning_size",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
// Hash - first 1KB file
1 => {
progress_bar_current_stage.show();
// progress_bar_all_stages.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, 1 + item.max_stage as u64);
}
label_stage.set_text(&flg!(
"progress_analyzed_partial_hash",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
// Hash - normal hash
2 => {
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((2f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(2 * item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((2f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(2, 1 + item.max_stage as u64);
}
label_stage.set_text(&flg!(
"progress_analyzed_full_hash",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!("Not available current_stage");
}
}
}
common_dir_traversal::CheckingMethod::Name => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!(
"progress_scanning_name",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
common_dir_traversal::CheckingMethod::SizeName => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!(
"progress_scanning_size_name",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
common_dir_traversal::CheckingMethod::Size => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!(
"progress_scanning_size",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
common_dir_traversal::CheckingMethod::None => {
panic!();
}
};
}
};
main_context.spawn_local(future);
}
{
// Empty Files
process_bar_duplicates(gui_data, &main_context, futures_receiver_duplicate_files);
process_bar_empty_files(gui_data, &main_context, futures_receiver_empty_files);
process_bar_empty_folder(gui_data, &main_context, futures_receiver_empty_folder);
process_bar_big_files(gui_data, &main_context, futures_receiver_big_files);
process_bar_same_music(gui_data, &main_context, futures_receiver_same_music);
process_bar_similar_images(gui_data, &main_context, futures_receiver_similar_images);
process_bar_similar_videos(gui_data, &main_context, futures_receiver_similar_videos);
process_bar_temporary(gui_data, &main_context, futures_receiver_temporary);
process_bar_invalid_symlinks(gui_data, &main_context, futures_receiver_invalid_symlinks);
process_bar_broken_files(gui_data, &main_context, futures_receiver_broken_files);
process_bar_bad_extensions(gui_data, &main_context, futures_receiver_bad_extensions);
}
fn process_bar_empty_files(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_empty_files: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_empty_files.next().await {
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
};
main_context.spawn_local(future);
}
{
// Empty Folder
}
fn process_bar_empty_folder(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_empty_folder: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
@ -166,24 +72,19 @@ pub fn connect_progress_window(
}
};
main_context.spawn_local(future);
}
{
// Big Files
}
fn process_bar_big_files(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_big_files: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_big_files.next().await {
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
};
main_context.spawn_local(future);
}
{
// Same Music
}
fn process_bar_same_music(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_same_music: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
@ -193,59 +94,43 @@ pub fn connect_progress_window(
match item.current_stage {
0 => {
progress_bar_current_stage.hide();
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
1 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64);
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
match item.checking_method {
CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(&item))),
CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content", progress_ratio_tm(&item))),
_ => panic!(),
}
label_stage.set_text(&flg!(
"progress_scanning_music_tags",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
2 => {
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((2f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(2 * item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((2f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(2, (item.max_stage + 1) as u64);
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
match item.checking_method {
CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags_end", progress_ratio_tm(&item))),
CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content_end", progress_ratio_tm(&item))),
_ => panic!(),
}
label_stage.set_text(&flg!(
"progress_scanning_music_tags_end",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!();
3 => {
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
match item.checking_method {
CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(&item))),
_ => panic!(),
}
}
_ => panic!(),
}
}
};
main_context.spawn_local(future);
}
{
// Similar Images
}
fn process_bar_similar_images(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_similar_images: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
@ -255,60 +140,26 @@ pub fn connect_progress_window(
match item.current_stage {
0 => {
progress_bar_current_stage.hide();
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
1 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((item.current_stage as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64);
}
label_stage.set_text(&flg!(
"progress_scanning_image",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_scanning_image", progress_ratio_tm(&item)));
}
2 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((2f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((item.current_stage as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(2, (item.max_stage + 1) as u64);
}
label_stage.set_text(&flg!(
"progress_comparing_image_hashes",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!();
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_comparing_image_hashes", progress_ratio_tm(&item)));
}
_ => panic!(),
}
}
};
main_context.spawn_local(future);
}
{
// Similar Videos
}
fn process_bar_similar_videos(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_similar_videos: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
@ -318,71 +169,43 @@ pub fn connect_progress_window(
match item.current_stage {
0 => {
progress_bar_current_stage.hide();
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
1 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64);
}
label_stage.set_text(&flg!(
"progress_scanning_video",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!();
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_scanning_video", progress_ratio_tm(&item)));
}
_ => panic!(),
}
}
};
main_context.spawn_local(future);
}
{
// Temporary
}
fn process_bar_temporary(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_temporary: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_temporary.next().await {
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
};
main_context.spawn_local(future);
}
{
// Invalid Symlinks
}
fn process_bar_invalid_symlinks(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_invalid_symlinks: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_invalid_symlinks.next().await {
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
};
main_context.spawn_local(future);
}
{
// Broken Files
}
fn process_bar_broken_files(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_broken_files: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
@ -392,41 +215,21 @@ pub fn connect_progress_window(
match item.current_stage {
0 => {
progress_bar_current_stage.hide();
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
1 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64);
}
label_stage.set_text(&flg!(
"progress_scanning_broken_files",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!();
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_scanning_broken_files", progress_ratio_tm(&item)));
}
_ => panic!(),
}
}
};
main_context.spawn_local(future);
}
{
// Broken Files
}
fn process_bar_bad_extensions(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_bad_extensions: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
@ -436,37 +239,104 @@ pub fn connect_progress_window(
match item.current_stage {
0 => {
progress_bar_current_stage.hide();
label_stage.set_text(&flg!(
"progress_scanning_general_file",
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
));
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
1 => {
progress_bar_current_stage.show();
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((1f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
(item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64);
}
label_stage.set_text(&flg!(
"progress_scanning_extension_of_files",
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
));
}
_ => {
panic!();
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_scanning_extension_of_files", progress_ratio_tm(&item)));
}
_ => panic!(),
}
}
};
main_context.spawn_local(future);
}
fn process_bar_duplicates(gui_data: &GuiData, main_context: &MainContext, mut futures_receiver_duplicate_files: UnboundedReceiver<ProgressData>) {
let label_stage = gui_data.progress_window.label_stage.clone();
let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone();
let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone();
let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let future = async move {
while let Some(item) = futures_receiver_duplicate_files.next().await {
match item.checking_method {
CheckingMethod::Hash => {
label_stage.show();
match item.current_stage {
// Checking Size
0 => {
progress_bar_current_stage.hide();
// progress_bar_all_stages.hide();
progress_bar_all_stages.set_fraction(0 as f64);
label_stage.set_text(&flg!("progress_scanning_size", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
// Hash - first 1KB file
1 => {
progress_bar_current_stage.show();
// progress_bar_all_stages.show();
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_analyzed_partial_hash", progress_ratio_tm(&item)));
}
// Hash - normal hash
2 => {
common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state);
label_stage.set_text(&flg!("progress_analyzed_full_hash", progress_ratio_tm(&item)));
}
_ => {
panic!("Not available current_stage");
}
}
}
CheckingMethod::Name => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!("progress_scanning_name", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
CheckingMethod::SizeName => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!("progress_scanning_size_name", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
CheckingMethod::Size => {
label_stage.show();
grid_progress_stages.hide();
label_stage.set_text(&flg!("progress_scanning_size", file_number_tm(&item)));
taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE);
}
_ => panic!(),
};
}
};
main_context.spawn_local(future);
}
fn common_set_data(item: &ProgressData, progress_bar_all_stages: &ProgressBar, progress_bar_current_stage: &ProgressBar, taskbar_state: &Rc<RefCell<TaskbarProgress>>) {
if item.entries_to_check != 0 {
progress_bar_all_stages.set_fraction((item.current_stage as f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
taskbar_state.borrow().set_progress_value(
((item.current_stage as usize) * item.entries_to_check + item.entries_checked) as u64,
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
);
} else {
progress_bar_all_stages.set_fraction((item.current_stage as f64) / (item.max_stage + 1) as f64);
progress_bar_current_stage.set_fraction(0f64);
taskbar_state.borrow().set_progress_value(item.current_stage as u64, 1 + item.max_stage as u64);
}
}
fn file_number_tm(item: &ProgressData) -> HashMap<&'static str, String> {
generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())])
}
fn progress_ratio_tm(item: &ProgressData) -> HashMap<&'static str, String> {
generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())])
}

View file

@ -0,0 +1,77 @@
use gtk4::prelude::*;
use gtk4::{CheckButton, Widget};
use czkawka_core::common_dir_traversal::CheckingMethod;
use crate::gui_structs::gui_data::GuiData;
use crate::help_combo_box::AUDIO_TYPE_CHECK_METHOD_COMBO_BOX;
use crate::help_functions::scale_set_min_max_values;
const MINIMUM_SECONDS: f64 = 0.5;
const MAXIMUM_SECONDS: f64 = 180.0;
const DEFAULT_SECONDS: f64 = 15.0;
const MINIMUM_SIMILARITY: f64 = 0.0;
const MAXIMUM_SIMILARITY: f64 = 10.0;
const DEFAULT_SIMILARITY: f64 = 5.0;
pub fn connect_same_music_change_mode(gui_data: &GuiData) {
let check_button_music_title = gui_data.main_notebook.check_button_music_title.clone();
let check_button_music_approximate_comparison = gui_data.main_notebook.check_button_music_approximate_comparison.clone();
let check_button_music_bitrate = gui_data.main_notebook.check_button_music_bitrate.clone();
let check_button_music_artist = gui_data.main_notebook.check_button_music_artist.clone();
let check_button_music_genre = gui_data.main_notebook.check_button_music_genre.clone();
let check_button_music_length = gui_data.main_notebook.check_button_music_length.clone();
let check_button_music_year = gui_data.main_notebook.check_button_music_year.clone();
let buttons = [
check_button_music_title,
check_button_music_approximate_comparison,
check_button_music_bitrate,
check_button_music_artist,
check_button_music_genre,
check_button_music_year,
check_button_music_length,
];
let scale_seconds_same_music = gui_data.main_notebook.scale_seconds_same_music.clone();
let scale_similarity_same_music = gui_data.main_notebook.scale_similarity_same_music.clone();
let label_same_music_similarity = gui_data.main_notebook.label_same_music_similarity.clone();
let label_same_music_seconds = gui_data.main_notebook.label_same_music_seconds.clone();
scale_set_min_max_values(&scale_seconds_same_music, MINIMUM_SECONDS, MAXIMUM_SECONDS, DEFAULT_SECONDS, None);
scale_set_min_max_values(&scale_similarity_same_music, MINIMUM_SIMILARITY, MAXIMUM_SIMILARITY, DEFAULT_SIMILARITY, None);
let scales_and_labels = [
scale_seconds_same_music.into(),
scale_similarity_same_music.into(),
label_same_music_similarity.into(),
label_same_music_seconds.into(),
];
let combo_box_audio_check_type = gui_data.main_notebook.combo_box_audio_check_type.clone();
let check_method_index = combo_box_audio_check_type.active().unwrap() as usize;
let check_method = AUDIO_TYPE_CHECK_METHOD_COMBO_BOX[check_method_index].check_method;
disable_enable_buttons(&buttons, &scales_and_labels, check_method);
combo_box_audio_check_type.connect_changed(move |combo_box_text| {
if let Some(active) = combo_box_text.active() {
let check_method = AUDIO_TYPE_CHECK_METHOD_COMBO_BOX[active as usize].check_method;
disable_enable_buttons(&buttons, &scales_and_labels, check_method);
}
});
}
fn disable_enable_buttons(buttons: &[CheckButton; 7], scales: &[Widget; 4], current_mode: CheckingMethod) {
match current_mode {
CheckingMethod::AudioTags => {
buttons.iter().for_each(WidgetExt::show);
scales.iter().for_each(WidgetExt::hide);
}
CheckingMethod::AudioContent => {
buttons.iter().for_each(WidgetExt::hide);
scales.iter().for_each(WidgetExt::show);
}
_ => panic!(),
}
}

View file

@ -15,6 +15,7 @@ pub mod connect_notebook_tabs;
pub mod connect_popovers_select;
pub mod connect_popovers_sort;
pub mod connect_progress_window;
pub mod connect_same_music_mode_changed;
pub mod connect_selection_of_directories;
pub mod connect_settings;
pub mod connect_show_hide_ui;

View file

@ -7,7 +7,7 @@ use czkawka_core::localizer_core::{fnc_get_similarity_minimal, fnc_get_similarit
use czkawka_core::similar_images::{get_string_from_similarity, SIMILAR_VALUES};
use crate::flg;
use crate::help_combo_box::{BIG_FILES_CHECK_METHOD_COMBO_BOX, DUPLICATES_CHECK_METHOD_COMBO_BOX, IMAGES_HASH_SIZE_COMBO_BOX};
use crate::help_combo_box::{AUDIO_TYPE_CHECK_METHOD_COMBO_BOX, BIG_FILES_CHECK_METHOD_COMBO_BOX, DUPLICATES_CHECK_METHOD_COMBO_BOX, IMAGES_HASH_SIZE_COMBO_BOX};
use crate::help_functions::get_all_direct_children;
use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS};
@ -121,6 +121,12 @@ pub struct GuiMainNotebook {
pub check_button_music_genre: CheckButton,
pub check_button_music_length: CheckButton,
pub check_button_music_approximate_comparison: CheckButton,
pub label_audio_check_type: Label,
pub combo_box_audio_check_type: ComboBoxText,
pub label_same_music_seconds: Label,
pub label_same_music_similarity: Label,
pub scale_seconds_same_music: Scale,
pub scale_similarity_same_music: Scale,
}
impl GuiMainNotebook {
@ -247,6 +253,13 @@ impl GuiMainNotebook {
let image_preview_similar_images: Image = builder.object("image_preview_similar_images").unwrap();
let image_preview_duplicates: Image = builder.object("image_preview_duplicates").unwrap();
let label_audio_check_type: Label = builder.object("label_audio_check_type").unwrap();
let combo_box_audio_check_type: ComboBoxText = builder.object("combo_box_audio_check_type").unwrap();
let label_same_music_seconds: Label = builder.object("label_same_music_seconds").unwrap();
let label_same_music_similarity: Label = builder.object("label_same_music_similarity").unwrap();
let scale_seconds_same_music: Scale = builder.object("scale_seconds_same_music").unwrap();
let scale_similarity_same_music: Scale = builder.object("scale_similarity_same_music").unwrap();
Self {
notebook_main,
scrolled_window_duplicate_finder,
@ -289,6 +302,7 @@ impl GuiMainNotebook {
check_button_music_genre,
check_button_music_length,
check_button_music_approximate_comparison,
label_audio_check_type,
scale_similarity_similar_images,
scale_similarity_similar_videos,
check_button_broken_files_audio,
@ -331,6 +345,11 @@ impl GuiMainNotebook {
combo_box_big_files_mode,
label_big_files_mode,
check_button_broken_files_image,
combo_box_audio_check_type,
label_same_music_seconds,
label_same_music_similarity,
scale_seconds_same_music,
scale_similarity_same_music,
}
}
@ -407,6 +426,13 @@ impl GuiMainNotebook {
self.check_button_broken_files_image.set_label(Some(&flg!("main_check_box_broken_files_image")));
self.check_button_broken_files_pdf.set_label(Some(&flg!("main_check_box_broken_files_pdf")));
self.label_same_music_seconds.set_label(&flg!("same_music_seconds_label"));
self.label_same_music_similarity.set_label(&flg!("same_music_similarity_label"));
self.label_same_music_seconds.set_tooltip_text(Some(&flg!("same_music_tooltip")));
self.label_same_music_similarity.set_tooltip_text(Some(&flg!("same_music_tooltip")));
self.scale_seconds_same_music.set_tooltip_text(Some(&flg!("same_music_tooltip")));
self.scale_similarity_similar_videos.set_tooltip_text(Some(&flg!("same_music_tooltip")));
{
let hash_size_index = self.combo_box_image_hash_size.active().unwrap() as usize;
let hash_size = IMAGES_HASH_SIZE_COMBO_BOX[hash_size_index];
@ -541,6 +567,19 @@ impl GuiMainNotebook {
}
}
{
let active = self.combo_box_audio_check_type.active().unwrap_or(0);
self.combo_box_audio_check_type.remove_all();
for i in &AUDIO_TYPE_CHECK_METHOD_COMBO_BOX {
let text = match i.check_method {
CheckingMethod::AudioTags => flg!("music_checking_by_tags"),
CheckingMethod::AudioContent => flg!("music_checking_by_content"),
_ => panic!(),
};
self.combo_box_audio_check_type.append_text(&text);
}
self.combo_box_audio_check_type.set_active(Some(active));
}
{
let active = self.combo_box_duplicate_check_method.active().unwrap_or(0);
self.combo_box_duplicate_check_method.remove_all();
@ -550,7 +589,7 @@ impl GuiMainNotebook {
CheckingMethod::Size => flg!("duplicate_mode_size_combo_box"),
CheckingMethod::Name => flg!("duplicate_mode_name_combo_box"),
CheckingMethod::SizeName => flg!("duplicate_mode_size_name_combo_box"),
CheckingMethod::None => panic!(),
_ => panic!(),
};
self.combo_box_duplicate_check_method.append_text(&text);
}

View file

@ -48,6 +48,23 @@ pub const DUPLICATES_CHECK_METHOD_COMBO_BOX: [CheckMethodStruct; 4] = [
},
];
#[derive(Copy, Clone)]
pub struct AudioTypeStruct {
pub eng_name: &'static str,
pub check_method: CheckingMethod,
}
pub const AUDIO_TYPE_CHECK_METHOD_COMBO_BOX: [AudioTypeStruct; 2] = [
AudioTypeStruct {
eng_name: "Tags",
check_method: CheckingMethod::AudioTags,
},
AudioTypeStruct {
eng_name: "Content",
check_method: CheckingMethod::AudioContent,
},
];
#[derive(Copy, Clone)]
pub struct SearchModeStruct {
pub eng_name: &'static str,

View file

@ -7,7 +7,7 @@ use gdk4::gdk_pixbuf::{InterpType, Pixbuf};
use glib::signal::Inhibit;
use glib::Error;
use gtk4::prelude::*;
use gtk4::{ListStore, ScrollType, TextView, TreeView, Widget};
use gtk4::{ListStore, Scale, ScrollType, TextView, TreeView, Widget};
use image::codecs::jpeg::JpegEncoder;
use image::{DynamicImage, EncodableLayout};
use once_cell::sync::OnceCell;
@ -770,7 +770,16 @@ pub fn check_if_list_store_column_have_all_same_values(list_store: &ListStore, c
false
}
pub fn scale_step_function(scale: &gtk4::Scale, _scroll_type: ScrollType, value: f64) -> Inhibit {
pub fn scale_set_min_max_values(scale: &Scale, minimum: f64, maximum: f64, current_value: f64, step: Option<f64>) {
scale.set_range(minimum, maximum);
scale.set_fill_level(maximum);
scale.set_value(current_value);
if let Some(step) = step {
scale.adjustment().set_step_increment(step);
}
}
pub fn scale_step_function(scale: &Scale, _scroll_type: ScrollType, value: f64) -> Inhibit {
scale.set_increments(1_f64, 1_f64);
scale.set_round_digits(0);
scale.set_fill_level(value.round());

View file

@ -42,6 +42,7 @@ use crate::compute_results::*;
use crate::connect_things::connect_button_sort::connect_button_sort;
use crate::connect_things::connect_popovers_select::connect_popover_select;
use crate::connect_things::connect_popovers_sort::connect_popover_sort;
use crate::connect_things::connect_same_music_mode_changed::connect_same_music_change_mode;
use crate::initialize_gui::*;
use crate::language_functions::LANGUAGES_ALL;
use crate::saving_loading::*;
@ -163,6 +164,7 @@ fn build_ui(application: &Application, arguments: &[OsString]) {
connect_button_about(&gui_data);
connect_about_buttons(&gui_data);
connect_similar_image_size_change(&gui_data);
connect_same_music_change_mode(&gui_data);
let window_main = gui_data.window_main.clone();
let taskbar_state = gui_data.taskbar_state.clone();

View file

@ -237,6 +237,10 @@
(5,232,"GtkLabel","label_buttons_sort",230,None,None,None,1),
(5,234,"GtkLabel","label_audio_check_type",124,None,None,None,None),
(5,235,"GtkComboBoxText","combo_box_audio_check_type",124,None,None,None,1),
(5,236,"GtkScale","scale_seconds_same_music",117,None,None,None,7),
(5,237,"GtkScale","scale_similarity_same_music",117,None,None,None,9),
(5,238,"GtkLabel","label_same_music_seconds",117,None,None,None,6),
(5,239,"GtkLabel","label_same_music_similarity",117,None,None,None,8),
(6,1,"GtkPopover","popover_right_click",None,None,None,None,None),
(6,2,"GtkBox",None,1,None,None,None,None),
(6,3,"GtkButton","buttons_popover_right_click_open_file",2,None,None,None,None),
@ -769,6 +773,26 @@
(5,232,"GtkLabel","label","SortMenu",None,None,None,None,None),
(5,234,"GtkLabel","label","Audio check type",None,None,None,None,None),
(5,234,"GtkWidget","margin-end","2",None,None,None,None,None),
(5,236,"GtkRange","fill-level","100",None,None,None,None,None),
(5,236,"GtkRange","round-digits","1",None,None,None,None,None),
(5,236,"GtkScale","digits","0",None,None,None,None,None),
(5,236,"GtkScale","draw-value","1",None,None,None,None,None),
(5,236,"GtkScale","value-pos","right",None,None,None,None,None),
(5,236,"GtkWidget","focusable","1",None,None,None,None,None),
(5,236,"GtkWidget","hexpand","1",None,None,None,None,None),
(5,237,"GtkRange","fill-level","100",None,None,None,None,None),
(5,237,"GtkRange","round-digits","1",None,None,None,None,None),
(5,237,"GtkScale","digits","0",None,None,None,None,None),
(5,237,"GtkScale","draw-value","1",None,None,None,None,None),
(5,237,"GtkScale","value-pos","right",None,None,None,None,None),
(5,237,"GtkWidget","focusable","1",None,None,None,None,None),
(5,237,"GtkWidget","hexpand","1",None,None,None,None,None),
(5,238,"GtkLabel","label","Minimal fragment second duration",None,None,None,None,None),
(5,238,"GtkWidget","margin-end","5",None,None,None,None,None),
(5,238,"GtkWidget","margin-start","5",None,None,None,None,None),
(5,239,"GtkLabel","label","Max difference",None,None,None,None,None),
(5,239,"GtkWidget","margin-end","5",None,None,None,None,None),
(5,239,"GtkWidget","margin-start","5",None,None,None,None,None),
(6,1,"GtkPopover","child",None,None,None,None,None,2),
(6,1,"GtkPopover","position","left",None,None,None,None,None),
(6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None),
@ -991,6 +1015,7 @@
(9,58,"GtkWidget","focusable","1",None,None,None,None,None),
(9,58,"GtkWidget","hexpand","1",None,None,None,None,None),
(9,59,"GtkAccessible","accessible-role","menu-item-checkbox",None,None,None,None,None),
(9,59,"GtkLabel","label","Restart Required",None,None,None,None,None),
(9,59,"GtkWidget","margin-bottom","4",None,None,None,None,None),
(9,59,"GtkWidget","margin-top","5",None,None,None,None,None),
(10,1,"GtkPopover","child",None,None,None,None,None,2),

View file

@ -721,6 +721,42 @@
<property name="label">Length</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_same_music_seconds">
<property name="label">Minimal fragment second duration</property>
<property name="margin-end">5</property>
<property name="margin-start">5</property>
</object>
</child>
<child>
<object class="GtkScale" id="scale_seconds_same_music">
<property name="digits">0</property>
<property name="draw-value">1</property>
<property name="fill-level">100</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="round-digits">1</property>
<property name="value-pos">right</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_same_music_similarity">
<property name="label">Max difference</property>
<property name="margin-end">5</property>
<property name="margin-start">5</property>
</object>
</child>
<child>
<object class="GtkScale" id="scale_similarity_same_music">
<property name="digits">0</property>
<property name="draw-value">1</property>
<property name="fill-level">100</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="round-digits">1</property>
<property name="value-pos">right</property>
</object>
</child>
</object>
</child>
<child>

View file

@ -142,6 +142,7 @@
<child>
<object class="GtkLabel" id="label_restart_needed">
<property name="accessible-role">menu-item-checkbox</property>
<property name="label">Restart Required</property>
<property name="margin-bottom">4</property>
<property name="margin-top">5</property>
</object>