diff --git a/.github/workflows/linux_cli.yml b/.github/workflows/linux_cli.yml index 9aee9e6..6db43c6 100644 --- a/.github/workflows/linux_cli.yml +++ b/.github/workflows/linux_cli.yml @@ -38,73 +38,12 @@ jobs: path: target/release/czkawka_cli if: ${{ matrix.type == 'release' }} - # Duplicate finder checks included and excluded directories - # Others are just check delete files number - name: Linux Regression Test run: | - wget https://github.com/qarmin/czkawka/releases/download/1.1.0/TestSuite.zip - unzip TestSuite.zip -d TestSuite - python3 misc/check_results.py TestSuite 15 8 - - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -D aen -m 1024 - python3 misc/check_results.py TestSuite 7 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -D aen -m 1024 - python3 misc/check_results.py TestSuite 7 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -x TEXT -D aeo -m 1024 - python3 misc/check_results.py TestSuite 14 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -e "$(pwd)/TestSuite/SubFolder" -D aeo -m 1024 - python3 misc/check_results.py TestSuite 13 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -m 1500 -D aeo - python3 misc/check_results.py TestSuite 8 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -R -m 1024 - python3 misc/check_results.py TestSuite 15 8 - target/release/czkawka_cli dup -d "$(pwd)/TestSuite" -R -D aeo -m 1024 - python3 misc/check_results.py TestSuite 13 8 - - - target/release/czkawka_cli big -d "$(pwd)/TestSuite" - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli empty-files -d "$(pwd)/TestSuite" - python3 misc/check_results.py TestSuite 15 8 - target/release/czkawka_cli empty-files -d "$(pwd)/TestSuite" -D - python3 misc/check_results.py TestSuite 13 8 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli empty-folders -d "$(pwd)/TestSuite" - python3 misc/check_results.py TestSuite 15 8 - target/release/czkawka_cli empty-folders -d "$(pwd)/TestSuite" -D - python3 misc/check_results.py TestSuite 15 2 - - rm -rf TestSuite - unzip TestSuite.zip -d TestSuite - - target/release/czkawka_cli temp -d "$(pwd)/TestSuite" - python3 misc/check_results.py TestSuite 15 8 - target/release/czkawka_cli temp -d "$(pwd)/TestSuite" -D - python3 misc/check_results.py TestSuite 14 8 + wget https://github.com/qarmin/czkawka/releases/download/6.0.0/TestFiles.zip + cd ci_tester + cargo build --release + cd .. + + ci_tester/target/release/ci_tester target/release/czkawka_cli + if: ${{ matrix.type == 'release' }} diff --git a/.gitignore b/.gitignore index fb47305..d7dab46 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ flatpak/ *.profdata /lcov_report* /report +ci_tester/target +ci_tester/Cargo.lock +czkawka_slint_gui/Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 7e7795b..b0946f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,9 @@ members = [ "czkawka_core", "czkawka_cli", "czkawka_gui", -# "czkawka_slint_gui", ] exclude = [ + "ci_tester", "czkawka_slint_gui" ] resolver = "2" diff --git a/ci_tester/Cargo.toml b/ci_tester/Cargo.toml new file mode 100644 index 0000000..38f354a --- /dev/null +++ b/ci_tester/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ci_tester" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +state = "0.6.0" +handsome_logger = "0.8.0" +log = "0.4.20" \ No newline at end of file diff --git a/ci_tester/src/main.rs b/ci_tester/src/main.rs new file mode 100644 index 0000000..7efe1a4 --- /dev/null +++ b/ci_tester/src/main.rs @@ -0,0 +1,233 @@ +use log::info; +use std::collections::BTreeSet; +use std::fs; +use std::process::Command; +use std::process::Stdio; + +#[derive(Default, Clone, Debug)] +struct CollectedFiles { + files: BTreeSet, + folders: BTreeSet, + symlinks: BTreeSet, +} + +static CZKAWKA_PATH: state::InitCell = state::InitCell::new(); +static COLLECTED_FILES: state::InitCell = state::InitCell::new(); + +const ATTEMPTS: u32 = 10; + +// App runs - ./ci_tester PATH_TO_CZKAWKA +fn main() { + handsome_logger::init().unwrap(); + let args: Vec = std::env::args().collect(); + let path_to_czkawka = args[1].clone(); + CZKAWKA_PATH.set(path_to_czkawka); + remove_test_dir(); + run_with_good_status(&["ls"], false); + unzip_files(); + + let all_files = collect_all_files_and_dirs("TestFiles").unwrap(); + COLLECTED_FILES.set(all_files); + remove_test_dir(); + + for _ in 0..ATTEMPTS { + test_empty_files(); + test_smallest_files(); + test_biggest_files(); + test_empty_folders(); + test_temporary_files(); + test_symlinks_files(); + test_remove_duplicates_one_oldest(); + test_remove_duplicates_one_newest(); + test_remove_duplicates_all_expect_newest(); + test_remove_duplicates_all_expect_oldest(); + } + + println!("Completed checking"); +} +fn test_remove_duplicates_all_expect_oldest() { + info!("test_remove_duplicates_all_expect_oldest"); + run_test( + &["dup", "-d", "TestFiles", "-D", "AEO"], + vec!["Images/A1.jpg", "Images/A5.jpg", "Music/M1.mp3", "Music/M2.mp3", "Videos/V1.mp4", "Videos/V5.mp4"], + vec![], + vec![], + ); +} +fn test_remove_duplicates_all_expect_newest() { + info!("test_remove_duplicates_all_expect_newest"); + run_test( + &["dup", "-d", "TestFiles", "-D", "AEN"], + vec!["Images/A2.jpg", "Images/A5.jpg", "Music/M1.mp3", "Music/M5.mp3", "Videos/V1.mp4", "Videos/V2.mp4"], + vec![], + vec![], + ); +} + +fn test_remove_duplicates_one_newest() { + info!("test_remove_duplicates_one_newest"); + run_test( + &["dup", "-d", "TestFiles", "-D", "ON"], + vec!["Images/A1.jpg", "Music/M2.mp3", "Videos/V5.mp4"], + vec![], + vec![], + ); +} +fn test_remove_duplicates_one_oldest() { + info!("test_remove_duplicates_one_oldest"); + run_test( + &["dup", "-d", "TestFiles", "-D", "OO"], + vec!["Images/A2.jpg", "Music/M5.mp3", "Videos/V2.mp4"], + vec![], + vec![], + ); +} + +fn test_symlinks_files() { + info!("test_symlinks_files"); + run_test(&["symlinks", "-d", "TestFiles", "-D"], vec![], vec![], vec!["Symlinks/EmptyFiles"]); +} +fn test_temporary_files() { + info!("test_temporary_files"); + run_test(&["temp", "-d", "TestFiles", "-D"], vec!["Temporary/Boczze.cache"], vec![], vec![]); +} +fn test_empty_folders() { + info!("test_empty_folders"); + run_test( + &["empty-folders", "-d", "TestFiles", "-D"], + vec![], + vec!["EmptyFolders/One", "EmptyFolders/Two", "EmptyFolders/Two/TwoInside"], + vec![], + ); +} + +fn test_biggest_files() { + info!("test_biggest_files"); + run_test( + &["big", "-d", "TestFiles", "-n", "6", "-D"], + vec!["Music/M3.flac", "Music/M4.mp3", "Videos/V2.mp4", "Videos/V3.webm", "Videos/V1.mp4", "Videos/V5.mp4"], + vec![], + vec![], + ); +} + +fn test_smallest_files() { + info!("test_smallest_files"); + run_test( + &["big", "-d", "TestFiles", "-J", "-n", "5", "-D"], + vec!["Broken/Br.jpg", "Broken/Br.mp3", "Broken/Br.pdf", "Broken/Br.zip", "EmptyFolders/ThreeButNot/KEKEKE"], + vec![], + vec![], + ); +} + +fn test_empty_files() { + info!("test_empty_files"); + run_test(&["empty-files", "-d", "TestFiles", "-D"], vec!["EmptyFile"], vec![], vec![]); +} + +//////////////////////////////////// +//////////////////////////////////// +/////////HELPER FUNCTIONS/////////// +//////////////////////////////////// +//////////////////////////////////// + +fn run_test(arguments: &[&str], expected_files_differences: Vec<&'static str>, expected_folders_differences: Vec<&'static str>, expected_symlinks_differences: Vec<&'static str>) { + unzip_files(); + // Add path_to_czkawka to arguments + let mut all_arguments = vec![]; + all_arguments.push(CZKAWKA_PATH.get().as_str()); + all_arguments.extend_from_slice(arguments); + run_with_good_status(&all_arguments, true); + file_folder_diffs( + COLLECTED_FILES.get(), + expected_files_differences, + expected_folders_differences, + expected_symlinks_differences, + ); + + remove_test_dir(); +} +fn unzip_files() { + run_with_good_status(&["unzip", "-X", "TestFiles.zip", "-d", "TestFiles"], false); +} +fn remove_test_dir() { + let _ = fs::remove_dir_all("TestFiles"); +} + +fn run_with_good_status(str_command: &[&str], print_messages: bool) { + let mut command = Command::new(str_command[0]); + let mut com = command.args(&str_command[1..]); + if !print_messages { + com = com.stderr(Stdio::piped()).stdout(Stdio::piped()); + } + let status = com.spawn().expect("failed to execute process").wait().unwrap(); + assert!(status.success()); +} + +fn file_folder_diffs( + all_files: &CollectedFiles, + mut expected_files_differences: Vec<&'static str>, + mut expected_folders_differences: Vec<&'static str>, + mut expected_symlinks_differences: Vec<&'static str>, +) { + let current_files = collect_all_files_and_dirs("TestFiles").unwrap(); + let mut diff_files = all_files + .files + .difference(¤t_files.files) + .map(|e| e.strip_prefix("TestFiles/").unwrap().to_string()) + .collect::>(); + let mut diff_folders = all_files + .folders + .difference(¤t_files.folders) + .map(|e| e.strip_prefix("TestFiles/").unwrap().to_string()) + .collect::>(); + let mut diff_symlinks = all_files + .symlinks + .difference(¤t_files.symlinks) + .map(|e| e.strip_prefix("TestFiles/").unwrap().to_string()) + .collect::>(); + + expected_symlinks_differences.sort(); + expected_folders_differences.sort(); + expected_files_differences.sort(); + + diff_files.sort(); + diff_folders.sort(); + diff_symlinks.sort(); + + assert_eq!(diff_files, expected_files_differences); + assert_eq!(diff_folders, expected_folders_differences); + assert_eq!(diff_symlinks, expected_symlinks_differences); +} + +fn collect_all_files_and_dirs(dir: &str) -> std::io::Result { + let mut files = BTreeSet::new(); + let mut folders = BTreeSet::new(); + let mut symlinks = BTreeSet::new(); + + let mut folders_to_check = vec![dir.to_string()]; + while !folders_to_check.is_empty() { + let folder = folders_to_check.pop().unwrap(); + let rd = fs::read_dir(folder)?; + for entry in rd { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + folders.insert(path.display().to_string()); + folders_to_check.push(path.display().to_string()); + } else if path.is_symlink() { + symlinks.insert(path.display().to_string()); + } else if path.is_file() { + files.insert(path.display().to_string()); + } else { + panic!("Unknown type of file {:?}", path); + } + } + } + + folders.remove(dir); + // println!("Found {} files, {} folders and {} symlinks", files.len(), folders.len(), symlinks.len()); + Ok(CollectedFiles { files, folders, symlinks }) +} diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index 2f042e6..ae633d5 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -152,6 +152,10 @@ pub struct DuplicatesArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[clap(flatten)] pub case_sensitive_name_comparison: CaseSensitiveNameComparison, @@ -178,6 +182,10 @@ pub struct EmptyFoldersArgs { pub delete_folders: bool, #[clap(flatten)] pub file_to_save: FileToSave, + #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, #[cfg(target_family = "unix")] #[clap(flatten)] pub exclude_other_filesystems: ExcludeOtherFilesystems, @@ -202,6 +210,10 @@ pub struct BiggestFilesArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[clap(short = 'J', long, help = "Finds the smallest files instead the biggest")] pub smallest_mode: bool, @@ -227,6 +239,10 @@ pub struct EmptyFilesArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -248,6 +264,10 @@ pub struct TemporaryArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -294,6 +314,10 @@ pub struct SimilarImagesArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -348,6 +372,10 @@ pub struct SameMusicArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -389,6 +417,10 @@ pub struct InvalidSymlinksArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -412,6 +444,10 @@ pub struct BrokenFilesArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -433,6 +469,10 @@ pub struct SimilarVideosArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub allowed_extensions: AllowedExtensions, #[clap(flatten)] pub not_recursive: NotRecursive, @@ -483,6 +523,10 @@ pub struct BadExtensionsArgs { #[clap(flatten)] pub file_to_save: FileToSave, #[clap(flatten)] + pub json_compact_file_to_save: JsonCompactFileToSave, + #[clap(flatten)] + pub json_pretty_file_to_save: JsonPrettyFileToSave, + #[clap(flatten)] pub not_recursive: NotRecursive, #[cfg(target_family = "unix")] #[clap(flatten)] @@ -555,10 +599,22 @@ pub struct ExcludeOtherFilesystems { #[derive(Debug, clap::Args)] pub struct FileToSave { - #[clap(short, long, value_name = "file-name", help = "Saves the results into the file")] + #[clap(short, long, value_name = "file-name", help = "Saves the results into the formatted txt file")] pub file_to_save: Option, } +#[derive(Debug, clap::Args)] +pub struct JsonCompactFileToSave { + #[clap(short, long, value_name = "json-file-name", help = "Saves the results into the compact json file")] + pub compact_file_to_save: Option, +} + +#[derive(Debug, clap::Args)] +pub struct JsonPrettyFileToSave { + #[clap(short, long, value_name = "pretty-json-file-name", help = "Saves the results into the pretty json file")] + pub pretty_file_to_save: Option, +} + #[derive(Debug, clap::Args)] pub struct AllowHardLinks { #[clap(short = 'L', long, help = "Do not ignore hard links")] @@ -586,6 +642,24 @@ impl FileToSave { None } } +impl JsonCompactFileToSave { + pub fn file_name(&self) -> Option<&str> { + if let Some(file_name) = &self.compact_file_to_save { + return file_name.to_str(); + } + + None + } +} +impl JsonPrettyFileToSave { + pub fn file_name(&self) -> Option<&str> { + if let Some(file_name) = &self.pretty_file_to_save { + return file_name.to_str(); + } + + None + } +} fn parse_hash_type(src: &str) -> Result { match src.to_ascii_lowercase().as_str() { diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index d31cb68..29c6228 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -69,6 +69,8 @@ fn duplicates(duplicates: DuplicatesArgs) { delete_method, hash_type, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -79,37 +81,33 @@ fn duplicates(duplicates: DuplicatesArgs) { set_number_of_threads(thread_number.thread_number); - let mut df = DuplicateFinder::new(); + let mut item = DuplicateFinder::new(); - df.set_included_directory(directories.directories); - df.set_excluded_directory(excluded_directories.excluded_directories); - df.set_excluded_items(excluded_items.excluded_items); - df.set_minimal_file_size(minimal_file_size); - df.set_maximal_file_size(maximal_file_size); - df.set_minimal_cache_file_size(minimal_cached_file_size); - df.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - df.set_check_method(search_method); - df.set_delete_method(delete_method); - df.set_hash_type(hash_type); - df.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_minimal_file_size(minimal_file_size); + item.set_maximal_file_size(maximal_file_size); + item.set_minimal_cache_file_size(minimal_cached_file_size); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_check_method(search_method); + item.set_delete_method(delete_method); + item.set_hash_type(hash_type); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - df.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - df.set_ignore_hard_links(!allow_hard_links.allow_hard_links); - df.set_dryrun(dryrun.dryrun); - df.set_case_sensitive_name_comparison(case_sensitive_name_comparison.case_sensitive_name_comparison); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_ignore_hard_links(!allow_hard_links.allow_hard_links); + item.set_dryrun(dryrun.dryrun); + item.set_case_sensitive_name_comparison(case_sensitive_name_comparison.case_sensitive_name_comparison); - df.find_duplicates(None, None); + item.find_duplicates(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = df.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - df.print_results_to_output(); + item.print_results_to_output(); } - df.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn empty_folders(empty_folders: EmptyFoldersArgs) { @@ -118,6 +116,8 @@ fn empty_folders(empty_folders: EmptyFoldersArgs) { directories, delete_folders, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, excluded_directories, excluded_items, #[cfg(target_family = "unix")] @@ -126,27 +126,23 @@ fn empty_folders(empty_folders: EmptyFoldersArgs) { set_number_of_threads(thread_number.thread_number); - let mut ef = EmptyFolder::new(); + let mut item = EmptyFolder::new(); - ef.set_included_directory(directories.directories); - ef.set_excluded_directory(excluded_directories.excluded_directories); - ef.set_excluded_items(excluded_items.excluded_items); - ef.set_delete_folder(delete_folders); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_delete_folder(delete_folders); #[cfg(target_family = "unix")] - ef.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - ef.find_empty_folders(None, None); + item.find_empty_folders(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = ef.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - ef.print_results_to_output(); + item.print_results_to_output(); } - ef.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn biggest_files(biggest_files: BiggestFilesArgs) { @@ -158,6 +154,8 @@ fn biggest_files(biggest_files: BiggestFilesArgs) { allowed_extensions, number_of_files, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -167,35 +165,31 @@ fn biggest_files(biggest_files: BiggestFilesArgs) { set_number_of_threads(thread_number.thread_number); - let mut bf = BigFile::new(); + let mut item = BigFile::new(); - bf.set_included_directory(directories.directories); - bf.set_excluded_directory(excluded_directories.excluded_directories); - bf.set_excluded_items(excluded_items.excluded_items); - bf.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - bf.set_number_of_files_to_check(number_of_files); - bf.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_number_of_files_to_check(number_of_files); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - bf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - bf.set_delete_method(DeleteMethod::Delete); + item.set_delete_method(DeleteMethod::Delete); } if smallest_mode { - bf.set_search_mode(SearchMode::SmallestFiles); + item.set_search_mode(SearchMode::SmallestFiles); } - bf.find_big_files(None, None); + item.find_big_files(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = bf.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - bf.print_results_to_output(); + item.print_results_to_output(); } - bf.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn empty_files(empty_files: EmptyFilesArgs) { @@ -207,6 +201,8 @@ fn empty_files(empty_files: EmptyFilesArgs) { allowed_extensions, delete_files, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -214,32 +210,28 @@ fn empty_files(empty_files: EmptyFilesArgs) { set_number_of_threads(thread_number.thread_number); - let mut ef = EmptyFiles::new(); + let mut item = EmptyFiles::new(); - ef.set_included_directory(directories.directories); - ef.set_excluded_directory(excluded_directories.excluded_directories); - ef.set_excluded_items(excluded_items.excluded_items); - ef.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - ef.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - ef.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - ef.set_delete_method(DeleteMethod::Delete); + item.set_delete_method(DeleteMethod::Delete); } - ef.find_empty_files(None, None); + item.find_empty_files(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = ef.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - ef.print_results_to_output(); + item.print_results_to_output(); } - ef.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn temporary(temporary: TemporaryArgs) { @@ -252,36 +244,34 @@ fn temporary(temporary: TemporaryArgs) { exclude_other_filesystems, delete_files, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, } = temporary; set_number_of_threads(thread_number.thread_number); - let mut tf = Temporary::new(); + let mut item = Temporary::new(); - tf.set_included_directory(directories.directories); - tf.set_excluded_directory(excluded_directories.excluded_directories); - tf.set_excluded_items(excluded_items.excluded_items); - tf.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - tf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - tf.set_delete_method(DeleteMethod::Delete); + item.set_delete_method(DeleteMethod::Delete); } - tf.find_temporary_files(None, None); + item.find_temporary_files(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = tf.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - tf.print_results_to_output(); + item.print_results_to_output(); } - tf.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn similar_images(similar_images: SimilarImagesArgs) { @@ -291,6 +281,8 @@ fn similar_images(similar_images: SimilarImagesArgs) { excluded_directories, excluded_items, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, minimal_file_size, maximal_file_size, similarity_preset, @@ -304,34 +296,30 @@ fn similar_images(similar_images: SimilarImagesArgs) { set_number_of_threads(thread_number.thread_number); - let mut sf = SimilarImages::new(); + let mut item = SimilarImages::new(); - sf.set_included_directory(directories.directories); - sf.set_excluded_directory(excluded_directories.excluded_directories); - sf.set_excluded_items(excluded_items.excluded_items); - sf.set_minimal_file_size(minimal_file_size); - sf.set_maximal_file_size(maximal_file_size); - sf.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_minimal_file_size(minimal_file_size); + item.set_maximal_file_size(maximal_file_size); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - sf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - sf.set_image_filter(image_filter); - sf.set_hash_alg(hash_alg); - sf.set_hash_size(hash_size); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_image_filter(image_filter); + item.set_hash_alg(hash_alg); + item.set_hash_size(hash_size); - sf.set_similarity(return_similarity_from_similarity_preset(&similarity_preset, hash_size)); + item.set_similarity(return_similarity_from_similarity_preset(&similarity_preset, hash_size)); - sf.find_similar_images(None, None); + item.find_similar_images(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = sf.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - sf.print_results_to_output(); + item.print_results_to_output(); } - sf.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn same_music(same_music: SameMusicArgs) { @@ -342,6 +330,8 @@ fn same_music(same_music: SameMusicArgs) { excluded_items, // delete_files, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -352,34 +342,30 @@ fn same_music(same_music: SameMusicArgs) { set_number_of_threads(thread_number.thread_number); - let mut mf = SameMusic::new(); + let mut item = SameMusic::new(); - mf.set_included_directory(directories.directories); - mf.set_excluded_directory(excluded_directories.excluded_directories); - mf.set_excluded_items(excluded_items.excluded_items); - mf.set_minimal_file_size(minimal_file_size); - mf.set_maximal_file_size(maximal_file_size); - mf.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_minimal_file_size(minimal_file_size); + item.set_maximal_file_size(maximal_file_size); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - mf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - mf.set_music_similarity(music_similarity); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_music_similarity(music_similarity); // if delete_files { - // // TODO mf.set_delete_method(same_music::DeleteMethod::Delete); + // // TODO item.set_delete_method(same_music::DeleteMethod::Delete); // } - mf.find_same_music(None, None); + item.find_same_music(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = mf.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - mf.print_results_to_output(); + item.print_results_to_output(); } - mf.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn invalid_symlinks(invalid_symlinks: InvalidSymlinksArgs) { @@ -390,6 +376,8 @@ fn invalid_symlinks(invalid_symlinks: InvalidSymlinksArgs) { excluded_items, allowed_extensions, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -398,31 +386,27 @@ fn invalid_symlinks(invalid_symlinks: InvalidSymlinksArgs) { set_number_of_threads(thread_number.thread_number); - let mut ifs = InvalidSymlinks::new(); + let mut item = InvalidSymlinks::new(); - ifs.set_included_directory(directories.directories); - ifs.set_excluded_directory(excluded_directories.excluded_directories); - ifs.set_excluded_items(excluded_items.excluded_items); - ifs.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - ifs.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - ifs.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - ifs.set_delete_method(DeleteMethod::Delete); + item.set_delete_method(DeleteMethod::Delete); } - ifs.find_invalid_links(None, None); + item.find_invalid_links(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = ifs.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - ifs.print_results_to_output(); + item.print_results_to_output(); } - ifs.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn broken_files(broken_files: BrokenFilesArgs) { @@ -434,6 +418,8 @@ fn broken_files(broken_files: BrokenFilesArgs) { allowed_extensions, delete_files, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -441,32 +427,28 @@ fn broken_files(broken_files: BrokenFilesArgs) { set_number_of_threads(thread_number.thread_number); - let mut br = BrokenFiles::new(); + let mut item = BrokenFiles::new(); - br.set_included_directory(directories.directories); - br.set_excluded_directory(excluded_directories.excluded_directories); - br.set_excluded_items(excluded_items.excluded_items); - br.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - br.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - br.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - br.set_delete_method(DeleteMethod::Delete); + item.set_delete_method(DeleteMethod::Delete); } - br.find_broken_files(None, None); + item.find_broken_files(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = br.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - br.print_results_to_output(); + item.print_results_to_output(); } - br.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn similar_videos(similar_videos: SimilarVideosArgs) { @@ -476,6 +458,8 @@ fn similar_videos(similar_videos: SimilarVideosArgs) { excluded_directories, excluded_items, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -487,31 +471,27 @@ fn similar_videos(similar_videos: SimilarVideosArgs) { set_number_of_threads(thread_number.thread_number); - let mut vr = SimilarVideos::new(); + let mut item = SimilarVideos::new(); - vr.set_included_directory(directories.directories); - vr.set_excluded_directory(excluded_directories.excluded_directories); - vr.set_excluded_items(excluded_items.excluded_items); - vr.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - vr.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - vr.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - vr.set_minimal_file_size(minimal_file_size); - vr.set_maximal_file_size(maximal_file_size); - vr.set_tolerance(tolerance); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_minimal_file_size(minimal_file_size); + item.set_maximal_file_size(maximal_file_size); + item.set_tolerance(tolerance); - vr.find_similar_videos(None, None); + item.find_similar_videos(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = vr.print_results_to_file(file_name) { - error!("Failed to save results to file {e}"); - } - } + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); if !cfg!(debug_assertions) { - vr.print_results_to_output(); + item.print_results_to_output(); } - vr.get_text_messages().print_messages(); + item.get_text_messages().print_messages(); } fn bad_extensions(bad_extensions: BadExtensionsArgs) { @@ -521,6 +501,8 @@ fn bad_extensions(bad_extensions: BadExtensionsArgs) { excluded_directories, excluded_items, file_to_save, + json_compact_file_to_save, + json_pretty_file_to_save, not_recursive, #[cfg(target_family = "unix")] exclude_other_filesystems, @@ -529,26 +511,40 @@ fn bad_extensions(bad_extensions: BadExtensionsArgs) { set_number_of_threads(thread_number.thread_number); - let mut be = BadExtensions::new(); + let mut item = BadExtensions::new(); - be.set_included_directory(directories.directories); - be.set_excluded_directory(excluded_directories.excluded_directories); - be.set_excluded_items(excluded_items.excluded_items); - be.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); - be.set_recursive_search(!not_recursive.not_recursive); + item.set_included_directory(directories.directories); + item.set_excluded_directory(excluded_directories.excluded_directories); + item.set_excluded_items(excluded_items.excluded_items); + item.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); + item.set_recursive_search(!not_recursive.not_recursive); #[cfg(target_family = "unix")] - be.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + item.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); - be.find_bad_extensions_files(None, None); + item.find_bad_extensions_files(None, None); - if let Some(file_name) = file_to_save.file_name() { - if let Err(e) = be.print_results_to_file(file_name) { + save_results_to_files(file_to_save.file_name(), json_compact_file_to_save.file_name(), json_pretty_file_to_save.file_name(), &item); + + if !cfg!(debug_assertions) { + item.print_results_to_output(); + } + item.get_text_messages().print_messages(); +} + +fn save_results_to_files(txt_file_name: Option<&str>, compact_json_file_name: Option<&str>, pretty_json_file_name: Option<&str>, item: &T) { + if let Some(file_name) = txt_file_name { + if let Err(e) = item.print_results_to_file(file_name) { error!("Failed to save results to file {e}"); } } - - if !cfg!(debug_assertions) { - be.print_results_to_output(); + if let Some(file_name) = compact_json_file_name { + if let Err(e) = item.save_results_to_file_as_json(file_name, false) { + error!("Failed to save compact json results to file {e}"); + } + } + if let Some(file_name) = pretty_json_file_name { + if let Err(e) = item.save_results_to_file_as_json(file_name, true) { + error!("Failed to save pretty json results to file {e}"); + } } - be.get_text_messages().print_messages(); } diff --git a/czkawka_core/src/bad_extensions.rs b/czkawka_core/src/bad_extensions.rs index d018847..1b5c973 100644 --- a/czkawka_core/src/bad_extensions.rs +++ b/czkawka_core/src/bad_extensions.rs @@ -11,6 +11,7 @@ use futures::channel::mpsc::UnboundedSender; use log::debug; use mime_guess::get_mime_extensions; use rayon::prelude::*; +use serde::Serialize; use crate::common::{prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads}; use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData, ToolType}; @@ -158,7 +159,7 @@ const WORKAROUNDS: &[(&str, &str)] = &[ ("exe", "xls"), // Not sure why xls is not recognized ]; -#[derive(Clone)] +#[derive(Clone, Serialize)] pub struct BadFileEntry { pub path: PathBuf, pub modified_date: u64, @@ -426,6 +427,10 @@ impl PrintResults for BadExtensions { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.bad_extensions_files, pretty_print) + } } impl BadExtensions { diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index 33ade77..62c6107 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -12,13 +12,14 @@ use futures::channel::mpsc::UnboundedSender; use humansize::{format_size, BINARY}; use log::debug; use rayon::prelude::*; +use serde::{Deserialize, Serialize}; use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, split_path}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::{DebugPrint, PrintResults}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileEntry { pub path: PathBuf, pub size: u64, @@ -39,7 +40,7 @@ pub struct Info { pub struct BigFile { common_data: CommonToolData, information: Info, - big_files: Vec<(u64, FileEntry)>, + big_files: Vec, number_of_files_to_check: usize, search_mode: SearchMode, } @@ -189,7 +190,7 @@ impl BigFile { iter = Box::new(old_map.into_iter().rev()); } - for (size, mut vector) in iter { + for (_size, mut vector) in iter { if self.information.number_of_real_files < self.number_of_files_to_check { if vector.len() > 1 { vector.sort_unstable_by_key(|e| { @@ -199,7 +200,7 @@ impl BigFile { } for file in vector { if self.information.number_of_real_files < self.number_of_files_to_check { - self.big_files.push((size, file)); + self.big_files.push(file); self.information.number_of_real_files += 1; } else { break; @@ -214,7 +215,7 @@ impl BigFile { fn delete_files(&mut self) { match self.common_data.delete_method { DeleteMethod::Delete => { - for (_, file_entry) in &self.big_files { + for file_entry in &self.big_files { if fs::remove_file(&file_entry.path).is_err() { self.common_data.text_messages.warnings.push(file_entry.path.display().to_string()); } @@ -262,8 +263,8 @@ impl PrintResults for BigFile { } else { writeln!(writer, "{} the smallest files.\n\n", self.information.number_of_real_files)?; } - for (size, file_entry) in &self.big_files { - writeln!(writer, "{} ({}) - {}", format_size(*size, BINARY), size, file_entry.path.display())?; + for file_entry in &self.big_files { + writeln!(writer, "{} ({}) - {}", format_size(file_entry.size, BINARY), file_entry.size, file_entry.path.display())?; } } else { write!(writer, "Not found any files.").unwrap(); @@ -271,6 +272,10 @@ impl PrintResults for BigFile { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.big_files, pretty_print) + } } impl CommonData for BigFile { @@ -287,7 +292,7 @@ impl BigFile { self.search_mode = search_mode; } - pub const fn get_big_files(&self) -> &Vec<(u64, FileEntry)> { + pub const fn get_big_files(&self) -> &Vec { &self.big_files } diff --git a/czkawka_core/src/broken_files.rs b/czkawka_core/src/broken_files.rs index 9ee8ad9..63ec060 100644 --- a/czkawka_core/src/broken_files.rs +++ b/czkawka_core/src/broken_files.rs @@ -481,6 +481,10 @@ impl PrintResults for BrokenFiles { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.broken_files, pretty_print) + } } fn check_extension_availability(file_name_lowercase: &str) -> TypeOfFile { diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index f113a9c..9c6eb8c 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -37,7 +37,7 @@ impl Directories { let directories: Vec = included_directory; let mut checked_directories: Vec = Vec::new(); - for directory in directories { + for mut directory in directories { if directory.to_string_lossy().contains('*') { messages.warnings.push(flc!( "core_directory_wildcard_no_supported", @@ -46,23 +46,6 @@ impl Directories { continue; } - #[cfg(not(target_family = "windows"))] - if directory.is_relative() { - messages.warnings.push(flc!( - "core_directory_relative_path", - generate_translation_hashmap(vec![("path", directory.display().to_string())]) - )); - continue; - } - #[cfg(target_family = "windows")] - if directory.is_relative() && !directory.starts_with("\\") { - messages.warnings.push(flc!( - "core_directory_relative_path", - generate_translation_hashmap(vec![("path", directory.display().to_string())]) - )); - continue; - } - if !directory.exists() { messages.warnings.push(flc!( "core_directory_must_exists", @@ -77,6 +60,20 @@ impl Directories { )); continue; } + + // If not checking windows strange paths, try to canonicalize them + if !directory.starts_with("\\") { + let Ok(dir2) = directory.canonicalize() else { + messages.warnings.push(flc!( + "core_directory_must_exists", + generate_translation_hashmap(vec![("path", directory.display().to_string())]) + )); + continue; + }; + + directory = dir2; + } + checked_directories.push(directory); } diff --git a/czkawka_core/src/common_traits.rs b/czkawka_core/src/common_traits.rs index 0ba9ebc..1397ced 100644 --- a/czkawka_core/src/common_traits.rs +++ b/czkawka_core/src/common_traits.rs @@ -1,4 +1,5 @@ use fun_time::fun_time; +use serde::Serialize; use std::fs::File; use std::io::{BufWriter, Write}; use std::path::Path; @@ -31,6 +32,39 @@ pub trait PrintResults { writer.flush()?; Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()>; + + fn save_results_to_file_as_json_internal(&self, file_name: &str, item_to_serialize: &T, pretty_print: bool) -> std::io::Result<()> { + if pretty_print { + self.save_results_to_file_as_json_pretty(file_name, item_to_serialize) + } else { + self.save_results_to_file_as_json_compact(file_name, item_to_serialize) + } + } + + #[fun_time(message = "save_results_to_file_as_json_pretty")] + fn save_results_to_file_as_json_pretty(&self, file_name: &str, item_to_serialize: &T) -> std::io::Result<()> { + let file_handler = File::create(file_name)?; + let mut writer = BufWriter::new(file_handler); + serde_json::to_writer_pretty(&mut writer, item_to_serialize)?; + Ok(()) + } + + #[fun_time(message = "save_results_to_file_as_json_compact")] + fn save_results_to_file_as_json_compact(&self, file_name: &str, item_to_serialize: &T) -> std::io::Result<()> { + let file_handler = File::create(file_name)?; + let mut writer = BufWriter::new(file_handler); + serde_json::to_writer(&mut writer, item_to_serialize)?; + Ok(()) + } + + fn save_all_in_one(&self, file_name: &str) -> std::io::Result<()> { + self.save_results_to_file_as_json(&format!("{file_name}_pretty.json"), true)?; + self.save_results_to_file_as_json(&format!("{file_name}_compact.json"), false)?; + self.print_results_to_file(&format!("{file_name}.txt"))?; + Ok(()) + } } pub trait ResultEntry { diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index 48c1463..cf14bd7 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, HashSet}; +use std::fmt::Debug; use std::fs::File; use std::hash::Hasher; use std::io::prelude::*; @@ -813,31 +814,116 @@ impl DuplicateFinder { match self.check_method { CheckingMethod::Name => { - for vector in self.files_with_identical_names.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); - } + let vec_files = self.files_with_identical_names.values().collect::>(); + delete_files(&vec_files, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); } CheckingMethod::SizeName => { - for vector in self.files_with_identical_size_names.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); - } + let vec_files = self.files_with_identical_size_names.values().collect::>(); + delete_files(&vec_files, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); } CheckingMethod::Hash => { - for vector_vectors in self.files_with_identical_hashes.values() { - for vector in vector_vectors { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); - } + for vec_files in self.files_with_identical_hashes.values() { + let vev: Vec<&Vec> = vec_files.iter().collect::>(); + delete_files(&vev, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); } } CheckingMethod::Size => { - for vector in self.files_with_identical_size.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); - } + let vec_files = self.files_with_identical_size.values().collect::>(); + delete_files(&vec_files, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); } _ => panic!(), } } } +// Here we assume, that internal Vec<> have at least 1 object +#[allow(clippy::ptr_arg)] +fn delete_files(items: &Vec<&Vec>, delete_method: &DeleteMethod, text_messages: &mut Messages, dryrun: bool) -> (u64, usize, usize) { + let res = items + .iter() + .map(|values| { + let mut gained_space: u64 = 0; + let mut removed_files: usize = 0; + let mut failed_to_remove_files: usize = 0; + let mut infos = Vec::new(); + let mut errors = Vec::new(); + + let mut all_values = (*values).clone(); + let len = all_values.len(); + + // Sorted from oldest to newest - from smallest value to bigger + all_values.sort_unstable_by_key(ResultEntry::get_modified_date); + + if delete_method == &DeleteMethod::HardLink { + let original_file = &all_values[0]; + for file_entry in &all_values[1..] { + if dryrun { + infos.push(format!( + "Dryrun - would create hardlink from {:?} to {:?}", + original_file.get_path(), + original_file.get_path() + )); + } else { + if dryrun { + infos.push(format!("Replace file {:?} with hard link to {:?}", original_file.get_path(), file_entry.get_path())); + } else { + if let Err(e) = make_hard_link(original_file.get_path(), file_entry.get_path()) { + errors.push(format!( + "Cannot create hard link from {:?} to {:?} - {}", + file_entry.get_path(), + original_file.get_path(), + e + )); + failed_to_remove_files += 1; + } else { + gained_space += 1; + removed_files += 1; + } + } + } + } + + return (infos, errors, gained_space, removed_files, failed_to_remove_files); + } + + let items = match delete_method { + DeleteMethod::Delete => &all_values, + DeleteMethod::AllExceptNewest => &all_values[..(len - 1)], + DeleteMethod::AllExceptOldest => &all_values[1..], + DeleteMethod::OneOldest => &all_values[..1], + DeleteMethod::OneNewest => &all_values[(len - 1)..], + DeleteMethod::HardLink | DeleteMethod::None => unreachable!("HardLink and None should be handled before"), + }; + + for i in items { + if dryrun { + infos.push(format!("Dryrun - would delete file: {:?}", i.get_path())); + } else { + if let Err(e) = std::fs::remove_file(i.get_path()) { + errors.push(format!("Cannot delete file: {:?} - {e}", i.get_path())); + failed_to_remove_files += 1; + } else { + removed_files += 1; + gained_space += i.get_size(); + } + } + } + (infos, errors, gained_space, removed_files, failed_to_remove_files) + }) + .collect::>(); + + let mut gained_space = 0; + let mut removed_files = 0; + let mut failed_to_remove_files = 0; + for (infos, errors, gained_space_v, removed_files_v, failed_to_remove_files_v) in res { + text_messages.messages.extend(infos); + text_messages.errors.extend(errors); + gained_space += gained_space_v; + removed_files += removed_files_v; + failed_to_remove_files += failed_to_remove_files_v; + } + + (gained_space, removed_files, failed_to_remove_files) +} impl DuplicateFinder { pub fn set_case_sensitive_name_comparison(&mut self, case_sensitive_name_comparison: bool) { @@ -989,6 +1075,24 @@ impl PrintResults for DuplicateFinder { } writeln!(writer)?; } + } else if !self.files_with_identical_names_referenced.is_empty() { + writeln!( + writer, + "-------------------------------------------------Files with same names in referenced folders-------------------------------------------------" + )?; + writeln!( + writer, + "Found {} files in {} groups with same name(may have different content)", + self.information.number_of_duplicated_files_by_name, self.information.number_of_groups_by_name, + )?; + for (name, (file_entry, vector)) in self.files_with_identical_names_referenced.iter().rev() { + writeln!(writer, "Name - {} - {} files ", name, vector.len())?; + writeln!(writer, "Reference file - {}", file_entry.path.display())?; + for j in vector { + writeln!(writer, "{}", j.path.display())?; + } + writeln!(writer)?; + } } else { write!(writer, "Not found any files with same names.")?; } @@ -1011,6 +1115,24 @@ impl PrintResults for DuplicateFinder { } writeln!(writer)?; } + } else if !self.files_with_identical_names_referenced.is_empty() { + writeln!( + writer, + "-------------------------------------------------Files with same size and names in referenced folders-------------------------------------------------" + )?; + writeln!( + writer, + "Found {} files in {} groups with same size and name(may have different content)", + self.information.number_of_duplicated_files_by_size_name, self.information.number_of_groups_by_size_name, + )?; + for ((size, name), (file_entry, vector)) in self.files_with_identical_size_names_referenced.iter().rev() { + writeln!(writer, "Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len())?; + writeln!(writer, "Reference file - {}", file_entry.path.display())?; + for j in vector { + writeln!(writer, "{}", j.path.display())?; + } + writeln!(writer)?; + } } else { write!(writer, "Not found any files with same size and names.")?; } @@ -1034,6 +1156,25 @@ impl PrintResults for DuplicateFinder { writeln!(writer, "{}", file_entry.path.display())?; } } + } else if !self.files_with_identical_size_referenced.is_empty() { + writeln!( + writer, + "-------------------------------------------------Files with same size in referenced folders-------------------------------------------------" + )?; + writeln!( + writer, + "Found {} duplicated files which in {} groups which takes {}.", + self.information.number_of_duplicated_files_by_size, + self.information.number_of_groups_by_size, + format_size(self.information.lost_space_by_size, BINARY) + )?; + for (size, (file_entry, vector)) in self.files_with_identical_size_referenced.iter().rev() { + writeln!(writer, "\n---- Size {} ({}) - {} files", format_size(*size, BINARY), size, vector.len())?; + writeln!(writer, "Reference file - {}", file_entry.path.display())?; + for file_entry in vector { + writeln!(writer, "{}", file_entry.path.display())?; + } + } } else { write!(writer, "Not found any duplicates.")?; } @@ -1059,6 +1200,27 @@ impl PrintResults for DuplicateFinder { } } } + } else if !self.files_with_identical_hashes_referenced.is_empty() { + writeln!( + writer, + "-------------------------------------------------Files with same hashes in referenced folders-------------------------------------------------" + )?; + writeln!( + writer, + "Found {} duplicated files which in {} groups which takes {}.", + self.information.number_of_duplicated_files_by_hash, + self.information.number_of_groups_by_hash, + format_size(self.information.lost_space_by_hash, BINARY) + )?; + for (size, vectors_vector) in self.files_with_identical_hashes_referenced.iter().rev() { + for (file_entry, vector) in vectors_vector { + writeln!(writer, "\n---- Size {} ({}) - {} files", format_size(*size, BINARY), size, vector.len())?; + writeln!(writer, "Reference file - {}", file_entry.path.display())?; + for file_entry in vector { + writeln!(writer, "{}", file_entry.path.display())?; + } + } + } } else { write!(writer, "Not found any duplicates.")?; } @@ -1068,71 +1230,26 @@ impl PrintResults for DuplicateFinder { Ok(()) } -} -fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, text_messages: &mut Messages, dryrun: bool) -> (u64, usize, usize) { - assert!(vector.len() > 1, "Vector length must be bigger than 1(This should be done in previous steps)."); - let mut gained_space: u64 = 0; - let mut removed_files: usize = 0; - let mut failed_to_remove_files: usize = 0; - let mut values = vector.iter().enumerate(); - let q_index = match delete_method { - DeleteMethod::OneOldest | DeleteMethod::AllExceptNewest => values.max_by(|(_, l), (_, r)| l.modified_date.cmp(&r.modified_date)), - DeleteMethod::OneNewest | DeleteMethod::AllExceptOldest | DeleteMethod::HardLink => values.min_by(|(_, l), (_, r)| l.modified_date.cmp(&r.modified_date)), - DeleteMethod::None => values.next(), - _ => unreachable!(), - }; - let q_index = q_index.map_or(0, |t| t.0); - let n = match delete_method { - DeleteMethod::OneNewest | DeleteMethod::OneOldest => 1, - DeleteMethod::AllExceptNewest | DeleteMethod::AllExceptOldest | DeleteMethod::None | DeleteMethod::HardLink => usize::MAX, - _ => unreachable!(), - }; - for (index, file) in vector.iter().enumerate() { - if q_index == index { - continue; - } - if removed_files + failed_to_remove_files >= n { - break; - } - - let r = match delete_method { - DeleteMethod::OneOldest | DeleteMethod::OneNewest | DeleteMethod::AllExceptOldest | DeleteMethod::AllExceptNewest => { - if dryrun { - Ok(Some(format!("Delete {}", file.path.display()))) - } else { - fs::remove_file(&file.path).map(|()| None) - } + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> io::Result<()> { + if self.get_use_reference() { + match self.check_method { + CheckingMethod::Name => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_names_referenced, pretty_print), + CheckingMethod::SizeName => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_size_names_referenced, pretty_print), + CheckingMethod::Size => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_size_referenced, pretty_print), + CheckingMethod::Hash => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_hashes_referenced, pretty_print), + _ => panic!(), } - DeleteMethod::HardLink => { - let src = &vector[q_index].path; - if dryrun { - Ok(Some(format!("Replace file {} with hard link to {}", file.path.display(), src.display()))) - } else { - make_hard_link(src, &file.path).map(|()| None) - } - } - DeleteMethod::None => Ok(None), - _ => unreachable!(), - }; - - match r { - Err(e) => { - failed_to_remove_files += 1; - text_messages.warnings.push(format!("Failed to remove {} ({})", file.path.display(), e)); - } - Ok(Some(msg)) => { - text_messages.messages.push(msg); - removed_files += 1; - gained_space += file.size; - } - Ok(None) => { - removed_files += 1; - gained_space += file.size; + } else { + match self.check_method { + CheckingMethod::Name => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_names, pretty_print), + CheckingMethod::SizeName => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_size_names, pretty_print), + CheckingMethod::Size => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_size, pretty_print), + CheckingMethod::Hash => self.save_results_to_file_as_json_internal(file_name, &self.files_with_identical_hashes, pretty_print), + _ => panic!(), } } } - (gained_space, removed_files, failed_to_remove_files) } #[cfg(target_family = "windows")] diff --git a/czkawka_core/src/empty_files.rs b/czkawka_core/src/empty_files.rs index f71e053..df9587c 100644 --- a/czkawka_core/src/empty_files.rs +++ b/czkawka_core/src/empty_files.rs @@ -141,6 +141,10 @@ impl PrintResults for EmptyFiles { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.empty_files, pretty_print) + } } impl EmptyFiles { diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index 7cc130b..11f14f5 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -158,6 +158,10 @@ impl PrintResults for EmptyFolder { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.empty_folder_list.keys().collect::>(), pretty_print) + } } impl CommonData for EmptyFolder { diff --git a/czkawka_core/src/invalid_symlinks.rs b/czkawka_core/src/invalid_symlinks.rs index 118222c..a51708f 100644 --- a/czkawka_core/src/invalid_symlinks.rs +++ b/czkawka_core/src/invalid_symlinks.rs @@ -127,6 +127,10 @@ impl PrintResults for InvalidSymlinks { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.invalid_symlinks, pretty_print) + } } impl CommonData for InvalidSymlinks { diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index da5955c..108dabc 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -924,10 +924,40 @@ impl PrintResults for SameMusic { file_entry.genre, file_entry.bitrate, file_entry.path.display() - ) - .unwrap(); + )?; } - writeln!(writer).unwrap(); + writeln!(writer)?; + } + } else if !self.duplicated_music_entries_referenced.is_empty() { + writeln!(writer, "{} music files which have similar friends\n\n.", self.duplicated_music_entries_referenced.len())?; + for (file_entry, vec_file_entry) in &self.duplicated_music_entries_referenced { + writeln!(writer, "Found {} music files which have similar friends", vec_file_entry.len())?; + writeln!(writer)?; + writeln!( + writer, + "TT: {} - TA: {} - Y: {} - L: {} - G: {} - B: {} - P: {}", + file_entry.track_title, + file_entry.track_artist, + file_entry.year, + file_entry.length, + file_entry.genre, + file_entry.bitrate, + file_entry.path.display() + )?; + for file_entry in vec_file_entry { + writeln!( + writer, + "TT: {} - TA: {} - Y: {} - L: {} - G: {} - B: {} - P: {}", + file_entry.track_title, + file_entry.track_artist, + file_entry.year, + file_entry.length, + file_entry.genre, + file_entry.bitrate, + file_entry.path.display() + )?; + } + writeln!(writer)?; } } else { write!(writer, "Not found any similar music files.")?; @@ -935,6 +965,14 @@ impl PrintResults for SameMusic { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + if self.get_use_reference() { + self.save_results_to_file_as_json_internal(file_name, &self.duplicated_music_entries_referenced, pretty_print) + } else { + self.save_results_to_file_as_json_internal(file_name, &self.duplicated_music_entries, pretty_print) + } + } } fn get_approximate_conversion(what: &mut String) { diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 05e30c6..c080172 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -850,12 +850,46 @@ impl PrintResults for SimilarImages { } writeln!(writer)?; } + } else if !self.similar_referenced_vectors.is_empty() { + writeln!(writer, "{} images which have similar friends\n\n", self.similar_referenced_vectors.len())?; + + for (file_entry, vec_file_entry) in &self.similar_referenced_vectors { + writeln!(writer, "Found {} images which have similar friends", vec_file_entry.len())?; + writeln!(writer)?; + writeln!( + writer, + "{} - {} - {} - {}", + file_entry.path.display(), + file_entry.dimensions, + format_size(file_entry.size, BINARY), + get_string_from_similarity(&file_entry.similarity, self.hash_size) + )?; + for file_entry in vec_file_entry { + writeln!( + writer, + "{} - {} - {} - {}", + file_entry.path.display(), + file_entry.dimensions, + format_size(file_entry.size, BINARY), + get_string_from_similarity(&file_entry.similarity, self.hash_size) + )?; + } + writeln!(writer)?; + } } else { write!(writer, "Not found any similar images.")?; } Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + if self.get_use_reference() { + self.save_results_to_file_as_json_internal(file_name, &self.similar_referenced_vectors, pretty_print) + } else { + self.save_results_to_file_as_json_internal(file_name, &self.similar_vectors, pretty_print) + } + } } pub fn get_string_from_similarity(similarity: &u32, hash_size: u8) -> String { diff --git a/czkawka_core/src/similar_videos.rs b/czkawka_core/src/similar_videos.rs index ab6ad33..be197fd 100644 --- a/czkawka_core/src/similar_videos.rs +++ b/czkawka_core/src/similar_videos.rs @@ -435,12 +435,32 @@ impl PrintResults for SimilarVideos { } writeln!(writer)?; } + } else if !self.similar_referenced_vectors.is_empty() { + write!(writer, "{} videos which have similar friends\n\n", self.similar_referenced_vectors.len())?; + + for (fe, struct_similar) in &self.similar_referenced_vectors { + writeln!(writer, "Found {} videos which have similar friends", struct_similar.len())?; + writeln!(writer)?; + writeln!(writer, "{} - {}", fe.path.display(), format_size(fe.size, BINARY))?; + for file_entry in struct_similar { + writeln!(writer, "{} - {}", file_entry.path.display(), format_size(file_entry.size, BINARY))?; + } + writeln!(writer)?; + } } else { write!(writer, "Not found any similar videos.")?; } Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + if self.get_use_reference() { + self.save_results_to_file_as_json_internal(file_name, &self.similar_referenced_vectors, pretty_print) + } else { + self.save_results_to_file_as_json_internal(file_name, &self.similar_vectors, pretty_print) + } + } } pub fn check_if_ffmpeg_is_installed() -> bool { diff --git a/czkawka_core/src/temporary.rs b/czkawka_core/src/temporary.rs index d11901b..fb4c035 100644 --- a/czkawka_core/src/temporary.rs +++ b/czkawka_core/src/temporary.rs @@ -10,6 +10,7 @@ use crossbeam_channel::Receiver; use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use rayon::prelude::*; +use serde::Serialize; use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; @@ -32,7 +33,7 @@ const TEMP_EXTENSIONS: &[&str] = &[ ".partial", ]; -#[derive(Clone)] +#[derive(Clone, Serialize)] pub struct FileEntry { pub path: PathBuf, pub modified_date: u64, @@ -206,6 +207,10 @@ impl PrintResults for Temporary { Ok(()) } + + fn save_results_to_file_as_json(&self, file_name: &str, pretty_print: bool) -> std::io::Result<()> { + self.save_results_to_file_as_json_internal(file_name, &self.temporary_files, pretty_print) + } } impl Default for Temporary { diff --git a/czkawka_gui/i18n/en/czkawka_gui.ftl b/czkawka_gui/i18n/en/czkawka_gui.ftl index 0dac912..d7d90ee 100644 --- a/czkawka_gui/i18n/en/czkawka_gui.ftl +++ b/czkawka_gui/i18n/en/czkawka_gui.ftl @@ -538,7 +538,7 @@ move_files_title_dialog = Choose folder to which you want to move duplicated fil move_files_choose_more_than_1_path = Only one path may be selected to be able to copy their duplicated files, selected {$path_number}. move_stats = Properly moved {$num_files}/{$all_files} items -save_results_to_file = Saved results to file {$name} +save_results_to_file = Saved results both to txt and json files. search_not_choosing_any_music = ERROR: You must select at least one checkbox with music searching types. search_not_choosing_any_broken_files = ERROR: You must select at least one checkbox with type of checked broken files. diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 00bdb53..f1b24f5 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -957,11 +957,11 @@ fn computer_big_files( let vector = bf.get_big_files(); - for (size, file_entry) in vector { + for file_entry in vector { let (directory, file) = split_path(&file_entry.path); let values: [(u32, &dyn ToValue); COLUMNS_NUMBER] = [ (ColumnsBigFiles::SelectionButton as u32, &false), - (ColumnsBigFiles::Size as u32, &(format_size(*size, BINARY))), + (ColumnsBigFiles::Size as u32, &(format_size(file_entry.size, BINARY))), (ColumnsBigFiles::Name as u32, &file), (ColumnsBigFiles::Path as u32, &directory), ( @@ -969,7 +969,7 @@ fn computer_big_files( &(NaiveDateTime::from_timestamp_opt(file_entry.modified_date as i64, 0).unwrap().to_string()), ), (ColumnsBigFiles::ModificationAsSecs as u32, &(file_entry.modified_date as i64)), - (ColumnsBigFiles::SizeAsBytes as u32, &(size)), + (ColumnsBigFiles::SizeAsBytes as u32, &(file_entry.size)), ]; list_store.set(&list_store.append(), &values); } diff --git a/czkawka_gui/src/connect_things/connect_button_save.rs b/czkawka_gui/src/connect_things/connect_button_save.rs index 88c7da3..e407e08 100644 --- a/czkawka_gui/src/connect_things/connect_button_save.rs +++ b/czkawka_gui/src/connect_things/connect_button_save.rs @@ -2,14 +2,14 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use czkawka_core::common_traits::PrintResults; use gtk4::prelude::*; use gtk4::{Button, Entry}; +use czkawka_core::common_traits::PrintResults; + use crate::flg; use crate::gui_structs::gui_data::GuiData; use crate::help_functions::BottomButtonsEnum; -use crate::localizer_core::generate_translation_hashmap; use crate::notebook_enums::*; pub fn connect_button_save(gui_data: &GuiData) { @@ -30,64 +30,18 @@ pub fn connect_button_save(gui_data: &GuiData) { let entry_info = gui_data.entry_info.clone(); let notebook_main = gui_data.main_notebook.notebook_main.clone(); buttons_save.connect_clicked(move |_| { - let file_name; - let result = match to_notebook_main_enum(notebook_main.current_page().unwrap()) { - NotebookMainEnum::Duplicate => { - file_name = "results_duplicates.txt"; - - shared_duplication_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::EmptyDirectories => { - file_name = "results_empty_folder.txt"; - - shared_empty_folders_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::EmptyFiles => { - file_name = "results_empty_files.txt"; - - shared_empty_files_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::Temporary => { - file_name = "results_temporary_files.txt"; - - shared_temporary_files_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::BigFiles => { - file_name = "results_big_files.txt"; - - shared_big_files_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::SimilarImages => { - file_name = "results_similar_images.txt"; - - shared_similar_images_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::SimilarVideos => { - file_name = "results_similar_videos.txt"; - - shared_similar_videos_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::SameMusic => { - file_name = "results_same_music.txt"; - - shared_same_music_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::Symlinks => { - file_name = "results_invalid_symlinks.txt"; - - shared_same_invalid_symlinks.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::BrokenFiles => { - file_name = "results_broken_files.txt"; - - shared_broken_files_state.borrow_mut().print_results_to_file(file_name) - } - NotebookMainEnum::BadExtensions => { - file_name = "results_bad_extensions.txt"; - - shared_bad_extensions_state.borrow_mut().print_results_to_file(file_name) - } + NotebookMainEnum::Duplicate => shared_duplication_state.borrow().save_all_in_one("results_duplicates"), + NotebookMainEnum::EmptyDirectories => shared_empty_folders_state.borrow().save_all_in_one("results_empty_directories"), + NotebookMainEnum::EmptyFiles => shared_empty_files_state.borrow().save_all_in_one("results_empty_files"), + NotebookMainEnum::Temporary => shared_temporary_files_state.borrow().save_all_in_one("results_temporary_files"), + NotebookMainEnum::BigFiles => shared_big_files_state.borrow().save_all_in_one("results_big_files"), + NotebookMainEnum::SimilarImages => shared_similar_images_state.borrow().save_all_in_one("results_similar_images"), + NotebookMainEnum::SimilarVideos => shared_similar_videos_state.borrow().save_all_in_one("results_similar_videos"), + NotebookMainEnum::SameMusic => shared_same_music_state.borrow().save_all_in_one("results_same_music"), + NotebookMainEnum::Symlinks => shared_same_invalid_symlinks.borrow().save_all_in_one("results_invalid_symlinks"), + NotebookMainEnum::BrokenFiles => shared_broken_files_state.borrow().save_all_in_one("results_broken_files"), + NotebookMainEnum::BadExtensions => shared_bad_extensions_state.borrow().save_all_in_one("results_bad_extensions"), }; match result { @@ -99,7 +53,6 @@ pub fn connect_button_save(gui_data: &GuiData) { } post_save_things( - file_name, &to_notebook_main_enum(notebook_main.current_page().unwrap()), &shared_buttons, &entry_info, @@ -109,13 +62,12 @@ pub fn connect_button_save(gui_data: &GuiData) { } fn post_save_things( - file_name: &str, type_of_tab: &NotebookMainEnum, shared_buttons: &Rc>>>, entry_info: &Entry, buttons_save: &Button, ) { - entry_info.set_text(flg!("save_results_to_file", generate_translation_hashmap(vec![("name", file_name.to_string())])).as_str()); + entry_info.set_text(&flg!("save_results_to_file")); // Set state { buttons_save.hide(); diff --git a/misc/check_results.py b/misc/check_results.py deleted file mode 100644 index 68b5c0f..0000000 --- a/misc/check_results.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import sys - -if len(sys.argv) != 4: - print("ERROR: Not provided 3 required arguments - " + str(sys.argv)) - exit(1) - -folder_name = sys.argv[1] -files_required = sys.argv[2] -directories_required = sys.argv[3] - -file_count = sum(len(files) for _, _, files in os.walk(folder_name)) -if str(file_count) != files_required: - print("Current files: " + str(file_count) + ", but required is: " + str(files_required)) - print("This commit probably introduced regression, please recheck it.") - exit(1) - -directory_count = sum(len(dire) for _, dire, files in os.walk(folder_name)) -if str(directory_count) != directories_required: - print("Current directories: " + str(directory_count) + ", but required is: " + str(directories_required)) - print("This commit probably introduced regression, please recheck it.") - exit(1)