1
0
Fork 0
mirror of synced 2024-04-29 18:13:47 +12:00

Add support for checking for invalid symlinks (#124)

This commit is contained in:
Rafał Mikrut 2020-12-21 16:09:39 +01:00 committed by GitHub
parent 4a33ff7d86
commit 1d59199bb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 891 additions and 14 deletions

View file

@ -147,6 +147,23 @@ pub enum Commands {
#[structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = "1024", help = "Minimum size in bytes", long_help = "Minimum size of checked files in bytes, assigning bigger value may speed up searching")]
minimal_file_size: u64,
},
#[structopt(name = "symlinks", about = "Finds invalid symlinks", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt")]
InvalidSymlinks {
#[structopt(flatten)]
directories: Directories,
#[structopt(flatten)]
excluded_directories: ExcludedDirectories,
#[structopt(flatten)]
excluded_items: ExcludedItems,
#[structopt(flatten)]
allowed_extensions: AllowedExtensions,
#[structopt(short = "D", long, help = "Delete found files")]
delete_files: bool,
#[structopt(flatten)]
file_to_save: FileToSave,
#[structopt(flatten)]
not_recursive: NotRecursive,
},
}
#[derive(Debug, StructOpt)]
@ -301,4 +318,5 @@ EXAMPLES:
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt
{bin} zeroed -d /home/rafal -e /home/krzak -f results.txt"
{bin} music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt"#;
{bin} music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt
{bin} symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt"#;

View file

@ -10,6 +10,8 @@ use czkawka_core::{
duplicate::DuplicateFinder,
empty_files::{self, EmptyFiles},
empty_folder::EmptyFolder,
invalid_symlinks,
invalid_symlinks::InvalidSymlinks,
same_music::SameMusic,
similar_images::SimilarImages,
temporary::{self, Temporary},
@ -296,5 +298,38 @@ fn main() {
mf.print_results();
mf.get_text_messages().print_messages();
}
Commands::InvalidSymlinks {
directories,
excluded_directories,
excluded_items,
allowed_extensions,
file_to_save,
not_recursive,
delete_files,
} => {
let mut ifs = InvalidSymlinks::new();
ifs.set_included_directory(path_list_to_str(directories.directories));
ifs.set_excluded_directory(path_list_to_str(excluded_directories.excluded_directories));
ifs.set_excluded_items(path_list_to_str(excluded_items.excluded_items));
ifs.set_allowed_extensions(allowed_extensions.allowed_extensions.join(","));
ifs.set_recursive_search(!not_recursive.not_recursive);
if delete_files {
ifs.set_delete_method(invalid_symlinks::DeleteMethod::Delete);
}
ifs.find_invalid_links(None, None);
if let Some(file_name) = file_to_save.file_name() {
if !ifs.save_results_to_file(file_name) {
ifs.get_text_messages().print_messages();
process::exit(1);
}
}
#[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed
ifs.print_results();
ifs.get_text_messages().print_messages();
}
}
}

View file

