Add option to not remove outdated cache entries (#472)

* Add option to not remove outdated cache entries

* Default duplicates cache size lowered to 512 KB

* Add some tooltips,
Add logic to opening cache/config folders

* Add option to clear cache files from outdated results(manually)
pull/473/head
Rafał Mikrut 3 years ago committed by GitHub
parent f2333b9df2
commit 4871016a3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,7 +22,7 @@ pub enum Commands {
minimal_file_size: u64,
#[structopt(short = "i", long, parse(try_from_str = parse_maximal_file_size), default_value = "18446744073709551615", help = "Maximum size in bytes", long_help = "Maximum size of checked files in bytes, assigning lower value may speed up searching")]
maximal_file_size: u64,
#[structopt(short = "c", long, parse(try_from_str = parse_minimal_file_size), default_value = "2097152", help = "Minimum cached file size in bytes", long_help = "Minimum size of cached files in bytes, assigning bigger value may speed up will cause that lower amount of files will be cached, but loading of cache will be faster")]
#[structopt(short = "c", long, parse(try_from_str = parse_minimal_file_size), default_value = "524288", help = "Minimum cached file size in bytes", long_help = "Minimum size of cached files in bytes, assigning bigger value may speed up will cause that lower amount of files will be cached, but loading of cache will be faster")]
minimal_cached_file_size: u64,
#[structopt(flatten)]
allowed_extensions: AllowedExtensions,

@ -10,30 +10,37 @@ impl Messages {
Default::default()
}
pub fn print_messages(&self) {
println!("{}", self.create_messages_text());
}
pub fn create_messages_text(&self) -> String {
let mut text_to_return: String = "".to_string();
if !self.messages.is_empty() {
println!("-------------------------------MESSAGES--------------------------------");
text_to_return += "-------------------------------MESSAGES--------------------------------\n";
for i in &self.messages {
println!("{}", i);
text_to_return += format!("{}\n", i).as_str();
}
println!("---------------------------END OF MESSAGES-----------------------------");
text_to_return += "---------------------------END OF MESSAGES-----------------------------\n";
}
if !self.warnings.is_empty() {
println!("-------------------------------WARNINGS--------------------------------");
text_to_return += "-------------------------------WARNINGS--------------------------------\n";
for i in &self.warnings {
println!("{}", i);
text_to_return += format!("{}\n", i).as_str();
}
println!("---------------------------END OF WARNINGS-----------------------------");
text_to_return += "---------------------------END OF WARNINGS-----------------------------\n";
}
if !self.errors.is_empty() {
println!("--------------------------------ERRORS---------------------------------");
text_to_return += "--------------------------------ERRORS---------------------------------\n";
for i in &self.errors {
println!("{}", i);
text_to_return += format!("{}\n", i).as_str();
}
println!("----------------------------END OF ERRORS------------------------------");
text_to_return += "----------------------------END OF ERRORS------------------------------\n";
}
text_to_return
}
}

@ -153,6 +153,7 @@ pub struct DuplicateFinder {
stopped_search: bool,
use_cache: bool,
minimal_cache_file_size: u64,
delete_outdated_cache: bool,
}
impl DuplicateFinder {
@ -177,6 +178,7 @@ impl DuplicateFinder {
dryrun: false,
use_cache: true,
minimal_cache_file_size: 2 * 1024 * 1024, // By default cache only >= 1MB files
delete_outdated_cache: true,
}
}
@ -214,6 +216,10 @@ impl DuplicateFinder {
self.debug_print();
}
pub fn set_delete_outdated_cache(&mut self, delete_outdated_cache: bool) {
self.delete_outdated_cache = delete_outdated_cache;
}
pub const fn get_check_method(&self) -> &CheckingMethod {
&self.check_method
}
@ -826,7 +832,7 @@ impl DuplicateFinder {
let mut non_cached_files_to_check: BTreeMap<u64, Vec<FileEntry>> = Default::default();
if self.use_cache {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, &self.hash_type) {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.delete_outdated_cache, &self.hash_type) {
Some(t) => t,
None => Default::default(),
};
@ -1348,7 +1354,7 @@ pub fn make_hard_link(src: &Path, dst: &Path) -> io::Result<()> {
result
}
fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages, type_of_hash: &HashType, minimal_cache_file_size: u64) {
pub fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages, type_of_hash: &HashType, minimal_cache_file_size: u64) {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let cache_dir = PathBuf::from(proj_dirs.cache_dir());
if cache_dir.exists() {
@ -1417,7 +1423,7 @@ fn get_file_hash_name(type_of_hash: &HashType) -> String {
format!("cache_duplicates_{:?}.txt", type_of_hash)
}
fn load_hashes_from_file(text_messages: &mut Messages, type_of_hash: &HashType) -> Option<BTreeMap<u64, Vec<FileEntry>>> {
pub fn load_hashes_from_file(text_messages: &mut Messages, delete_outdated_cache: bool, type_of_hash: &HashType) -> Option<BTreeMap<u64, Vec<FileEntry>>> {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let cache_dir = PathBuf::from(proj_dirs.cache_dir());
let cache_file = cache_dir.join(get_file_hash_name(type_of_hash).as_str());
@ -1449,7 +1455,7 @@ fn load_hashes_from_file(text_messages: &mut Messages, type_of_hash: &HashType)
continue;
}
// Don't load cache data if destination file not exists
if Path::new(uuu[0]).exists() {
if !delete_outdated_cache || Path::new(uuu[0]).exists() {
let file_entry = FileEntry {
path: PathBuf::from(uuu[0]),
size: match uuu[1].parse::<u64>() {

@ -99,6 +99,7 @@ pub struct SimilarImages {
hash_alg: HashAlg,
image_filter: FilterType,
use_cache: bool,
delete_outdated_cache: bool,
}
/// Info struck with helpful information's about results
@ -137,6 +138,7 @@ impl SimilarImages {
hash_alg: HashAlg::Gradient,
image_filter: FilterType::Lanczos3,
use_cache: true,
delete_outdated_cache: true,
}
}
@ -149,6 +151,10 @@ impl SimilarImages {
}
}
pub fn set_delete_outdated_cache(&mut self, delete_outdated_cache: bool) {
self.delete_outdated_cache = delete_outdated_cache;
}
pub fn set_hash_alg(&mut self, hash_alg: HashAlg) {
self.hash_alg = hash_alg;
}
@ -369,7 +375,7 @@ impl SimilarImages {
// Cache algorithm:
// - Load data from file
// - Remove from data to search this already loaded entries(size of image must match)
// - Remove from data to search, already loaded entries from cache(size and modified datamust 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
@ -383,7 +389,7 @@ impl SimilarImages {
let mut non_cached_files_to_check: BTreeMap<String, FileEntry> = Default::default();
if self.use_cache {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.hash_size, self.hash_alg, self.image_filter) {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.delete_outdated_cache, self.hash_size, self.hash_alg, self.image_filter) {
Some(t) => t,
None => Default::default(),
};
@ -494,6 +500,7 @@ impl SimilarImages {
vec_file_entry.push((file_entry.clone(), file_entry.hash));
}
// All valid entries are used to create bktree used to check for hash similarity
for (file_entry, buf) in &vec_file_entry {
// Only use to comparing, non broken hashes(all 0 or 255 hashes means that algorithm fails to decode them because e.g. contains a log of alpha channel)
if !(buf.iter().all(|e| *e == 0) || buf.iter().all(|e| *e == 255)) {
@ -725,7 +732,7 @@ impl PrintResults for SimilarImages {
}
}
fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType) {
pub fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType) {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
// Lin: /home/username/.cache/czkawka
// Win: C:\Users\Username\AppData\Local\Qarmin\Czkawka\cache
@ -752,7 +759,7 @@ fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mu
let mut writer = BufWriter::new(file_handler);
for file_entry in hashmap.values() {
let mut string: String = String::with_capacity(100);
let mut string: String = String::with_capacity(128);
string += format!("{}//{}//{}//{}", file_entry.path.display(), file_entry.size, file_entry.dimensions, file_entry.modified_date).as_str();
@ -769,7 +776,7 @@ fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mu
}
}
fn load_hashes_from_file(text_messages: &mut Messages, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType) -> Option<BTreeMap<String, FileEntry>> {
pub fn load_hashes_from_file(text_messages: &mut Messages, delete_outdated_cache: bool, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType) -> Option<BTreeMap<String, FileEntry>> {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let cache_dir = PathBuf::from(proj_dirs.cache_dir());
let cache_file = cache_dir.join(get_cache_file(&hash_size, &hash_alg, &image_filter));
@ -809,7 +816,7 @@ fn load_hashes_from_file(text_messages: &mut Messages, hash_size: u8, hash_alg:
continue;
}
// Don't load cache data if destination file not exists
if Path::new(uuu[0]).exists() {
if !delete_outdated_cache || Path::new(uuu[0]).exists() {
let mut hash: Vec<u8> = Vec::new();
for i in 0..number_of_results {
hash.push(match uuu[4 + i as usize].parse::<u8>() {

@ -75,6 +75,7 @@ pub struct SimilarVideos {
videos_to_check: BTreeMap<String, FileEntry>,
use_cache: bool,
tolerance: i32,
delete_outdated_cache: bool,
}
/// Info struck with helpful information's about results
@ -110,9 +111,14 @@ impl SimilarVideos {
videos_to_check: Default::default(),
use_cache: true,
tolerance: 10,
delete_outdated_cache: false,
}
}
pub fn set_delete_outdated_cache(&mut self, delete_outdated_cache: bool) {
self.delete_outdated_cache = delete_outdated_cache;
}
pub fn set_tolerance(&mut self, tolerance: i32) {
assert!((0..=MAX_TOLERANCE).contains(&tolerance));
self.tolerance = tolerance
@ -348,7 +354,7 @@ impl SimilarVideos {
let mut non_cached_files_to_check: BTreeMap<String, FileEntry> = Default::default();
if self.use_cache {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages) {
loaded_hash_map = match load_hashes_from_file(&mut self.text_messages, self.delete_outdated_cache) {
Some(t) => t,
None => Default::default(),
};
@ -585,7 +591,7 @@ impl PrintResults for SimilarVideos {
}
}
fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages) {
pub fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mut Messages) {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
// Lin: /home/username/.cache/czkawka
// Win: C:\Users\Username\AppData\Local\Qarmin\Czkawka\cache
@ -642,7 +648,7 @@ fn save_hashes_to_file(hashmap: &BTreeMap<String, FileEntry>, text_messages: &mu
}
}
fn load_hashes_from_file(text_messages: &mut Messages) -> Option<BTreeMap<String, FileEntry>> {
pub fn load_hashes_from_file(text_messages: &mut Messages, delete_outdated_cache: bool) -> Option<BTreeMap<String, FileEntry>> {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let cache_dir = PathBuf::from(proj_dirs.cache_dir());
let cache_file = cache_dir.join("cache_similar_videos.txt");
@ -681,7 +687,7 @@ fn load_hashes_from_file(text_messages: &mut Messages) -> Option<BTreeMap<String
continue;
};
// Don't load cache data if destination file not exists
if Path::new(uuu[0]).exists() {
if !delete_outdated_cache || Path::new(uuu[0]).exists() {
let mut hash: [u64; 19] = [0; 19];
for i in 0..HASH_SIZE {
hash[i] = match uuu[6 + i as usize].parse::<u64>() {

@ -108,6 +108,9 @@ pub fn connect_button_search(
let radio_button_similar_hash_algorithm_mean = gui_data.main_notebook.radio_button_similar_hash_algorithm_mean.clone();
let radio_button_similar_hash_algorithm_vertgradient = gui_data.main_notebook.radio_button_similar_hash_algorithm_vertgradient.clone();
let radio_button_similar_hash_algorithm_doublegradient = gui_data.main_notebook.radio_button_similar_hash_algorithm_doublegradient.clone();
let check_button_settings_duplicates_delete_outdated_cache = gui_data.settings.check_button_settings_duplicates_delete_outdated_cache.clone();
let check_button_settings_similar_videos_delete_outdated_cache = gui_data.settings.check_button_settings_similar_videos_delete_outdated_cache.clone();
let check_button_settings_similar_images_delete_outdated_cache = gui_data.settings.check_button_settings_similar_images_delete_outdated_cache.clone();
buttons_search_clone.connect_clicked(move |_| {
let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories));
@ -174,6 +177,8 @@ pub fn connect_button_search(
panic!("No radio button is pressed");
}
let delete_outdated_cache = check_button_settings_duplicates_delete_outdated_cache.is_active();
let futures_sender_duplicate_files = futures_sender_duplicate_files.clone();
// Find duplicates
thread::spawn(move || {
@ -190,6 +195,7 @@ pub fn connect_button_search(
df.set_hash_type(hash_type);
df.set_ignore_hard_links(hide_hard_links);
df.set_use_cache(use_cache);
df.set_delete_outdated_cache(delete_outdated_cache);
df.find_duplicates(Some(&stop_receiver), Some(&futures_sender_duplicate_files));
let _ = glib_stop_sender.send(Message::Duplicates(df));
});
@ -331,6 +337,8 @@ pub fn connect_button_search(
let similarity = similar_images::Similarity::Similar(scale_similarity_similar_images.value() as u32);
let delete_outdated_cache = check_button_settings_similar_images_delete_outdated_cache.is_active();
let futures_sender_similar_images = futures_sender_similar_images.clone();
// Find similar images
thread::spawn(move || {
@ -347,6 +355,7 @@ pub fn connect_button_search(
sf.set_hash_alg(hash_alg);
sf.set_hash_size(hash_size);
sf.set_image_filter(image_filter);
sf.set_delete_outdated_cache(delete_outdated_cache);
sf.find_similar_images(Some(&stop_receiver), Some(&futures_sender_similar_images));
let _ = glib_stop_sender.send(Message::SimilarImages(sf));
});
@ -363,6 +372,8 @@ pub fn connect_button_search(
let tolerance = scale_similarity_similar_videos.value() as i32;
let delete_outdated_cache = check_button_settings_similar_videos_delete_outdated_cache.is_active();
let futures_sender_similar_videos = futures_sender_similar_videos.clone();
// Find similar videos
thread::spawn(move || {
@ -376,6 +387,7 @@ pub fn connect_button_search(
sf.set_maximal_file_size(maximal_file_size);
sf.set_use_cache(use_cache);
sf.set_tolerance(tolerance);
sf.set_delete_outdated_cache(delete_outdated_cache);
sf.find_similar_videos(Some(&stop_receiver), Some(&futures_sender_similar_videos));
let _ = glib_stop_sender.send(Message::SimilarVideos(sf));
});

@ -1,7 +1,15 @@
use czkawka_core::common_messages::Messages;
use czkawka_core::duplicate::HashType;
use directories_next::ProjectDirs;
use gtk::prelude::*;
use gtk::WindowPosition;
use gtk::{LabelBuilder, ResponseType, WindowPosition};
use image::imageops::FilterType;
use img_hash::HashAlg;
use std::collections::BTreeMap;
use std::default::Default;
use crate::gui_data::GuiData;
use crate::help_functions::get_dialog_box_child;
use crate::saving_loading::{load_configuration, reset_configuration, save_configuration};
pub fn connect_settings(gui_data: &GuiData) {
@ -57,4 +65,147 @@ pub fn connect_settings(gui_data: &GuiData) {
reset_configuration(true, &upper_notebook, &settings, &text_view_errors);
});
}
// Connect button for opening cache
{
let button_settings_open_cache_folder = gui_data.settings.button_settings_open_cache_folder.clone();
button_settings_open_cache_folder.connect_clicked(move |_| {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let cache_dir = proj_dirs.cache_dir();
open::that_in_background(cache_dir);
}
});
}
// Connect button for opening settings
{
let button_settings_open_settings_folder = gui_data.settings.button_settings_open_settings_folder.clone();
button_settings_open_settings_folder.connect_clicked(move |_| {
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
let config_dir = proj_dirs.config_dir();
open::that_in_background(config_dir);
}
});
}
// Connect clear cache methods
{
{
let button_settings_duplicates_clear_cache = gui_data.settings.button_settings_duplicates_clear_cache.clone();
let settings_window = gui_data.settings.window_settings.clone();
let text_view_errors = gui_data.text_view_errors.clone();
let entry_settings_cache_file_minimal_size = gui_data.settings.entry_settings_cache_file_minimal_size.clone();
button_settings_duplicates_clear_cache.connect_clicked(move |_| {
let dialog = create_clear_cache_dialog("duplicates");
dialog.show_all();
settings_window.set_sensitive(false);
let settings_window = settings_window.clone();
let text_view_errors = text_view_errors.clone();
let entry_settings_cache_file_minimal_size = entry_settings_cache_file_minimal_size.clone();
dialog.connect_response(move |dialog, response_type| {
if response_type == ResponseType::Ok {
let mut messages: Messages = Messages::new();
for type_of_hash in [HashType::Xxh3, HashType::Blake3, HashType::Crc32].iter() {
if let Some(cache_entries) = czkawka_core::duplicate::load_hashes_from_file(&mut messages, true, type_of_hash) {
let mut hashmap_to_save: BTreeMap<String, czkawka_core::duplicate::FileEntry> = Default::default();
for (_, vec_file_entry) in cache_entries {
for file_entry in vec_file_entry {
hashmap_to_save.insert(file_entry.path.to_string_lossy().to_string(), file_entry);
}
}
czkawka_core::duplicate::save_hashes_to_file(&hashmap_to_save, &mut messages, type_of_hash, entry_settings_cache_file_minimal_size.text().as_str().parse::<u64>().unwrap_or(2 * 1024 * 1024))
}
}
messages.messages.push("Properly cleared cache".to_string());
text_view_errors.buffer().unwrap().set_text(messages.create_messages_text().as_str());
}
dialog.close();
settings_window.set_sensitive(true);
});
});
}
{
let button_settings_similar_images_clear_cache = gui_data.settings.button_settings_similar_images_clear_cache.clone();
let settings_window = gui_data.settings.window_settings.clone();
let text_view_errors = gui_data.text_view_errors.clone();
button_settings_similar_images_clear_cache.connect_clicked(move |_| {
let dialog = create_clear_cache_dialog("similar images");
dialog.show_all();
settings_window.set_sensitive(false);
let settings_window = settings_window.clone();
let text_view_errors = text_view_errors.clone();
dialog.connect_response(move |dialog, response_type| {
if response_type == ResponseType::Ok {
let mut messages: Messages = Messages::new();
for hash_size in [8, 16, 32].iter() {
for image_filter in [FilterType::Lanczos3, FilterType::CatmullRom, FilterType::Gaussian, FilterType::Nearest, FilterType::Triangle].iter() {
for hash_alg in [HashAlg::Blockhash, HashAlg::Gradient, HashAlg::DoubleGradient, HashAlg::VertGradient, HashAlg::Mean].iter() {
if let Some(cache_entries) = czkawka_core::similar_images::load_hashes_from_file(&mut messages, true, *hash_size, *hash_alg, *image_filter) {
czkawka_core::similar_images::save_hashes_to_file(&cache_entries, &mut messages, *hash_size, *hash_alg, *image_filter);
}
}
}
}
messages.messages.push("Properly cleared cache".to_string());
text_view_errors.buffer().unwrap().set_text(messages.create_messages_text().as_str());
}
dialog.close();
settings_window.set_sensitive(true);
});
});
}
{
let button_settings_similar_videos_clear_cache = gui_data.settings.button_settings_similar_videos_clear_cache.clone();
let settings_window = gui_data.settings.window_settings.clone();
let text_view_errors = gui_data.text_view_errors.clone();
button_settings_similar_videos_clear_cache.connect_clicked(move |_| {
let dialog = create_clear_cache_dialog("similar videos");
dialog.show_all();
settings_window.set_sensitive(false);
let settings_window = settings_window.clone();
let text_view_errors = text_view_errors.clone();
dialog.connect_response(move |dialog, response_type| {
if response_type == ResponseType::Ok {
let mut messages: Messages = Messages::new();
if let Some(cache_entries) = czkawka_core::similar_videos::load_hashes_from_file(&mut messages, true) {
czkawka_core::similar_videos::save_hashes_to_file(&cache_entries, &mut messages);
}
messages.messages.push("Properly cleared cache".to_string());
text_view_errors.buffer().unwrap().set_text(messages.create_messages_text().as_str());
}
dialog.close();
settings_window.set_sensitive(true);
});
});
}
}
}
fn create_clear_cache_dialog(title_str: &str) -> gtk::Dialog {
let dialog = gtk::Dialog::builder().title(format!("Clearing {} cache", title_str).as_str()).build();
dialog.add_button("OK", ResponseType::Ok);
dialog.add_button("Cancel", ResponseType::Cancel);
let label = LabelBuilder::new().label(format!("Do you want to clear {} cache from outdated entries?", title_str).as_str()).build();
let label2 = LabelBuilder::new().label("This operation will remove all cache entries which points to invalid files.").build();
let label3 = LabelBuilder::new().label("This may speedup a little loading/saving to cache.").build();
let label4 = LabelBuilder::new()
.label("WARNING: Operation will remove all cached data from unplugged external drives, so hash will need to be generated again.")
.build();
let internal_box = get_dialog_box_child(&dialog);
internal_box.add(&label);
internal_box.add(&label2);
internal_box.add(&label3);
internal_box.add(&label4);
dialog
}

@ -19,8 +19,11 @@ impl GuiAbout {
about_dialog.set_position(WindowPosition::Center);
let button_repository: gtk::Button = builder.object("button_repository").unwrap();
button_repository.set_tooltip_text(Some("Link to repository page with source code."));
let button_donation: gtk::Button = builder.object("button_donation").unwrap();
button_donation.set_tooltip_text(Some("Link to donation page."));
let button_instruction: gtk::Button = builder.object("button_instruction").unwrap();
button_instruction.set_tooltip_text(Some("Link to instruction page."));
Self {
about_dialog,

@ -25,7 +25,20 @@ impl GuiBottomButtons {
let buttons_hardlink: gtk::Button = builder.object("buttons_hardlink").unwrap();
let buttons_move: gtk::Button = builder.object("buttons_move").unwrap();
buttons_search.set_tooltip_text(Some("Start to search for files/folders"));
buttons_select.set_tooltip_text(Some("Selects records\nOnly selected files/folders can be later processed."));
buttons_delete.set_tooltip_text(Some("Delete selected files/folders"));
buttons_save.set_tooltip_text(Some("Save data about search to file"));
buttons_symlink.set_tooltip_text(Some(
"Creates symbolic links\nOnly works when at least 2 results in group are selected\nFirst is unchanged and second and later are symlinked to first",
));
buttons_hardlink.set_tooltip_text(Some("Creates hardlinks\nOnly works when at least 2 results in group are selected\nFirst is unchanged and second and later are hardlinked to first"));
buttons_move.set_tooltip_text(Some(
"Moves files to chosen folder\nIt copy all files to folder without preserving directory tree\nWhen trying to move 2 files with identical name to folder, second will fail and show error",
));
let buttons_show_errors: gtk::Button = builder.object("buttons_show_errors").unwrap();
buttons_show_errors.set_tooltip_text(Some("Show/Hide bottom error panel"));
let buttons_names = [
"search".to_string(),

@ -10,6 +10,10 @@ impl GuiHeader {
pub fn create_from_builder(builder: &gtk::Builder) -> Self {
let button_settings: gtk::Button = builder.object("button_settings").unwrap();
let button_app_info: gtk::Button = builder.object("button_app_info").unwrap();
button_settings.set_tooltip_text(Some("Opens settings dialog"));
button_app_info.set_tooltip_text(Some("Opens dialog with info about app"));
Self { button_settings, button_app_info }
}
}

@ -51,7 +51,7 @@ impl GuiPopovers {
let popover_select: gtk::Popover = builder.object("popover_select").unwrap();
// Popover right click
// Popover right click(not implemented for now)
let glade_src = include_str!("../ui/popover_right_click.glade").to_string();
let builder = Builder::from_string(glade_src.as_str());

@ -18,14 +18,25 @@ pub struct GuiSettings {
pub check_button_settings_hide_hard_links: gtk::CheckButton,
pub entry_settings_cache_file_minimal_size: gtk::Entry,
pub check_button_settings_show_preview_duplicates: gtk::CheckButton,
pub check_button_settings_duplicates_delete_outdated_cache: gtk::CheckButton,
pub button_settings_duplicates_clear_cache: gtk::Button,
// Similar Images
pub check_button_settings_show_preview_similar_images: gtk::CheckButton,
pub check_button_settings_similar_images_delete_outdated_cache: gtk::CheckButton,
pub button_settings_similar_images_clear_cache: gtk::Button,
// Similar Videos
pub check_button_settings_similar_videos_delete_outdated_cache: gtk::CheckButton,
pub button_settings_similar_videos_clear_cache: gtk::Button,
// Buttons
pub button_settings_save_configuration: gtk::Button,
pub button_settings_load_configuration: gtk::Button,
pub button_settings_reset_configuration: gtk::Button,
pub button_settings_open_cache_folder: gtk::Button,
pub button_settings_open_settings_folder: gtk::Button,
}
impl GuiSettings {
@ -46,19 +57,64 @@ impl GuiSettings {
let check_button_settings_use_cache: gtk::CheckButton = builder.object("check_button_settings_use_cache").unwrap();
let check_button_settings_use_trash: gtk::CheckButton = builder.object("check_button_settings_use_trash").unwrap();
check_button_settings_save_at_exit.set_tooltip_text(Some("Saves configuration to file when closing app."));
check_button_settings_load_at_start.set_tooltip_text(Some("Loading at start configuration from file.\n\nNot selecting this option will load default settings."));
check_button_settings_confirm_deletion.set_tooltip_text(Some("Shows confirmation dialog when clicking at delete button."));
check_button_settings_confirm_group_deletion.set_tooltip_text(Some("Shows dialog when trying to remove all records from group."));
check_button_settings_show_text_view.set_tooltip_text(Some("Shows error panel at bottom."));
check_button_settings_use_cache.set_tooltip_text(Some("Option to which allows to not use cache feature."));
check_button_settings_use_trash.set_tooltip_text(Some("When enabled it moves files to trash instead deleting them permanently."));
// Duplicates
let check_button_settings_hide_hard_links: gtk::CheckButton = builder.object("check_button_settings_hide_hard_links").unwrap();
let entry_settings_cache_file_minimal_size: gtk::Entry = builder.object("entry_settings_cache_file_minimal_size").unwrap();
let check_button_settings_show_preview_duplicates: gtk::CheckButton = builder.object("check_button_settings_show_preview_duplicates").unwrap();
let check_button_settings_duplicates_delete_outdated_cache: gtk::CheckButton = builder.object("check_button_settings_duplicates_delete_outdated_cache").unwrap();
let button_settings_duplicates_clear_cache: gtk::Button = builder.object("button_settings_duplicates_clear_cache").unwrap();
check_button_settings_hide_hard_links.set_tooltip_text(Some(
"Hides all files except one, if are points to same data(are hardlinked).\n\nE.g. in case where on disk there is 7 files which are hardlinked to specific data and one different file with same data but different inode, then in duplicate finder will be visible only one unique file and one file from hardlinked ones.",
));
entry_settings_cache_file_minimal_size.set_tooltip_text(Some(
"Allows to set minimal size of file, which will be cached.\n\nChoosing smaller value, will generate more records which will speedup search, but slowdown cache loading/saving.",
));
check_button_settings_show_preview_duplicates.set_tooltip_text(Some("Shows preview at right side, when selecting image file."));
check_button_settings_duplicates_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan."));
button_settings_duplicates_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\nShould be used only if automatic clearing was disabled."));
// Similar Images
let check_button_settings_show_preview_similar_images: gtk::CheckButton = builder.object("check_button_settings_show_preview_similar_images").unwrap();
let check_button_settings_similar_images_delete_outdated_cache: gtk::CheckButton = builder.object("check_button_settings_similar_images_delete_outdated_cache").unwrap();
let button_settings_similar_images_clear_cache: gtk::Button = builder.object("button_settings_similar_images_clear_cache").unwrap();
check_button_settings_show_preview_similar_images.set_tooltip_text(Some("Shows preview at right side, when selecting image file."));
check_button_settings_similar_images_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan."));
button_settings_similar_images_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\nShould be used only if automatic clearing was disabled."));
// Similar Videos
let check_button_settings_similar_videos_delete_outdated_cache: gtk::CheckButton = builder.object("check_button_settings_similar_videos_delete_outdated_cache").unwrap();
let button_settings_similar_videos_clear_cache: gtk::Button = builder.object("button_settings_similar_videos_clear_cache").unwrap();
check_button_settings_similar_videos_delete_outdated_cache.set_tooltip_text(Some("Allows to delete outdated cache results which points to non-existent files.\n\nWhen enabled, app make sure when loading records, that all points to valid files and ignore broken ones.\n\nDisabling this option, will help to scan files on external drives, so cache entries about them will not be purged in next scan.\n\nIn case of having hundred of thousands records in cache, it is suggested to enable this option, to speedup cache loading and saving at start and end of scan."));
button_settings_similar_videos_clear_cache.set_tooltip_text(Some("Manually clear cache from outdated entries.\nShould be used only if automatic clearing was disabled."));
// Saving/Loading/Resetting configuration
let button_settings_save_configuration: gtk::Button = builder.object("button_settings_save_configuration").unwrap();
let button_settings_load_configuration: gtk::Button = builder.object("button_settings_load_configuration").unwrap();
let button_settings_reset_configuration: gtk::Button = builder.object("button_settings_reset_configuration").unwrap();
button_settings_save_configuration.set_tooltip_text(Some("Save current settings configuration to file."));
button_settings_load_configuration.set_tooltip_text(Some("Load settings from file and replace current configuration with them."));
button_settings_reset_configuration.set_tooltip_text(Some("Reset current configuration to default one."));
let button_settings_open_cache_folder: gtk::Button = builder.object("button_settings_open_cache_folder").unwrap();
let button_settings_open_settings_folder: gtk::Button = builder.object("button_settings_open_settings_folder").unwrap();
button_settings_open_cache_folder.set_tooltip_text(Some(
"Opens folder where are stored txt files with cache.\n\nModifying them may cause to show invalid results but also modifying e.g. path may save time when moving big amount of files to different place.\n\nYou can copy this files between computers to save time on scanning again for files(of course if they have similar directory structure).\n\nIn case of problems with cache, this files can be removed, so app will automatically regenerate them.",
));
button_settings_open_settings_folder.set_tooltip_text(Some("Opens folder where Czkawka config are stored.\n\nModifying them, may cause to show."));
Self {
window_settings,
check_button_settings_save_at_exit,
@ -71,10 +127,18 @@ impl GuiSettings {
check_button_settings_hide_hard_links,
entry_settings_cache_file_minimal_size,
check_button_settings_show_preview_duplicates,
check_button_settings_duplicates_delete_outdated_cache,
button_settings_duplicates_clear_cache,
check_button_settings_show_preview_similar_images,
check_button_settings_similar_images_delete_outdated_cache,
button_settings_similar_images_clear_cache,
check_button_settings_similar_videos_delete_outdated_cache,
button_settings_similar_videos_clear_cache,
button_settings_save_configuration,
button_settings_load_configuration,
button_settings_reset_configuration,
button_settings_open_cache_folder,
button_settings_open_settings_folder,
}
}
}

@ -44,6 +44,7 @@ impl GuiUpperNotebook {
let entry_excluded_items: gtk::Entry = builder.object("entry_excluded_items").unwrap();
let check_button_recursive: gtk::CheckButton = builder.object("check_button_recursive").unwrap();
check_button_recursive.set_tooltip_text(Some("If selected, search also for files which are not placed directly under chosen folders"));
let buttons_manual_add_directory: gtk::Button = builder.object("buttons_manual_add_directory").unwrap();
let buttons_add_included_directory: gtk::Button = builder.object("buttons_add_included_directory").unwrap();
@ -52,6 +53,13 @@ impl GuiUpperNotebook {
let buttons_add_excluded_directory: gtk::Button = builder.object("buttons_add_excluded_directory").unwrap();
let buttons_remove_excluded_directory: gtk::Button = builder.object("buttons_remove_excluded_directory").unwrap();
buttons_manual_add_directory.set_tooltip_text(Some("Allows to add directory name by hand"));
buttons_add_included_directory.set_tooltip_text(Some("Add new directory to search"));
buttons_remove_included_directory.set_tooltip_text(Some("Delete directory from search"));
buttons_manual_add_excluded_directory.set_tooltip_text(Some("Allows to add directory name by hand"));
buttons_add_excluded_directory.set_tooltip_text(Some("Add directory to be excluded in search"));
buttons_remove_excluded_directory.set_tooltip_text(Some("Delete directory from excluded list"));
Self {
notebook_upper,
scrolled_window_included_directories,

@ -142,6 +142,21 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
data_to_save.push("--cache_minimal_file_size:".to_string());
let entry_settings_cache_file_minimal_size = settings.entry_settings_cache_file_minimal_size.clone();
data_to_save.push(entry_settings_cache_file_minimal_size.text().as_str().parse::<u64>().unwrap_or(2 * 1024 * 1024).to_string());
//// Duplicates, delete outdated entries to trash
data_to_save.push("--delete_outdated_entries_duplicates:".to_string());
let check_button_settings_duplicates_delete_outdated_cache = settings.check_button_settings_duplicates_delete_outdated_cache.clone();
data_to_save.push(check_button_settings_duplicates_delete_outdated_cache.is_active().to_string());
//// Similar Images, delete outdated entries to trash
data_to_save.push("--delete_outdated_entries_similar_images:".to_string());
let check_button_settings_similar_images_delete_outdated_cache = settings.check_button_settings_similar_images_delete_outdated_cache.clone();
data_to_save.push(check_button_settings_similar_images_delete_outdated_cache.is_active().to_string());
//// Similar Videos, delete outdated entries to trash
data_to_save.push("--delete_outdated_entries_similar_videos:".to_string());
let check_button_settings_similar_videos_delete_outdated_cache = settings.check_button_settings_similar_videos_delete_outdated_cache.clone();
data_to_save.push(check_button_settings_similar_videos_delete_outdated_cache.is_active().to_string());
}
// Creating/Opening config file
@ -195,6 +210,9 @@ enum TypeOfLoadedData {
UseCache,
UseTrash,
CacheMinimalSize,
DeleteCacheDuplicates,
DeleteCacheSimilarImages,
DeleteCacheSimilarVideos,
}
pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNotebook, settings: &GuiSettings, text_view_errors: &TextView, scrolled_window_errors: &ScrolledWindow) {
@ -243,6 +261,9 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
let mut use_cache: bool = true;
let mut use_trash: bool = false;
let mut cache_minimal_size: u64 = 2 * 1024 * 1024;
let mut delete_outdated_cache_dupliactes: bool = true;
let mut delete_outdated_cache_similar_images: bool = true;
let mut delete_outdated_cache_similar_videos: bool = false;
let mut current_type = TypeOfLoadedData::None;
for (line_number, line) in loaded_data.replace("\r\n", "\n").split('\n').enumerate() {
@ -280,6 +301,12 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
current_type = TypeOfLoadedData::UseTrash;
} else if line.starts_with("--cache_minimal_file_size") {
current_type = TypeOfLoadedData::CacheMinimalSize;
} else if line.starts_with("--delete_outdated_entries_duplicates") {
current_type = TypeOfLoadedData::DeleteCacheDuplicates;
} else if line.starts_with("--delete_outdated_entries_similar_videos") {
current_type = TypeOfLoadedData::DeleteCacheSimilarVideos;
} else if line.starts_with("--delete_outdated_entries_similar_images") {
current_type = TypeOfLoadedData::DeleteCacheSimilarImages;
} else if line.starts_with("--") {
current_type = TypeOfLoadedData::None;
add_text_to_text_view(
@ -446,6 +473,45 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
);
}
}
TypeOfLoadedData::DeleteCacheDuplicates => {
let line = line.to_lowercase();
if line == "1" || line == "true" {
delete_outdated_cache_dupliactes = true;
} else if line == "0" || line == "false" {
delete_outdated_cache_dupliactes = false;
} else {
add_text_to_text_view(
&text_view_errors,
format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(),
);
}
}
TypeOfLoadedData::DeleteCacheSimilarImages => {
let line = line.to_lowercase();
if line == "1" || line == "true" {
delete_outdated_cache_similar_images = true;
} else if line == "0" || line == "false" {
delete_outdated_cache_similar_images = false;
} else {
add_text_to_text_view(
&text_view_errors,
format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(),
);
}
}
TypeOfLoadedData::DeleteCacheSimilarVideos => {
let line = line.to_lowercase();
if line == "1" || line == "true" {
delete_outdated_cache_similar_videos = true;
} else if line == "0" || line == "false" {
delete_outdated_cache_similar_videos = false;
} else {
add_text_to_text_view(
&text_view_errors,
format!("Found invalid data in line {} \"{}\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str(),
);
}
}
}
}
}
@ -488,6 +554,10 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
settings.check_button_settings_show_preview_similar_images.set_active(show_previews_similar_images);
settings.check_button_settings_show_preview_duplicates.set_active(show_previews_duplicates);
settings.check_button_settings_similar_videos_delete_outdated_cache.set_active(delete_outdated_cache_similar_videos);
settings.check_button_settings_similar_images_delete_outdated_cache.set_active(delete_outdated_cache_similar_images);
settings.check_button_settings_duplicates_delete_outdated_cache.set_active(delete_outdated_cache_dupliactes);
settings.check_button_settings_show_text_view.set_active(bottom_text_panel);
if !bottom_text_panel {
scrolled_window_errors.hide();
@ -568,7 +638,7 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb
entry_allowed_extensions.set_text("");
}
// Set settings
// Set default settings
{
settings.check_button_settings_save_at_exit.set_active(true);
settings.check_button_settings_load_at_start.set_active(true);
@ -580,7 +650,10 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb
settings.check_button_settings_hide_hard_links.set_active(true);
settings.check_button_settings_use_cache.set_active(true);
settings.check_button_settings_use_trash.set_active(false);
settings.entry_settings_cache_file_minimal_size.set_text("2097152");
settings.entry_settings_cache_file_minimal_size.set_text("524288");
settings.check_button_settings_similar_videos_delete_outdated_cache.set_active(false);
settings.check_button_settings_similar_images_delete_outdated_cache.set_active(true);
settings.check_button_settings_duplicates_delete_outdated_cache.set_active(true);
}
if manual_clearing {
add_text_to_text_view(&text_view_errors, "Current configuration was cleared.");

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2
<!-- Generated with glade 3.39.0
The MIT License (MIT)
@ -110,7 +110,7 @@ Author: Rafał Mikrut
</child>
<child>
<object class="GtkCheckButton" id="check_button_settings_confirm_deletion">
<property name="label" translatable="yes">Show confirm dialog when deleting</property>
<property name="label" translatable="yes">Show confirm dialog when deleting any files</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -187,6 +187,45 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkButton" id="button_settings_open_cache_folder">
<property name="label" translatable="yes">Open cache folder</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_settings_open_settings_folder">
<property name="label" translatable="yes">Open settings folder</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
@ -237,6 +276,21 @@ Author: Rafał Mikrut
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_settings_duplicates_delete_outdated_cache">
<property name="label" translatable="yes">Delete outdated cache entries automatically</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@ -260,7 +314,7 @@ Author: Rafał Mikrut
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="max-length">15</property>
<property name="text" translatable="yes">2097152</property>
<property name="text" translatable="yes">524288</property>
<property name="caps-lock-warning">False</property>
<property name="input-purpose">number</property>
</object>
@ -275,7 +329,21 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_settings_duplicates_clear_cache">
<property name="label" translatable="yes">Remove outdated results from duplicates cache</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
@ -314,6 +382,35 @@ Author: Rafał Mikrut
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_settings_similar_images_delete_outdated_cache">
<property name="label" translatable="yes">Delete outdated cache entries automatically</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_settings_similar_images_clear_cache">
<property name="label" translatable="yes">Remove outdated results from images cache</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>
@ -330,6 +427,55 @@ Author: Rafał Mikrut
<property name="tab-fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="button_settings_similar_videos_clear_cache">
<property name="label" translatable="yes">Remove outdated results from videos cache</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_settings_similar_videos_delete_outdated_cache">
<property name="label" translatable="yes">Delete outdated cache entries automatically</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Similar Videos</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab-fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>

Loading…
Cancel
Save