@ -0,0 +1,457 @@
use std::fs::{File, Metadata};
use std::io::prelude::*;
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{fs, thread};
use crate::common::Common;
use crate::common_directory::Directories;
use crate::common_extensions::Extensions;
use crate::common_items::ExcludedItems;
use crate::common_messages::Messages;
use crate::common_traits::*;
use crossbeam_channel::Receiver;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread::sleep;
#[derive(Debug)]
pub struct ProgressData {
pub current_stage: u8,
pub max_stage: u8,
pub files_checked: usize,
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum DeleteMethod {
None,
Delete,
}
const MAX_NUMBER_OF_SYMLINK_JUMPS: i32 = 20;
#[derive(Clone)]
pub enum ErrorType {
InfiniteRecursion,
NonExistentFile,
}
#[derive(Clone)]
pub struct FileEntry {
pub symlink_path: PathBuf,
pub destination_path: PathBuf,
pub type_of_error: ErrorType,
pub modified_date: u64,
}
/// Info struck with helpful information's about results
#[derive(Default)]
pub struct Info {
pub number_of_checked_files: usize,
pub number_of_checked_folders: usize,
pub number_of_ignored_files: usize,
pub number_of_ignored_things: usize,
pub number_of_invalid_symlinks: usize,
pub number_of_removed_files: usize,
pub number_of_failed_to_remove_files: usize,
}
impl Info {
pub fn new() -> Self {
Default::default()
}
}
/// Struct with required information's to work
pub struct InvalidSymlinks {
text_messages: Messages,
information: Info,
invalid_symlinks: Vec<FileEntry>,
directories: Directories,
allowed_extensions: Extensions,
excluded_items: ExcludedItems,
recursive_search: bool,
delete_method: DeleteMethod,
stopped_search: bool,
}
impl InvalidSymlinks {
pub fn new() -> Self {
Self {
text_messages: Messages::new(),
information: Info::new(),
recursive_search: true,
allowed_extensions: Extensions::new(),
directories: Directories::new(),
excluded_items: ExcludedItems::new(),
invalid_symlinks: vec![],
delete_method: DeleteMethod::None,
stopped_search: false,
}
}
pub fn find_invalid_links(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender<ProgressData>>) {
self.directories.optimize_directories(self.recursive_search, &mut self.text_messages);
if !self.check_files(stop_receiver, progress_sender) {
self.stopped_search = true;
return;
}
self.delete_files();
self.debug_print();
}
pub fn get_stopped_search(&self) -> bool {
self.stopped_search
}
pub const fn get_invalid_symlinks(&self) -> &Vec<FileEntry> {
&self.invalid_symlinks
}
pub const fn get_text_messages(&self) -> &Messages {
&self.text_messages
}
pub const fn get_information(&self) -> &Info {
&self.information
}
pub fn set_delete_method(&mut self, delete_method: DeleteMethod) {
self.delete_method = delete_method;
}
pub fn set_recursive_search(&mut self, recursive_search: bool) {
self.recursive_search = recursive_search;
}
pub fn set_included_directory(&mut self, included_directory: String) -> bool {
self.directories.set_included_directory(included_directory, &mut self.text_messages)
}
pub fn set_excluded_directory(&mut self, excluded_directory: String) {
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
}
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
}
pub fn set_excluded_items(&mut self, excluded_items: String) {
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages);
}
/// Check files for any with size == 0
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender<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
for id in &self.directories.included_directories {
folders_to_check.push(id.clone());
}
self.information.number_of_checked_folders += folders_to_check.len();
//// PROGRESS THREAD START
const LOOP_DURATION: u32 = 200; //in ms
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_file_counter = Arc::new(AtomicUsize::new(0));
let progress_thread_handle;
if let Some(progress_sender) = progress_sender {
let mut progress_send = progress_sender.clone();
let progress_thread_run = progress_thread_run.clone();
let atomic_file_counter = atomic_file_counter.clone();
progress_thread_handle = thread::spawn(move || loop {
progress_send
.try_send(ProgressData {
current_stage: 0,
max_stage: 0,
files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize,
})
.unwrap();
if !progress_thread_run.load(Ordering::Relaxed) {
break;
}
sleep(Duration::from_millis(LOOP_DURATION as u64));
});
} else {
progress_thread_handle = thread::spawn(|| {});
}
//// PROGRESS THREAD END
while !folders_to_check.is_empty() {
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
// End thread which send info to gui
progress_thread_run.store(false, Ordering::Relaxed);
progress_thread_handle.join().unwrap();
return false;
}
let current_folder = folders_to_check.pop().unwrap();
// Read current dir, if permission are denied just go to next
let read_dir = match fs::read_dir(&current_folder) {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display()));
continue;
} // Permissions denied
};
// Check every sub folder/file/link etc.
'dir: for entry in read_dir {
let entry_data = match entry {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display()));
continue;
} //Permissions denied
};
let metadata: Metadata = match entry_data.metadata() {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display()));
continue;
} //Permissions denied
};
if metadata.is_dir() {
self.information.number_of_checked_folders += 1;
if !self.recursive_search {
continue;
}
let next_folder = current_folder.join(entry_data.file_name());
if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) {
continue 'dir;
}
folders_to_check.push(next_folder);
} else if metadata.is_file() {
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
} else if metadata.file_type().is_symlink() {
atomic_file_counter.fetch_add(1, Ordering::Relaxed);
let file_name_lowercase: String = match entry_data.file_name().into_string() {
Ok(t) => t,
Err(_) => continue,
}
.to_lowercase();
// Checking allowed extensions
if !self.allowed_extensions.file_extensions.is_empty() {
let allowed = self.allowed_extensions.file_extensions.iter().any(|e| file_name_lowercase.ends_with((".".to_string() + e.to_lowercase().as_str()).as_str()));
if !allowed {
// Not an allowed extension, ignore it.
self.information.number_of_ignored_files += 1;
continue 'dir;
}
}
// Checking files
let current_file_name = current_folder.join(entry_data.file_name());
if self.excluded_items.is_excluded(&current_file_name) {
continue 'dir;
}
let mut destination_path = PathBuf::new();
let type_of_error;
match current_file_name.read_link() {
Ok(t) => {
destination_path.push(t);
let mut number_of_loop = 0;
let mut current_path = current_file_name.clone();
loop {
if number_of_loop == 0 && !current_path.exists() {
type_of_error = ErrorType::NonExistentFile;
break;
}
if number_of_loop == MAX_NUMBER_OF_SYMLINK_JUMPS {
type_of_error = ErrorType::InfiniteRecursion;
break;
}
current_path = match current_path.read_link() {
Ok(t) => t,
Err(_) => {
// Looks that some next symlinks are broken, but we do nothing with it
continue 'dir;
}
};
number_of_loop += 1;
}
}
Err(_) => {
// Failed to load info about it
type_of_error = ErrorType::NonExistentFile;
}
}
// Creating new file entry
let fe: FileEntry = FileEntry {
symlink_path: current_file_name.clone(),
destination_path,
type_of_error,
modified_date: match metadata.modified() {
Ok(t) => match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(),
Err(_) => {
self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display()));
0
}
},
Err(_) => {
self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display()));
continue;
} // Permissions Denied
},
};
// Adding files to Vector
self.invalid_symlinks.push(fe);
self.information.number_of_checked_files += 1;
} else {
// Not sure what exactly this is
self.information.number_of_ignored_things += 1;
}
}
}
self.information.number_of_invalid_symlinks = self.invalid_symlinks.len();
// End thread which send info to gui
progress_thread_run.store(false, Ordering::Relaxed);
progress_thread_handle.join().unwrap();
Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string());
true
}
/// 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 {
if fs::remove_file(file_entry.symlink_path.clone()).is_err() {
self.text_messages.warnings.push(file_entry.symlink_path.display().to_string());
}
}
}
DeleteMethod::None => {
//Just do nothing
}
}
Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
}
}
impl Default for InvalidSymlinks {
fn default() -> Self {
Self::new()
}
}
impl DebugPrint for InvalidSymlinks {
#[allow(dead_code)]
#[allow(unreachable_code)]
/// Debugging printing - only available on debug build
fn debug_print(&self) {
#[cfg(not(debug_assertions))]
{
return;
}
println!("---------------DEBUG PRINT---------------");
println!("### Information's");
println!("Errors size - {}", self.text_messages.errors.len());
println!("Warnings size - {}", self.text_messages.warnings.len());
println!("Messages size - {}", self.text_messages.messages.len());
println!("Number of checked files - {}", self.information.number_of_checked_files);
println!("Number of checked folders - {}", self.information.number_of_checked_folders);
println!("Number of ignored files - {}", self.information.number_of_ignored_files);
println!("Number of ignored things(like symbolic links) - {}", self.information.number_of_ignored_things);
println!("Number of removed files - {}", self.information.number_of_removed_files);
println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files);
println!("### Other");
println!("Invalid symlinks list size - {}", self.invalid_symlinks.len());
println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions);
println!("Excluded items - {:?}", self.excluded_items.items);
println!("Included directories - {:?}", self.directories.included_directories);
println!("Excluded directories - {:?}", self.directories.excluded_directories);
println!("Recursive search - {}", self.recursive_search.to_string());
println!("Delete Method - {:?}", self.delete_method);
println!("-----------------------------------------");
}
}
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(),
};
let mut file = match File::create(&file_name) {
Ok(t) => t,
Err(_) => {
self.text_messages.errors.push(format!("Failed to create file {}", file_name));
return false;
}
};
if writeln!(
file,
"Results of searching {:?} with excluded directories {:?} and excluded items {:?}",
self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items
)
.is_err()
{
self.text_messages.errors.push(format!("Failed to save results to file {}", file_name));
return false;
}
if !self.invalid_symlinks.is_empty() {
writeln!(file, "Found {} invalid symlinks.", self.information.number_of_invalid_symlinks).unwrap();
for file_entry in self.invalid_symlinks.iter() {
writeln!(
file,
"{}\t\t{}\t\t{}",
file_entry.symlink_path.display(),
file_entry.destination_path.display(),
match file_entry.type_of_error {
ErrorType::InfiniteRecursion => "Infinite Recursion",
ErrorType::NonExistentFile => "Non Existent File",
}
)
.unwrap();
}
} else {
write!(file, "Not found any invalid symlinks.").unwrap();
}
Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
true
}
}
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.iter() {
println!(
"{}\t\t{}\t\t{}",
file_entry.symlink_path.display(),
file_entry.destination_path.display(),
match file_entry.type_of_error {
ErrorType::InfiniteRecursion => "Infinite Recursion",
ErrorType::NonExistentFile => "Non Existent File",
}
);
}
Common::print_time(start_time, SystemTime::now(), "print_entries".to_string());
}
}

View file

@ -13,6 +13,7 @@ pub mod common_extensions;
pub mod common_items;
pub mod common_messages;
pub mod common_traits;
pub mod invalid_symlinks;
pub mod same_music;
pub mod similar_images;
pub mod zeroed;

View file

@ -438,6 +438,66 @@ Author: Rafał Mikrut
</object>
</child>
</object>
<object class="GtkPopover" id="popover_select_very_simple_list">
<property name="can_focus">False</property>
<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="buttons_popover_very_simple_list_select_all">
<property name="label" translatable="yes">Select All</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="buttons_popover_very_simple_list_unselect_all">
<property name="label" translatable="yes">Unselect All</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">1</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="buttons_popover_very_simple_list_reverse">
<property name="label" translatable="yes">Reverse Selection</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">3</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="window_main">
<property name="can_focus">False</property>
<property name="default_width">1100</property>
@ -1906,6 +1966,30 @@ Author: Rafał Mikrut
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window_invalid_symlinks">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">8</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">Invalid Symlinks</property>
</object>
<packing>
<property name="position">8</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>

View file

@ -6,6 +6,8 @@ use std::collections::BTreeMap;
use std::fs;
use std::fs::Metadata;
// TODO add support for checking if really symlink doesn't point to correct directory/file
pub fn connect_button_delete(gui_data: &GuiData) {
let gui_data = gui_data.clone();
let buttons_delete = gui_data.buttons_delete.clone();
@ -20,6 +22,7 @@ pub fn connect_button_delete(gui_data: &GuiData) {
let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone();
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let check_button_settings_confirm_deletion = gui_data.check_button_settings_confirm_deletion.clone();
buttons_delete.connect_clicked(move |_| {
@ -80,6 +83,9 @@ pub fn connect_button_delete(gui_data: &GuiData) {
"notebook_main_same_music_finder" => {
tree_remove(scrolled_window_same_music_finder.clone(), ColumnsSameMusic::Name as i32, ColumnsSameMusic::Path as i32, ColumnsSameMusic::Color as i32, &gui_data);
}
"scrolled_window_invalid_symlinks" => {
basic_remove_invalid_symlinks(scrolled_window_invalid_symlinks.clone(), ColumnsInvalidSymlinks::SymlinkPath as i32, &gui_data);
}
e => panic!("Not existent {}", e),
}
});
@ -171,6 +177,38 @@ fn empty_folder_remover(scrolled_window: gtk::ScrolledWindow, column_file_name:
selection.unselect_all();
}
fn basic_remove_invalid_symlinks(scrolled_window: gtk::ScrolledWindow, column_symlink_path: i32, gui_data: &GuiData) {
let text_view_errors = gui_data.text_view_errors.clone();
let tree_view = scrolled_window.get_children().get(0).unwrap().clone().downcast::<gtk::TreeView>().unwrap();
let selection = tree_view.get_selection();
let (selection_rows, tree_model) = selection.get_selected_rows();
if selection_rows.is_empty() {
return;
}
let list_store = tree_model.clone().downcast::<gtk::ListStore>().unwrap();
// let new_tree_model = TreeModel::new(); // TODO - maybe create new model when inserting a new data, because this seems to be not optimal when using thousands of rows
let mut messages: String = "".to_string();
// Must be deleted from end to start, because when deleting entries, TreePath(and also TreeIter) will points to invalid data
for tree_path in selection_rows.iter().rev() {
let symlink_path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), column_symlink_path).get::<String>().unwrap().unwrap();
match fs::remove_file(&symlink_path) {
Ok(_) => {
list_store.remove(&list_store.get_iter(tree_path).unwrap());
}
Err(_) => messages += format!("Failed to remove file {} because file doesn't exists or you don't have permissions.\n", symlink_path).as_str(),
}
}
text_view_errors.get_buffer().unwrap().set_text(messages.as_str());
selection.unselect_all();
}
fn basic_remove(scrolled_window: gtk::ScrolledWindow, column_file_name: i32, column_path: i32, gui_data: &GuiData) {
let text_view_errors = gui_data.text_view_errors.clone();

View file

@ -14,6 +14,7 @@ pub fn connect_button_save(gui_data: &GuiData) {
let shared_similar_images_state = gui_data.shared_similar_images_state.clone();
let shared_same_music_state = gui_data.shared_same_music_state.clone();
let shared_zeroed_files_state = gui_data.shared_zeroed_files_state.clone();
let shared_same_invalid_symlinks = gui_data.shared_same_invalid_symlinks.clone();
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
let notebook_main = gui_data.notebook_main.clone();
buttons_save.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
@ -73,6 +74,13 @@ pub fn connect_button_save(gui_data: &GuiData) {
post_save_things(file_name, "same_music", &gui_data);
}
"scrolled_window_invalid_symlinks" => {
let file_name = "results_invalid_symlinks.txt";
shared_same_invalid_symlinks.borrow_mut().save_results_to_file(file_name);
post_save_things(file_name, "invalid_symlinks", &gui_data);
}
e => panic!("Not existent {}", e),
});
}

View file

@ -7,6 +7,7 @@ use czkawka_core::big_file::BigFile;
use czkawka_core::duplicate::DuplicateFinder;
use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::invalid_symlinks::InvalidSymlinks;
use czkawka_core::same_music::{MusicSimilarity, SameMusic};
use czkawka_core::similar_images::SimilarImages;
use czkawka_core::temporary::Temporary;
@ -14,6 +15,8 @@ use czkawka_core::zeroed::ZeroedFiles;
use glib::Sender;
use gtk::prelude::*;
use gtk::WindowPosition;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
#[allow(clippy::too_many_arguments)]
@ -28,6 +31,7 @@ pub fn connect_button_search(
futures_sender_similar_images: futures::channel::mpsc::Sender<similar_images::ProgressData>,
futures_sender_temporary: futures::channel::mpsc::Sender<temporary::ProgressData>,
futures_sender_zeroed: futures::channel::mpsc::Sender<zeroed::ProgressData>,
futures_sender_invalid_symlinks: futures::channel::mpsc::Sender<invalid_symlinks::ProgressData>,
) {
let entry_info = gui_data.entry_info.clone();
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
@ -68,6 +72,7 @@ pub fn connect_button_search(
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone();
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let text_view_errors = gui_data.text_view_errors.clone();
let dialog_progress = gui_data.dialog_progress.clone();
let label_stage = gui_data.label_stage.clone();
@ -76,6 +81,8 @@ pub fn connect_button_search(
let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone();
let image_preview_similar_images = gui_data.image_preview_similar_images.clone();
let show_dialog = Arc::new(AtomicBool::new(true));
buttons_search_clone.connect_clicked(move |_| {
let included_directories = get_string_from_list_store(&scrolled_window_included_directories);
let excluded_directories = get_string_from_list_store(&scrolled_window_excluded_directories);
@ -91,12 +98,14 @@ pub fn connect_button_search(
entry_info.set_text("Searching data, it may take a while, please wait...");
// Set dialog to center to current screen(it is impossible to center it to main window)
dialog_progress.set_position(WindowPosition::CenterOnParent);
dialog_progress.set_position(WindowPosition::CenterAlways);
// Resets progress bars
progress_bar_all_stages.set_fraction(0 as f64);
progress_bar_current_stage.set_fraction(0 as f64);
reset_text_view(&text_view_errors);
match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
label_stage.show();
@ -104,7 +113,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_duplicate_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let check_method;
if radio_button_duplicates_name.get_active() {
@ -147,7 +155,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_main_empty_files_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
@ -172,7 +179,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_main_empty_folder_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
@ -194,7 +200,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_big_files_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let numbers_of_files_to_check = match entry_big_files_number.get_text().as_str().parse::<usize>() {
Ok(t) => t,
@ -223,7 +228,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_main_temporary_files_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
@ -249,7 +253,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_similar_images_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
@ -295,7 +298,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_zeroed_files_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
@ -320,7 +322,6 @@ pub fn connect_button_search(
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_same_music_finder).clear();
text_view_errors.get_buffer().unwrap().set_text("");
let minimal_file_size = match entry_same_music_minimal_size.get_text().as_str().parse::<u64>() {
Ok(t) => t,
@ -366,12 +367,37 @@ pub fn connect_button_search(
notebook_main.set_sensitive(true);
set_buttons(&mut *shared_buttons.borrow_mut().get_mut("same_music").unwrap(), &buttons_array, &buttons_names);
entry_info.set_text("ERROR: You must select at least one checkbox with music searching types.");
show_dialog.store(false, Ordering::Relaxed);
}
}
"scrolled_window_invalid_symlinks" => {
label_stage.show();
grid_progress_stages.hide();
dialog_progress.resize(1, 1);
get_list_store(&scrolled_window_invalid_symlinks).clear();
let glib_stop_sender = glib_stop_sender.clone();
let stop_receiver = stop_receiver.clone();
let futures_sender_invalid_symlinks = futures_sender_invalid_symlinks.clone();
thread::spawn(move || {
let mut isf = InvalidSymlinks::new();
isf.set_included_directory(included_directories);
isf.set_excluded_directory(excluded_directories);
isf.set_recursive_search(recursive_search);
isf.set_excluded_items(excluded_items);
isf.find_invalid_links(Some(&stop_receiver), Some(&futures_sender_invalid_symlinks));
let _ = glib_stop_sender.send(Message::InvalidSymlinks(isf));
});
}
e => panic!("Not existent {}", e),
}
// Show progress dialog
dialog_progress.show();
if show_dialog.load(Ordering::Relaxed) {
dialog_progress.show();
}
});
}

View file

@ -8,6 +8,7 @@ pub fn connect_button_select(gui_data: &GuiData) {
let buttons_select_clone = gui_data.buttons_select.clone();
let popover_select_duplicate = gui_data.popover_select_duplicate.clone();
let popover_select_simple_list = gui_data.popover_select_simple_list.clone();
let popover_select_very_simple_list = gui_data.popover_select_very_simple_list.clone();
let buttons_select = gui_data.buttons_select.clone();
buttons_select_clone.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" | "notebook_main_same_music_finder" | "notebook_main_similar_images_finder_label" => {
@ -18,6 +19,10 @@ pub fn connect_button_select(gui_data: &GuiData) {
popover_select_simple_list.set_relative_to(Some(&buttons_select));
popover_select_simple_list.popup();
}
"scrolled_window_invalid_symlinks" => {
popover_select_very_simple_list.set_relative_to(Some(&buttons_select));
popover_select_very_simple_list.popup();
}
e => panic!("Not existent {}", e),
});
}

View file

@ -25,7 +25,9 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
let shared_empty_folders_state = gui_data.shared_empty_folders_state.clone();
let shared_empty_files_state = gui_data.shared_empty_files_state.clone();
let scrolled_window_big_files_finder = gui_data.scrolled_window_big_files_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let shared_big_files_state = gui_data.shared_big_files_state.clone();
let shared_same_invalid_symlinks = gui_data.shared_same_invalid_symlinks.clone();
let scrolled_window_main_temporary_files_finder = gui_data.scrolled_window_main_temporary_files_finder.clone();
let shared_temporary_files_state = gui_data.shared_temporary_files_state.clone();
let shared_similar_images_state = gui_data.shared_similar_images_state.clone();
@ -605,6 +607,54 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
}
}
}
Message::InvalidSymlinks(ifs) => {
if ifs.get_stopped_search() {
entry_info.set_text("Searching for invalid symlink was stopped by user");
} else {
let information = ifs.get_information();
let text_messages = ifs.get_text_messages();
let invalid_symlinks: usize = information.number_of_invalid_symlinks;
entry_info.set_text(format!("Found {} invalid symlinks.", invalid_symlinks).as_str());
// Create GUI
{
let list_store = get_list_store(&scrolled_window_invalid_symlinks);
let col_indices = [0, 1, 2, 3];
let vector = ifs.get_invalid_symlinks();
for file_entry in vector {
let values: [&dyn ToValue; 4] = [
&file_entry.symlink_path.to_string_lossy().to_string(),
&file_entry.destination_path.to_string_lossy().to_string(),
&get_text_from_invalid_symlink_cause(&file_entry.type_of_error),
&(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
print_text_messages_to_text_view(text_messages, &text_view_errors);
}
// Set state
{
*shared_same_invalid_symlinks.borrow_mut() = ifs;
if invalid_symlinks > 0 {
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("save").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("delete").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("select").unwrap() = true;
} else {
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("save").unwrap() = false;
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("delete").unwrap() = false;
*shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap().get_mut("select").unwrap() = false;
}
set_buttons(&mut *shared_buttons.borrow_mut().get_mut("invalid_symlinks").unwrap(), &buttons_array, &buttons_names);
}
}
}
}
// Returning false here would close the receiver and have senders fail
glib::Continue(true)

View file

@ -15,6 +15,16 @@ pub fn connect_notebook_tabs(gui_data: &GuiData) {
let notebook_upper = gui_data.notebook_upper.clone();
let notebook_upper_children_names = gui_data.notebook_upper_children_names.clone();
// TODO Remove this because disabling it doesn't works
// Remove on Windows last tab(Invalid symlinks) because is not available
// if cfg!(target_family = "windows") {
// for (index, widget) in notebook_main_clone.get_children().iter().enumerate() {
// if widget.get_buildable_name() == Some("scrolled_window_invalid_symlinks".to_string()) {
// notebook_main_clone.remove_page(Some(index as u32));
// }
// }
// }
notebook_main_clone.connect_switch_page(move |_, _, number| {
let page: &str;
match notebook_main_children_names.get(number as usize).unwrap().as_str() {
@ -26,6 +36,7 @@ pub fn connect_notebook_tabs(gui_data: &GuiData) {
"notebook_main_similar_images_finder_label" => page = "similar_images",
"notebook_main_zeroed_files_finder" => page = "zeroed_files",
"notebook_main_same_music_finder" => page = "same_music",
"scrolled_window_invalid_symlinks" => page = "invalid_symlinks",
e => {
panic!("Not existent page {}", e);
}

View file

@ -552,9 +552,12 @@ pub fn connect_select_all(gui_data: &GuiData) {
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let popover_select_duplicate = gui_data.popover_select_duplicate.clone();
let popover_select_simple_list = gui_data.popover_select_simple_list.clone();
let popover_select_very_simple_list = gui_data.popover_select_very_simple_list.clone();
let buttons_popover_simple_list_select_all = gui_data.buttons_popover_simple_list_select_all.clone();
let buttons_popover_very_simple_list_select_all = gui_data.buttons_popover_very_simple_list_select_all.clone();
let buttons_popover_duplicate_select_all = gui_data.buttons_popover_duplicate_select_all.clone();
buttons_popover_duplicate_select_all.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
@ -589,6 +592,15 @@ pub fn connect_select_all(gui_data: &GuiData) {
}
e => panic!("Not existent {}", e),
});
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
let notebook_main = gui_data.notebook_main.clone();
buttons_popover_very_simple_list_select_all.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"scrolled_window_invalid_symlinks" => {
popover_select_all(&popover_select_very_simple_list, &scrolled_window_invalid_symlinks);
}
e => panic!("Not existent {}", e),
});
}
pub fn connect_unselect_all(gui_data: &GuiData) {
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
@ -602,9 +614,12 @@ pub fn connect_unselect_all(gui_data: &GuiData) {
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let popover_select_duplicate = gui_data.popover_select_duplicate.clone();
let popover_select_simple_list = gui_data.popover_select_simple_list.clone();
let popover_select_very_simple_list = gui_data.popover_select_very_simple_list.clone();
let buttons_popover_simple_list_unselect_all = gui_data.buttons_popover_simple_list_unselect_all.clone();
let buttons_popover_very_simple_list_unselect_all = gui_data.buttons_popover_very_simple_list_unselect_all.clone();
let buttons_popover_duplicate_unselect_all = gui_data.buttons_popover_duplicate_unselect_all.clone();
buttons_popover_duplicate_unselect_all.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
@ -639,6 +654,15 @@ pub fn connect_unselect_all(gui_data: &GuiData) {
}
e => panic!("Not existent {}", e),
});
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
let notebook_main = gui_data.notebook_main.clone();
buttons_popover_very_simple_list_unselect_all.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"scrolled_window_invalid_symlinks" => {
popover_unselect_all(&popover_select_very_simple_list, &scrolled_window_invalid_symlinks);
}
e => panic!("Not existent {}", e),
});
}
pub fn connect_reverse(gui_data: &GuiData) {
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
@ -652,9 +676,12 @@ pub fn connect_reverse(gui_data: &GuiData) {
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_duplicate_finder = gui_data.scrolled_window_duplicate_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let popover_select_duplicate = gui_data.popover_select_duplicate.clone();
let popover_select_simple_list = gui_data.popover_select_simple_list.clone();
let popover_select_very_simple_list = gui_data.popover_select_very_simple_list.clone();
let buttons_popover_simple_list_reverse = gui_data.buttons_popover_simple_list_reverse.clone();
let buttons_popover_very_simple_list_reverse = gui_data.buttons_popover_very_simple_list_reverse.clone();
let buttons_popover_duplicate_reverse = gui_data.buttons_popover_duplicate_reverse.clone();
buttons_popover_duplicate_reverse.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_main_duplicate_finder_label" => {
@ -689,6 +716,15 @@ pub fn connect_reverse(gui_data: &GuiData) {
}
e => panic!("Not existent {}", e),
});
let notebook_main_children_names = gui_data.notebook_main_children_names.clone();
let notebook_main = gui_data.notebook_main.clone();
buttons_popover_very_simple_list_reverse.connect_clicked(move |_| match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() {
"scrolled_window_invalid_symlinks" => {
popover_reverse(&popover_select_very_simple_list, &scrolled_window_invalid_symlinks);
}
e => panic!("Not existent {}", e),
});
}
pub fn connect_all_except_oldest(gui_data: &GuiData) {

View file

@ -1,6 +1,6 @@
use crate::gui_data::GuiData;
use czkawka_core::{big_file, duplicate, empty_files, empty_folder, same_music, similar_images, temporary, zeroed};
use czkawka_core::{big_file, duplicate, empty_files, empty_folder, invalid_symlinks, same_music, similar_images, temporary, zeroed};
use futures::StreamExt;
use gtk::{LabelExt, ProgressBarExt, WidgetExt};
@ -16,6 +16,7 @@ pub fn connect_progress_window(
mut futures_receiver_similar_images: futures::channel::mpsc::Receiver<similar_images::ProgressData>,
mut futures_receiver_temporary: futures::channel::mpsc::Receiver<temporary::ProgressData>,
mut futures_receiver_zeroed: futures::channel::mpsc::Receiver<zeroed::ProgressData>,
mut futures_receiver_invalid_symlinks: futures::channel::mpsc::Receiver<invalid_symlinks::ProgressData>,
) {
let main_context = glib::MainContext::default();
@ -230,4 +231,14 @@ pub fn connect_progress_window(
};
main_context.spawn_local(future);
}
{
// Invalid Symlinks
let label_stage = gui_data.label_stage.clone();
let future = async move {
while let Some(item) = futures_receiver_invalid_symlinks.next().await {
label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str());
}
};
main_context.spawn_local(future);
}
}

View file

@ -394,3 +394,43 @@ pub fn create_tree_view_same_music(tree_view: &mut gtk::TreeView) {
tree_view.set_vexpand(true);
}
pub fn create_tree_view_invalid_symlinks(tree_view: &mut gtk::TreeView) {
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Symlink Path");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsInvalidSymlinks::SymlinkPath as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Destination Path");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsInvalidSymlinks::DestinationPath as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Type of Error");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsInvalidSymlinks::TypeOfError as i32);
tree_view.append_column(&column);
let renderer = gtk::CellRendererText::new();
let column: gtk::TreeViewColumn = TreeViewColumn::new();
column.pack_start(&renderer, true);
column.set_title("Modification Date");
column.set_resizable(true);
column.set_min_width(50);
column.add_attribute(&renderer, "text", ColumnsInvalidSymlinks::Modification as i32);
tree_view.append_column(&column);
tree_view.set_vexpand(true);
}

View file

@ -4,6 +4,7 @@ use czkawka_core::big_file::BigFile;
use czkawka_core::duplicate::DuplicateFinder;
use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::invalid_symlinks::InvalidSymlinks;
use czkawka_core::same_music::SameMusic;
use czkawka_core::similar_images::SimilarImages;
use czkawka_core::temporary::Temporary;
@ -24,9 +25,10 @@ pub struct GuiData {
pub window_main: gtk::Window,
// States
pub main_notebooks_labels: [String; 8],
pub main_notebooks_labels: [String; 9],
pub upper_notebooks_labels: [String; 5],
pub buttons_labels: [String; 5],
// Buttons state
pub shared_buttons: Rc<RefCell<HashMap<String, HashMap<String, bool>>>>,
@ -42,6 +44,7 @@ pub struct GuiData {
pub shared_similar_images_state: Rc<RefCell<SimilarImages>>,
pub shared_zeroed_files_state: Rc<RefCell<ZeroedFiles>>,
pub shared_same_music_state: Rc<RefCell<SameMusic>>,
pub shared_same_invalid_symlinks: Rc<RefCell<InvalidSymlinks>>,
//// GUI Entry
pub entry_similar_images_minimal_size: gtk::Entry,
@ -82,9 +85,14 @@ pub struct GuiData {
pub buttons_popover_simple_list_select_custom: gtk::Button,
pub buttons_popover_simple_list_unselect_custom: gtk::Button,
pub buttons_popover_very_simple_list_select_all: gtk::Button,
pub buttons_popover_very_simple_list_unselect_all: gtk::Button,
pub buttons_popover_very_simple_list_reverse: gtk::Button,
//// Popovers
pub popover_select_duplicate: gtk::Popover,
pub popover_select_simple_list: gtk::Popover,
pub popover_select_very_simple_list: gtk::Popover,
//// Check Buttons
pub check_button_recursive: gtk::CheckButton,
@ -132,6 +140,7 @@ pub struct GuiData {
pub scrolled_window_similar_images_finder: gtk::ScrolledWindow,
pub scrolled_window_zeroed_files_finder: gtk::ScrolledWindow,
pub scrolled_window_same_music_finder: gtk::ScrolledWindow,
pub scrolled_window_invalid_symlinks: gtk::ScrolledWindow,
// Upper notebook
pub scrolled_window_included_directories: gtk::ScrolledWindow,
@ -191,6 +200,7 @@ impl GuiData {
"similar_images".to_string(),
"zeroed_files".to_string(),
"same_music".to_string(),
"invalid_symlinks".to_string(),
];
let upper_notebooks_labels = [
"included_directories".to_string(),
@ -242,6 +252,7 @@ impl GuiData {
let shared_similar_images_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SimilarImages::new()));
let shared_zeroed_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(ZeroedFiles::new()));
let shared_same_music_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SameMusic::new()));
let shared_same_invalid_symlinks: Rc<RefCell<_>> = Rc::new(RefCell::new(InvalidSymlinks::new()));
////////////////////////////////////////////////////////////////////////////////////////////////
@ -287,9 +298,14 @@ impl GuiData {
let buttons_popover_simple_list_select_custom: gtk::Button = builder.get_object("buttons_popover_simple_list_select_custom").unwrap();
let buttons_popover_simple_list_unselect_custom: gtk::Button = builder.get_object("buttons_popover_simple_list_unselect_custom").unwrap();
let buttons_popover_very_simple_list_select_all: gtk::Button = builder.get_object("buttons_popover_very_simple_list_select_all").unwrap();
let buttons_popover_very_simple_list_unselect_all: gtk::Button = builder.get_object("buttons_popover_very_simple_list_unselect_all").unwrap();
let buttons_popover_very_simple_list_reverse: gtk::Button = builder.get_object("buttons_popover_very_simple_list_reverse").unwrap();
//// Popovers
let popover_select_duplicate: gtk::Popover = builder.get_object("popover_select_duplicate").unwrap();
let popover_select_simple_list: gtk::Popover = builder.get_object("popover_select_simple_list").unwrap();
let popover_select_very_simple_list: gtk::Popover = builder.get_object("popover_select_very_simple_list").unwrap();
//// Check Buttons
let check_button_recursive: gtk::CheckButton = builder.get_object("check_button_recursive").unwrap();
@ -342,6 +358,7 @@ impl GuiData {
let scrolled_window_similar_images_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_similar_images_finder").unwrap();
let scrolled_window_zeroed_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_zeroed_files_finder").unwrap();
let scrolled_window_same_music_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_same_music_finder").unwrap();
let scrolled_window_invalid_symlinks: gtk::ScrolledWindow = builder.get_object("scrolled_window_invalid_symlinks").unwrap();
// Upper notebook
let scrolled_window_included_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_included_directories").unwrap();
@ -396,6 +413,7 @@ impl GuiData {
shared_similar_images_state,
shared_zeroed_files_state,
shared_same_music_state,
shared_same_invalid_symlinks,
entry_similar_images_minimal_size,
entry_duplicate_minimal_size,
entry_allowed_extensions,
@ -428,8 +446,12 @@ impl GuiData {
buttons_popover_simple_list_reverse,
buttons_popover_simple_list_select_custom,
buttons_popover_simple_list_unselect_custom,
buttons_popover_very_simple_list_select_all,
buttons_popover_very_simple_list_unselect_all,
buttons_popover_very_simple_list_reverse,
popover_select_duplicate,
popover_select_simple_list,
popover_select_very_simple_list,
check_button_recursive,
check_button_music_title,
check_button_music_artist,
@ -460,6 +482,7 @@ impl GuiData {
scrolled_window_similar_images_finder,
scrolled_window_zeroed_files_finder,
scrolled_window_same_music_finder,
scrolled_window_invalid_symlinks,
scrolled_window_included_directories,
scrolled_window_excluded_directories,
dialog_progress,

View file

@ -3,6 +3,8 @@ use czkawka_core::common_messages::Messages;
use czkawka_core::duplicate::DuplicateFinder;
use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::invalid_symlinks;
use czkawka_core::invalid_symlinks::InvalidSymlinks;
use czkawka_core::same_music::SameMusic;
use czkawka_core::similar_images::{SimilarImages, Similarity};
use czkawka_core::temporary::Temporary;
@ -21,6 +23,7 @@ pub enum Message {
SimilarImages(SimilarImages),
ZeroedFiles(ZeroedFiles),
SameMusic(SameMusic),
InvalidSymlinks(InvalidSymlinks),
}
pub enum ColumnsDuplicates {
@ -90,6 +93,12 @@ pub enum ColumnsSameMusic {
Color,
TextColor,
}
pub enum ColumnsInvalidSymlinks {
SymlinkPath = 0,
DestinationPath,
TypeOfError,
Modification,
}
pub const TEXT_COLOR: &str = "#ffffff";
pub const MAIN_ROW_COLOR: &str = "#343434";
@ -248,6 +257,13 @@ pub fn get_text_from_similarity(similarity: &Similarity) -> &str {
}
}
pub fn get_text_from_invalid_symlink_cause(error: &invalid_symlinks::ErrorType) -> &str {
match error {
invalid_symlinks::ErrorType::InfiniteRecursion => "Infinite recursion",
invalid_symlinks::ErrorType::NonExistentFile => "Non existent destination file",
}
}
pub fn get_list_store(scrolled_window: &gtk::ScrolledWindow) -> ListStore {
let list_store = scrolled_window.get_children().get(0).unwrap().clone().downcast::<gtk::TreeView>().unwrap().get_model().unwrap().downcast::<gtk::ListStore>().unwrap();

View file

@ -26,6 +26,7 @@ pub fn initialize_gui(gui_data: &GuiData) {
let scrolled_window_big_files_finder = gui_data.scrolled_window_big_files_finder.clone();
let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone();
let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone();
let scrolled_window_invalid_symlinks = gui_data.scrolled_window_invalid_symlinks.clone();
let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone();
let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone();
let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone();
@ -277,6 +278,20 @@ pub fn initialize_gui(gui_data: &GuiData) {
scrolled_window_same_music_finder.add(&tree_view);
scrolled_window_same_music_finder.show_all();
}
// Invalid Symlinks
{
let col_types: [glib::types::Type; 4] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String, glib::types::Type::String];
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
let mut tree_view: gtk::TreeView = TreeView::with_model(&list_store);
tree_view.get_selection().set_mode(SelectionMode::Multiple);
create_tree_view_invalid_symlinks(&mut tree_view);
scrolled_window_invalid_symlinks.add(&tree_view);
scrolled_window_invalid_symlinks.show_all();
}
}
// Set Included Directory

View file

@ -76,6 +76,7 @@ fn main() {
let (futures_sender_similar_images, futures_receiver_similar_images): (futures::channel::mpsc::Sender<similar_images::ProgressData>, futures::channel::mpsc::Receiver<similar_images::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_temporary, futures_receiver_temporary): (futures::channel::mpsc::Sender<temporary::ProgressData>, futures::channel::mpsc::Receiver<temporary::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_zeroed, futures_receiver_zeroed): (futures::channel::mpsc::Sender<zeroed::ProgressData>, futures::channel::mpsc::Receiver<zeroed::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_invalid_symlinks, futures_receiver_invalid_symlinks): (futures::channel::mpsc::Sender<invalid_symlinks::ProgressData>, futures::channel::mpsc::Receiver<invalid_symlinks::ProgressData>) = futures::channel::mpsc::channel(20);
initialize_gui(&gui_data);
reset_configuration(&gui_data, false); // Fallback for invalid loading setting project
@ -94,6 +95,7 @@ fn main() {
futures_sender_similar_images,
futures_sender_temporary,
futures_sender_zeroed,
futures_sender_invalid_symlinks,
);
connect_button_select(&gui_data);
connect_button_stop(&gui_data);
@ -112,6 +114,7 @@ fn main() {
futures_receiver_similar_images,
futures_receiver_temporary,
futures_receiver_zeroed,
futures_receiver_invalid_symlinks,
);
connect_hide_text_view_errors(&gui_data);
connect_settings(&gui_data);

View file

@ -426,7 +426,7 @@ pub fn reset_configuration(gui_data: &GuiData, manual_clearing: bool) {
{
let entry_excluded_items = gui_data.entry_excluded_items.clone();
if cfg!(target_family = "unix") {
entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*/Trash/*,*/.Trash-*/*");
entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*/Trash/*,*/.Trash-*/*,*/snap/*");
}
if cfg!(target_family = "windows") {
entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*:/windows/*");