From c9b58e46daf7f690135992fc31b5c64a011eac80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Mon, 7 Oct 2024 18:42:00 +0200 Subject: [PATCH 1/5] Test --- .github/workflows/linux_cli.yml | 3 +++ Cargo.toml | 7 +++++- ci_tester/Cargo.toml | 5 ++++ ci_tester/src/main.rs | 12 ++++++++++ czkawka_cli/src/commands.rs | 21 ++++++++++++++++- czkawka_cli/src/main.rs | 35 ++++++++++++++++++---------- czkawka_core/src/common_directory.rs | 2 +- czkawka_core/src/same_music.rs | 2 +- justfile | 8 +++---- 9 files changed, 75 insertions(+), 20 deletions(-) diff --git a/.github/workflows/linux_cli.yml b/.github/workflows/linux_cli.yml index 0509201a5..689a41541 100644 --- a/.github/workflows/linux_cli.yml +++ b/.github/workflows/linux_cli.yml @@ -32,6 +32,9 @@ jobs: name: czkawka_cli-${{ runner.os }} path: target/release/czkawka_cli + - name: Build test version + run: cargo build --profile test --bin czkawka_cli + - name: Linux Regression Test run: | wget https://github.com/qarmin/czkawka/releases/download/6.0.0/TestFiles.zip diff --git a/Cargo.toml b/Cargo.toml index 0184c4abd..b1a74c972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,4 +28,9 @@ overflow-checks = true # Optimize all dependencies except application/workspaces, even in debug builds [profile.dev.package."*"] -opt-level = 3 \ No newline at end of file +opt-level = 3 + +[profile.test] +debug-assertions = true # Forces to crash when there is duplicated item in cli +overflow-checks = true +opt-level = 3 diff --git a/ci_tester/Cargo.toml b/ci_tester/Cargo.toml index 38f354a39..39f00c9d4 100644 --- a/ci_tester/Cargo.toml +++ b/ci_tester/Cargo.toml @@ -5,6 +5,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +debug-assertions = true +overflow-checks = true + [dependencies] state = "0.6.0" handsome_logger = "0.8.0" diff --git a/ci_tester/src/main.rs b/ci_tester/src/main.rs index 04e473111..14850b3ef 100644 --- a/ci_tester/src/main.rs +++ b/ci_tester/src/main.rs @@ -17,12 +17,24 @@ static COLLECTED_FILES: state::InitCell = state::InitCell::new() const ATTEMPTS: u32 = 10; const PRINT_MESSAGES_CZKAWKA: bool = true; +fn test_args() { + let modes = ["dup", "big", "empty-folders", "empty-files", "temp", "image", "symlinks", "broken", "ext", "video", "music"]; + for mode in modes { + let _ = fs::remove_dir_all("RandomDirWithoutContent"); + fs::create_dir_all("RandomDirWithoutContent").expect("Should not fail in tests"); + run_with_good_status(&[CZKAWKA_PATH.get().as_str(), mode, "-d", "RandomDirWithoutContent"], false); + } +} + // App runs - ./ci_tester PATH_TO_CZKAWKA fn main() { handsome_logger::init().expect("Should not fail in tests"); let args: Vec = std::env::args().collect(); let path_to_czkawka = args[1].clone(); CZKAWKA_PATH.set(path_to_czkawka); + + test_args(); + remove_test_dir(); run_with_good_status(&["ls"], false); unzip_files(); diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index 9d2fb8d99..86d1a0a6c 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -88,6 +88,8 @@ pub enum Commands { pub struct DuplicatesArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, + #[clap(flatten)] + pub referenced_folder: ReferencedFolder, #[clap( short = 'p', long, @@ -198,6 +200,8 @@ pub struct TemporaryArgs { pub struct SimilarImagesArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, + #[clap(flatten)] + pub referenced_folder: ReferencedFolder, #[clap( short, long, @@ -264,6 +268,8 @@ pub struct SameMusicArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] + pub referenced_folder: ReferencedFolder, + #[clap(flatten)] pub delete_method: DMethod, #[clap(flatten)] pub dry_run: DryRun, @@ -386,6 +392,8 @@ pub struct SimilarVideosArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] + pub referenced_folder: ReferencedFolder, + #[clap(flatten)] pub delete_method: DMethod, #[clap(flatten)] pub allow_hard_links: AllowHardLinks, @@ -437,7 +445,7 @@ pub struct CommonCliItems { long, required = true, help = "Directorie(s) to search", - long_help = "List of directorie(s) which will be searched(absolute path)" + long_help = "List of directorie(s) which will be searched(absolute path) - this directories are not set as reference folders" )] pub directories: Vec, #[clap( @@ -495,6 +503,17 @@ pub struct FileToSave { pub file_to_save: Option, } +#[derive(Debug, clap::Args)] +pub struct ReferencedFolder { + #[clap( + short, + long, + help = "Reference directorie(s) to search", + long_help = "List of directorie(s) which will be searched(absolute path) - this directories are set as reference folders, so will not be visible in the results" + )] + pub reference_folders: Vec, +} + #[derive(Debug, clap::Args)] pub struct JsonCompactFileToSave { #[clap(short = 'C', long, value_name = "json-file-name", help = "Saves the results into the compact json file")] diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index 8a580ef6d..f48016d1b 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::needless_late_init)] #![warn(clippy::unwrap_used)] +use std::path::PathBuf; use std::thread; use clap::Parser; @@ -79,6 +80,7 @@ fn main() { fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let DuplicatesArgs { common_cli_items, + referenced_folder, minimal_file_size, maximal_file_size, minimal_cached_file_size, @@ -103,7 +105,7 @@ fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress ); let mut item = DuplicateFinder::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -119,7 +121,7 @@ fn empty_folders(empty_folders: EmptyFoldersArgs, stop_receiver: &Receiver<()>, let mut item = EmptyFolder::new(); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_folders { item.set_delete_method(DeleteMethod::Delete); } @@ -141,7 +143,7 @@ fn biggest_files(biggest_files: BiggestFilesArgs, stop_receiver: &Receiver<()>, let params = BigFileParameters::new(number_of_files, big_files_mode); let mut item = BigFile::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_files { item.set_delete_method(DeleteMethod::Delete); } @@ -156,7 +158,7 @@ fn empty_files(empty_files: EmptyFilesArgs, stop_receiver: &Receiver<()>, progre let mut item = EmptyFiles::new(); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_files { item.set_delete_method(DeleteMethod::Delete); } @@ -171,7 +173,7 @@ fn temporary(temporary: TemporaryArgs, stop_receiver: &Receiver<()>, progress_se let mut item = Temporary::new(); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_files { item.set_delete_method(DeleteMethod::Delete); } @@ -184,6 +186,7 @@ fn temporary(temporary: TemporaryArgs, stop_receiver: &Receiver<()>, progress_se fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SimilarImagesArgs { common_cli_items, + referenced_folder, minimal_file_size, maximal_file_size, similarity_preset, @@ -207,7 +210,7 @@ fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<() ); let mut item = SimilarImages::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -221,6 +224,7 @@ fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<() fn same_music(same_music: SameMusicArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SameMusicArgs { common_cli_items, + referenced_folder, delete_method, minimal_file_size, maximal_file_size, @@ -243,7 +247,7 @@ fn same_music(same_music: SameMusicArgs, stop_receiver: &Receiver<()>, progress_ ); let mut item = SameMusic::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -259,7 +263,7 @@ fn invalid_symlinks(invalid_symlinks: InvalidSymlinksArgs, stop_receiver: &Recei let mut item = InvalidSymlinks::new(); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_files { item.set_delete_method(DeleteMethod::Delete); } @@ -283,7 +287,7 @@ fn broken_files(broken_files: BrokenFilesArgs, stop_receiver: &Receiver<()>, pro let params = BrokenFilesParameters::new(checked_type); let mut item = BrokenFiles::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); if delete_files { item.set_delete_method(DeleteMethod::Delete); } @@ -295,6 +299,7 @@ fn broken_files(broken_files: BrokenFilesArgs, stop_receiver: &Receiver<()>, pro fn similar_videos(similar_videos: SimilarVideosArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SimilarVideosArgs { + referenced_folder, common_cli_items, tolerance, minimal_file_size, @@ -308,7 +313,7 @@ fn similar_videos(similar_videos: SimilarVideosArgs, stop_receiver: &Receiver<() let params = SimilarVideosParameters::new(tolerance, ignore_same_size.ignore_same_size, !allow_hard_links.allow_hard_links); let mut item = SimilarVideos::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -325,7 +330,7 @@ fn bad_extensions(bad_extensions: BadExtensionsArgs, stop_receiver: &Receiver<() let params = BadExtensionsParameters::new(); let mut item = BadExtensions::new(params); - set_common_settings(&mut item, &common_cli_items); + set_common_settings(&mut item, &common_cli_items, None); item.find_bad_extensions_files(Some(stop_receiver), Some(progress_sender)); @@ -354,12 +359,18 @@ fn save_and_print_results(component: &mut T, commo component.get_text_messages().print_messages(); } -fn set_common_settings(component: &mut T, common_cli_items: &CommonCliItems) +fn set_common_settings(component: &mut T, common_cli_items: &CommonCliItems, referenced_folders: Option<&Vec>) where T: CommonData + PrintResults, { set_number_of_threads(common_cli_items.thread_number); + let mut included_directories = common_cli_items.directories.clone(); + if let Some(referenced_folders) = referenced_folders { + included_directories.extend_from_slice(referenced_folders); + component.set_reference_directory(referenced_folders.clone()); + } + component.set_included_directory(common_cli_items.directories.clone()); component.set_excluded_directory(common_cli_items.excluded_directories.clone()); component.set_excluded_items(common_cli_items.excluded_items.clone()); diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index cf2524ca4..22f963257 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -119,7 +119,7 @@ impl Directories { // Try to canonicalize them if cfg!(windows) { // Only canonicalize if it's not a network path - // This can be check by checking if path starts with \\?\UNC\ + // This can be done by checking if path starts with \\?\UNC\ if let Ok(dir_can) = directory.canonicalize() { let dir_can_str = dir_can.to_string_lossy().to_string(); if let Some(dir_can_str) = dir_can_str.strip_prefix(r"\\?\") { diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index ab5f24802..96defdfe4 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -398,7 +398,7 @@ impl SameMusic { } }) .while_some() - .filter_map(|e| e) + .flatten() .collect::>(); debug!("read_tags - ended reading tags"); diff --git a/justfile b/justfile index d82ab850e..e312fb09f 100644 --- a/justfile +++ b/justfile @@ -18,10 +18,10 @@ krokiet_r: krokiet_dark: RUST_BACKTRACE=1 SLINT_STYLE=fluent-dark cargo run --bin krokiet -cli: - RUST_BACKTRACE=1 cargo run --bin czkawka_cli -cli_r: - RUST_BACKTRACE=1 cargo run --bin czkawka_cli --release +cli +args: + RUST_BACKTRACE=1 cargo run --bin czkawka_cli -- {{args}} +cli_r args: + RUST_BACKTRACE=1 cargo run --bin czkawka_cli --release -- {{args}} cli_help: cargo run --bin czkawka_cli -- --help From fc5f5a346cfcda64cdc6e763a611b0edd36cb7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Mon, 7 Oct 2024 18:56:38 +0200 Subject: [PATCH 2/5] Debug --- .github/workflows/linux_cli.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux_cli.yml b/.github/workflows/linux_cli.yml index 689a41541..a89115613 100644 --- a/.github/workflows/linux_cli.yml +++ b/.github/workflows/linux_cli.yml @@ -42,7 +42,7 @@ jobs: cargo build --release cd .. - ci_tester/target/release/ci_tester target/release/czkawka_cli + ci_tester/target/release/ci_tester target/debug/czkawka_cli - name: Prepare files to release run: | From 5b6ae78f9ab6f1cb59979062ddb0ad825da8c175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Mon, 7 Oct 2024 20:38:01 +0200 Subject: [PATCH 3/5] Hellow --- Changelog.md | 3 +++ ci_tester/src/main.rs | 5 +++-- czkawka_cli/README.md | 8 -------- czkawka_cli/src/commands.rs | 20 ++++++++++---------- czkawka_cli/src/main.rs | 26 +++++++++++++------------- czkawka_core/src/duplicate.rs | 3 ++- 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Changelog.md b/Changelog.md index c250127f7..e73637244 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ - Due to the removal image_type from image struct, old cache files are incompatible with new version and should be regenerated from scratch(it uses new name) +- Some CLI arguments could change short name, due fixing ambiguous names ### Known regressions @@ -15,6 +16,7 @@ - Providing nightly builds - [#1360](https://github.com/qarmin/czkawka/pull/1360) - https://github.com/qarmin/czkawka/releases/tag/Nightly +- Added finding duplicated options in CLI -[#1364](https://github.com/qarmin/czkawka/pull/1364) ### Core @@ -68,6 +70,7 @@ - Fixed and added more input parameters to the application - [#1354](https://github.com/qarmin/czkawka/pull/1354) - Fixed crash when stopping scan multiple times - [#1355](https://github.com/qarmin/czkawka/pull/1355) - Print results also in debug build - [#1355](https://github.com/qarmin/czkawka/pull/1355) +- Added support for selecting reference directories -[#1364](https://github.com/qarmin/czkawka/pull/1364) ## Version 7.0.0 - 19.02.2024r diff --git a/ci_tester/src/main.rs b/ci_tester/src/main.rs index 14850b3ef..be1c98565 100644 --- a/ci_tester/src/main.rs +++ b/ci_tester/src/main.rs @@ -20,9 +20,10 @@ const PRINT_MESSAGES_CZKAWKA: bool = true; fn test_args() { let modes = ["dup", "big", "empty-folders", "empty-files", "temp", "image", "symlinks", "broken", "ext", "video", "music"]; for mode in modes { + println!("Testing mode {}", mode); let _ = fs::remove_dir_all("RandomDirWithoutContent"); fs::create_dir_all("RandomDirWithoutContent").expect("Should not fail in tests"); - run_with_good_status(&[CZKAWKA_PATH.get().as_str(), mode, "-d", "RandomDirWithoutContent"], false); + run_with_good_status(&[CZKAWKA_PATH.get().as_str(), mode, "-d", "RandomDirWithoutContent"], true); } } @@ -34,7 +35,7 @@ fn main() { CZKAWKA_PATH.set(path_to_czkawka); test_args(); - + return; remove_test_dir(); run_with_good_status(&["ls"], false); unzip_files(); diff --git a/czkawka_cli/README.md b/czkawka_cli/README.md index e5a5cd120..3c29dd675 100644 --- a/czkawka_cli/README.md +++ b/czkawka_cli/README.md @@ -47,14 +47,6 @@ cd .. cargo build --release --bin czkawka_cli ``` -## Limitations - -Not all available features in core are available in CLI. - -List of not available features: - -- Ability to use/choose referenced directories - ## LICENSE MIT \ No newline at end of file diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index 86d1a0a6c..3fbc6f882 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -25,7 +25,7 @@ pub enum Commands { #[clap( name = "dup", about = "Finds duplicate files", - after_help = "EXAMPLE:\n czkawka dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo" + after_help = "EXAMPLE:\n czkawka dup -d /home/rafal - -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo" )] Duplicates(DuplicatesArgs), #[clap( @@ -89,9 +89,9 @@ pub struct DuplicatesArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] - pub referenced_folder: ReferencedFolder, + pub reference_directories: ReferenceDirectories, #[clap( - short = 'p', + short = 'Z', long, value_parser = parse_minimal_file_size, default_value = "257144", @@ -201,7 +201,7 @@ pub struct SimilarImagesArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] - pub referenced_folder: ReferencedFolder, + pub reference_directories: ReferenceDirectories, #[clap( short, long, @@ -268,7 +268,7 @@ pub struct SameMusicArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] - pub referenced_folder: ReferencedFolder, + pub reference_directories: ReferenceDirectories, #[clap(flatten)] pub delete_method: DMethod, #[clap(flatten)] @@ -323,7 +323,7 @@ pub struct SameMusicArgs { )] pub minimum_segment_duration: f32, #[clap( - short = 'd', + short = 'Y', long, value_parser = parse_maximum_difference, default_value = "2.0", @@ -392,7 +392,7 @@ pub struct SimilarVideosArgs { #[clap(flatten)] pub common_cli_items: CommonCliItems, #[clap(flatten)] - pub referenced_folder: ReferencedFolder, + pub reference_directories: ReferenceDirectories, #[clap(flatten)] pub delete_method: DMethod, #[clap(flatten)] @@ -504,14 +504,14 @@ pub struct FileToSave { } #[derive(Debug, clap::Args)] -pub struct ReferencedFolder { +pub struct ReferenceDirectories { #[clap( short, long, help = "Reference directorie(s) to search", long_help = "List of directorie(s) which will be searched(absolute path) - this directories are set as reference folders, so will not be visible in the results" )] - pub reference_folders: Vec, + pub reference_directories: Vec, } #[derive(Debug, clap::Args)] @@ -546,7 +546,7 @@ pub struct DryRun { #[derive(Debug, clap::Args)] pub struct IgnoreSameSize { - #[clap(short, long, help = "Ignore files with the same size, leaving only one file of each size")] + #[clap(short = 'J', long, help = "Ignore files with the same size, leaving only one file of each size")] pub ignore_same_size: bool, } diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index f48016d1b..437409ff2 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -80,7 +80,7 @@ fn main() { fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let DuplicatesArgs { common_cli_items, - referenced_folder, + reference_directories, minimal_file_size, maximal_file_size, minimal_cached_file_size, @@ -105,7 +105,7 @@ fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress ); let mut item = DuplicateFinder::new(params); - set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); + set_common_settings(&mut item, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -186,7 +186,7 @@ fn temporary(temporary: TemporaryArgs, stop_receiver: &Receiver<()>, progress_se fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SimilarImagesArgs { common_cli_items, - referenced_folder, + reference_directories, minimal_file_size, maximal_file_size, similarity_preset, @@ -210,7 +210,7 @@ fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<() ); let mut item = SimilarImages::new(params); - set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); + set_common_settings(&mut item, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -224,7 +224,7 @@ fn similar_images(similar_images: SimilarImagesArgs, stop_receiver: &Receiver<() fn same_music(same_music: SameMusicArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SameMusicArgs { common_cli_items, - referenced_folder, + reference_directories, delete_method, minimal_file_size, maximal_file_size, @@ -247,7 +247,7 @@ fn same_music(same_music: SameMusicArgs, stop_receiver: &Receiver<()>, progress_ ); let mut item = SameMusic::new(params); - set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); + set_common_settings(&mut item, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -299,7 +299,7 @@ fn broken_files(broken_files: BrokenFilesArgs, stop_receiver: &Receiver<()>, pro fn similar_videos(similar_videos: SimilarVideosArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender) { let SimilarVideosArgs { - referenced_folder, + reference_directories, common_cli_items, tolerance, minimal_file_size, @@ -313,7 +313,7 @@ fn similar_videos(similar_videos: SimilarVideosArgs, stop_receiver: &Receiver<() let params = SimilarVideosParameters::new(tolerance, ignore_same_size.ignore_same_size, !allow_hard_links.allow_hard_links); let mut item = SimilarVideos::new(params); - set_common_settings(&mut item, &common_cli_items, Some(referenced_folder.reference_folders.as_ref())); + set_common_settings(&mut item, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); item.set_minimal_file_size(minimal_file_size); item.set_maximal_file_size(maximal_file_size); item.set_delete_method(delete_method.delete_method); @@ -359,19 +359,19 @@ fn save_and_print_results(component: &mut T, commo component.get_text_messages().print_messages(); } -fn set_common_settings(component: &mut T, common_cli_items: &CommonCliItems, referenced_folders: Option<&Vec>) +fn set_common_settings(component: &mut T, common_cli_items: &CommonCliItems, reference_directories: Option<&Vec>) where T: CommonData + PrintResults, { set_number_of_threads(common_cli_items.thread_number); let mut included_directories = common_cli_items.directories.clone(); - if let Some(referenced_folders) = referenced_folders { - included_directories.extend_from_slice(referenced_folders); - component.set_reference_directory(referenced_folders.clone()); + if let Some(reference_directories) = reference_directories { + included_directories.extend_from_slice(reference_directories); + component.set_reference_directory(reference_directories.clone()); } - component.set_included_directory(common_cli_items.directories.clone()); + component.set_included_directory(included_directories); component.set_excluded_directory(common_cli_items.excluded_directories.clone()); component.set_excluded_items(common_cli_items.excluded_items.clone()); component.set_recursive_search(!common_cli_items.not_recursive); diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index 84a847c8e..696249776 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -1026,8 +1026,9 @@ impl PrintResults for DuplicateFinder { fn write_results(&self, writer: &mut T) -> io::Result<()> { writeln!( writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + "Results of searching {:?} (reference directories {:?}) with excluded directories {:?} and excluded items {:?}", self.common_data.directories.included_directories, + self.common_data.directories.reference_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.get_excluded_items() )?; From 28b557627b8813e73933b1089c56a2faf5197419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Tue, 8 Oct 2024 22:40:06 +0200 Subject: [PATCH 4/5] Randoms --- krokiet/icons/krokiet_rename.svg | 1 + krokiet/src/connect_rename.rs | 129 ++++++++++++++++++++++++++++ krokiet/src/connect_show_preview.rs | 4 +- krokiet/src/main.rs | 3 + krokiet/ui/action_buttons.slint | 18 +++- krokiet/ui/callabler.slint | 2 + krokiet/ui/main_window.slint | 12 +++ krokiet/ui/popup_rename_files.slint | 77 +++++++++++++++++ 8 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 krokiet/icons/krokiet_rename.svg create mode 100644 krokiet/src/connect_rename.rs create mode 100644 krokiet/ui/popup_rename_files.slint diff --git a/krokiet/icons/krokiet_rename.svg b/krokiet/icons/krokiet_rename.svg new file mode 100644 index 000000000..b455cacc4 --- /dev/null +++ b/krokiet/icons/krokiet_rename.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/krokiet/src/connect_rename.rs b/krokiet/src/connect_rename.rs new file mode 100644 index 000000000..e25174b31 --- /dev/null +++ b/krokiet/src/connect_rename.rs @@ -0,0 +1,129 @@ +use std::path::{Path, PathBuf}; +use std::{fs, path}; + +use czkawka_core::common_messages::Messages; +use rayon::prelude::*; +use rfd::FileDialog; +use slint::{ComponentHandle, ModelRc, VecModel}; + +use crate::common::{get_is_header_mode, get_tool_model, set_tool_model}; +use crate::model_operations::{collect_path_name_from_model, deselect_all_items, filter_out_checked_items}; +use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow}; +use slint::Model; + +pub fn connect_rename(app: &MainWindow) { + let a = app.as_weak(); + app.global::().on_rename_files(move || { + let app = a.upgrade().expect("Failed to upgrade app :("); + let active_tab = app.global::().get_active_tab(); + let current_model = get_tool_model(&app, active_tab); + + dbg!(active_tab, current_model.iter().count()); + // let (errors, new_model) = move_operation(¤t_model, preserve_structure, copy_mode, &output_folder, active_tab); + // if let Some(new_model) = new_model { + // set_tool_model(&app, active_tab, new_model); + // } + // app.global::().set_info_text(Messages::new_from_errors(errors).create_messages_text().into()); + }); +} + +fn move_operation( + items: &ModelRc, + preserve_structure: bool, + copy_mode: bool, + output_folder: &str, + active_tab: CurrentTab, +) -> (Vec, Option>) { + assert_eq!(active_tab, CurrentTab::BadExtensions); + let (entries_to_move, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab)); + + if !entries_to_move.is_empty() { + let vec_items_to_move = collect_path_name_from_model(&entries_to_move, active_tab); + let errors = move_selected_items(vec_items_to_move, preserve_structure, copy_mode, output_folder); + deselect_all_items(&mut entries_left); + + let r = ModelRc::new(VecModel::from(entries_left)); + return (errors, Some(r)); + } + (vec![], None) +} + +fn move_selected_items(items_to_move: Vec<(String, String)>, preserve_structure: bool, copy_mode: bool, output_folder: &str) -> Vec { + if let Err(err) = fs::create_dir_all(output_folder) { + return vec![format!("Error while creating folder: {err}")]; + } + + // TODO option to override files + if copy_mode { + items_to_move + .into_par_iter() + .filter_map(|(path, name)| { + let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure); + + if output_file.exists() { + return Some(format!("File {output_file:?} already exists, and will not be overridden")); + } + try_to_copy_item(&input_file, &output_file)?; + None + }) + .collect() + } else { + items_to_move + .into_par_iter() + .filter_map(|(path, name)| { + let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure); + + if output_file.exists() { + return Some(format!("File {output_file:?} already exists, and will not be overridden")); + } + + // Try to rename file, may fail due various reasons + if fs::rename(&input_file, &output_file).is_ok() { + return None; + } + + // It is possible that this failed, because file is on different partition, so + // we need to copy file and then remove old + try_to_copy_item(&input_file, &output_file)?; + + if let Err(e) = fs::remove_file(&input_file) { + return Some(format!("Error while removing file {input_file:?}(after copying into different partition), reason {e}")); + } + + None + }) + .collect() + } +} + +// Tries to copy file/folder, and returns error if it fails +fn try_to_copy_item(input_file: &Path, output_file: &Path) -> Option { + let res = if input_file.is_dir() { + let options = fs_extra::dir::CopyOptions::new(); + fs_extra::dir::copy(input_file, output_file, &options) // TODO consider to use less buggy library + } else { + let options = fs_extra::file::CopyOptions::new(); + fs_extra::file::copy(input_file, output_file, &options) + }; + if let Err(e) = res { + return Some(format!("Error while copying {input_file:?} to {output_file:?}, reason {e}")); + } + None +} + +// Create input/output paths, and create output folder +fn collect_path_and_create_folders(input_path: &str, input_file: &str, output_path: &str, preserve_structure: bool) -> (PathBuf, PathBuf) { + let mut input_full_path = PathBuf::from(input_path); + input_full_path.push(input_file); + + let mut output_full_path = PathBuf::from(output_path); + if preserve_structure { + output_full_path.extend(Path::new(input_path).components().filter(|c| matches!(c, path::Component::Normal(_)))); + }; + let _ = fs::create_dir_all(&output_full_path); + output_full_path.push(input_file); + + println!("input_full_path: {input_full_path:?}, output_full_path: {output_full_path:?}, output_path: {output_path:?}, input_path: {input_path:?}"); + + (input_full_path, output_full_path) +} diff --git a/krokiet/src/connect_show_preview.rs b/krokiet/src/connect_show_preview.rs index f3527a616..5a0e57b56 100644 --- a/krokiet/src/connect_show_preview.rs +++ b/krokiet/src/connect_show_preview.rs @@ -20,8 +20,8 @@ pub fn connect_show_preview(app: &MainWindow) { let active_tab = gui_state.get_active_tab(); - if (active_tab == CurrentTab::SimilarImages && !settings.get_similar_images_show_image_preview()) - || (active_tab == CurrentTab::DuplicateFiles && !settings.get_duplicate_image_preview()) + if !((active_tab == CurrentTab::SimilarImages && settings.get_similar_images_show_image_preview()) + || (active_tab == CurrentTab::DuplicateFiles && settings.get_duplicate_image_preview())) { set_preview_visible(&gui_state, None); return; diff --git a/krokiet/src/main.rs b/krokiet/src/main.rs index b0ffa6b5c..2e86ee688 100644 --- a/krokiet/src/main.rs +++ b/krokiet/src/main.rs @@ -30,6 +30,7 @@ use crate::connect_directories_changes::connect_add_remove_directories; use crate::connect_move::connect_move; use crate::connect_open::connect_open_items; use crate::connect_progress_receiver::connect_progress_gathering; +use crate::connect_rename::connect_rename; use crate::connect_scan::connect_scan_button; use crate::connect_select::{connect_select, connect_showing_proper_select_buttons}; use crate::connect_show_preview::connect_show_preview; @@ -44,6 +45,7 @@ mod connect_directories_changes; mod connect_move; mod connect_open; mod connect_progress_receiver; +mod connect_rename; mod connect_scan; mod connect_select; mod connect_show_preview; @@ -83,6 +85,7 @@ fn main() { connect_select(&app); connect_showing_proper_select_buttons(&app); connect_move(&app); + connect_rename(&app); app.run().expect("Failed to run app :("); diff --git a/krokiet/ui/action_buttons.slint b/krokiet/ui/action_buttons.slint index 8319eb383..7df4af918 100644 --- a/krokiet/ui/action_buttons.slint +++ b/krokiet/ui/action_buttons.slint @@ -27,6 +27,7 @@ export component ActionButtons inherits HorizontalLayout { callback scan_starting(CurrentTab); callback show_select_popup(length, length); callback show_remove_popup(); + callback show_rename_popup(); callback request_folder_to_move(); in-out property <[MainListModel]> duplicate_files_model: []; @@ -126,7 +127,7 @@ export component ActionButtons inherits HorizontalLayout { select_button := Button { visible: lists_enabled; height: parent.height; - enabled: !scanning && lists_enabled && results_available; + enabled: !scanning && self.visible && results_available; text: "Select"; icon: @image-url("../icons/krokiet_select.svg"); clicked => { @@ -137,7 +138,7 @@ export component ActionButtons inherits HorizontalLayout { move_button := Button { visible: lists_enabled; height: parent.height; - enabled: !scanning && lists_enabled && results_available; + enabled: !scanning && self.visible && results_available; text: "Move"; icon: @image-url("../icons/krokiet_move.svg"); clicked => { @@ -148,7 +149,7 @@ export component ActionButtons inherits HorizontalLayout { delete_button := Button { visible: lists_enabled; height: parent.height; - enabled: !scanning && lists_enabled && results_available; + enabled: !scanning && self.visible && results_available; text: "Delete"; icon: @image-url("../icons/krokiet_delete.svg"); clicked => { @@ -156,6 +157,17 @@ export component ActionButtons inherits HorizontalLayout { } } + rename_button := Button { + visible: lists_enabled && GuiState.active_tab == CurrentTab.BadExtensions; + height: parent.height; + enabled: !scanning && self.visible && results_available; + text: "Rename"; + icon: @image-url("../icons/krokiet_rename.svg"); + clicked => { + show_rename_popup(); + } + } + Rectangle { horizontal-stretch: 0.5; } diff --git a/krokiet/ui/callabler.slint b/krokiet/ui/callabler.slint index edff596ea..79dffa76b 100644 --- a/krokiet/ui/callabler.slint +++ b/krokiet/ui/callabler.slint @@ -24,6 +24,8 @@ export global Callabler { callback move_items(bool, bool, string); + callback rename_files(); + // Translations pure callback translate(string, [{key: string, value: string}]) -> string; diff --git a/krokiet/ui/main_window.slint b/krokiet/ui/main_window.slint index 55fabdfd2..eeb34ed5e 100644 --- a/krokiet/ui/main_window.slint +++ b/krokiet/ui/main_window.slint @@ -16,6 +16,7 @@ import {PopupNewDirectories} from "popup_new_directories.slint"; import {PopupDelete} from "popup_delete.slint"; import {PopupMoveFolders} from "popup_move_folders.slint"; import { PopupSelectResults } from "popup_select_results.slint"; +import {PopupRenameFiles} from "popup_rename_files.slint"; import { ToolSettings } from "tool_settings.slint"; export {Settings, Callabler, GuiState} @@ -152,6 +153,9 @@ export component MainWindow inherits Window { show_remove_popup => { delete_popup_window.show_popup(); } + show_rename_popup => { + rename_popup_window.show_popup(); + } } HorizontalLayout { @@ -212,6 +216,14 @@ export component MainWindow inherits Window { y: parent.y + (parent.height - self.popup_height) / 2.0; } + rename_popup_window := PopupRenameFiles { + height: root.height; + width: root.width; + + x: parent.x + (root.width - self.popup_width) / 2.0; + y: parent.y + (parent.height - self.popup_height) / 2.0; + } + show_move_folders_dialog(folder_name) => { move_popup_window.folder_name = folder_name; move_popup_window.show_popup(); diff --git a/krokiet/ui/popup_rename_files.slint b/krokiet/ui/popup_rename_files.slint new file mode 100644 index 000000000..0b20ff9a9 --- /dev/null +++ b/krokiet/ui/popup_rename_files.slint @@ -0,0 +1,77 @@ +import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint"; +import {SelectableTableView} from "selectable_tree_view.slint"; +import {LeftSidePanel} from "left_side_panel.slint"; +import {MainList} from "main_lists.slint"; +import {CurrentTab, ProgressToSend} from "common.slint"; +import { ActionButtons } from "action_buttons.slint"; +import { Progress } from "progress.slint"; +import {MainListModel, SelectMode, SelectModel} from "common.slint"; +import {Settings} from "settings.slint"; +import {Callabler} from "callabler.slint"; +import { BottomPanel } from "bottom_panel.slint"; +import {ColorPalette} from "color_palette.slint"; +import {GuiState} from "gui_state.slint"; +import { Preview } from "preview.slint"; + +export component PopupRenameFiles inherits Rectangle { + out property popup_width: 500px; + out property popup_height: 150px; + in-out property folder_name: ""; + callback show_popup(); + + popup_window := PopupWindow { + width: popup_width; + height: popup_height; + + close-on-click: false; + Rectangle { + width: parent.width; + height: parent.height; + border-radius: 5px; + background: ColorPalette.popup_background; + VerticalLayout { + Text { + vertical-stretch: 0.0; + min-height: 30px; + text: "Renaming files"; + vertical-alignment: top; + horizontal-alignment: center; + font-size: 13px; + } + Text { + vertical-stretch: 1.0; + text: "This will rename extensions of selected files to more proper\n" + "\nAre you want to continue?"; + vertical-alignment: center; + horizontal-alignment: center; + font-size: 13px; + padding: 10px; + } + + HorizontalLayout { + Button { + text: "Yes"; + clicked => { + popup_window.close(); + Callabler.rename_files(); + debug("Renaming files"); + } + } + Rectangle { + + } + Button { + text: "No"; + clicked => { + popup_window.close(); + } + } + } + } + } + } + + show_popup() => { + debug("Showing popup"); + popup_window.show(); + } +} \ No newline at end of file From e1bf0103dccf82b525aae1a971ae1d413235897a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Wed, 9 Oct 2024 08:40:57 +0200 Subject: [PATCH 5/5] Renaming --- Changelog.md | 1 + czkawka_core/src/bad_extensions.rs | 10 ++- czkawka_gui/src/compute_results.rs | 2 +- krokiet/src/common.rs | 9 +++ krokiet/src/connect_rename.rs | 119 +++++----------------------- krokiet/src/connect_scan.rs | 8 +- krokiet/src/model_operations.rs | 30 ++++--- krokiet/ui/main_lists.slint | 6 -- krokiet/ui/popup_rename_files.slint | 2 - 9 files changed, 67 insertions(+), 120 deletions(-) diff --git a/Changelog.md b/Changelog.md index e73637244..e83ade9d4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -55,6 +55,7 @@ - Added ability to show preview of referenced folders - [#1359](https://github.com/qarmin/czkawka/pull/1359) - Enable selecting with space and jumping over entries with arrows and opening with enter - [#1359](https://github.com/qarmin/czkawka/pull/1359) +- Added button to rename files with invalid extension -[#1364](https://github.com/qarmin/czkawka/pull/1364) ### GTK GUI diff --git a/czkawka_core/src/bad_extensions.rs b/czkawka_core/src/bad_extensions.rs index 124417a8d..cbef33aac 100644 --- a/czkawka_core/src/bad_extensions.rs +++ b/czkawka_core/src/bad_extensions.rs @@ -169,7 +169,8 @@ pub struct BadFileEntry { pub modified_date: u64, pub size: u64, pub current_extension: String, - pub proper_extensions: String, + pub proper_extensions_group: String, + pub proper_extension: String, } impl ResultEntry for BadFileEntry { @@ -345,7 +346,8 @@ impl BadExtensions { modified_date: file_entry.modified_date, size: file_entry.size, current_extension, - proper_extensions: valid_extensions, + proper_extensions_group: valid_extensions, + proper_extension: proper_extension.to_string(), })) }) .while_some() @@ -386,6 +388,8 @@ impl BadExtensions { proper_extension: &str, ) -> (BTreeSet, String) { let mut all_available_extensions: BTreeSet = Default::default(); + // TODO Isn't this a bug? + // Why to file without extensions we set this as empty let valid_extensions = if current_extension.is_empty() { String::new() } else { @@ -443,7 +447,7 @@ impl PrintResults for BadExtensions { writeln!(writer, "Found {} files with invalid extension.\n", self.information.number_of_files_with_bad_extension)?; for file_entry in &self.bad_extensions_files { - writeln!(writer, "\"{}\" ----- {}", file_entry.path.to_string_lossy(), file_entry.proper_extensions)?; + writeln!(writer, "\"{}\" ----- {}", file_entry.path.to_string_lossy(), file_entry.proper_extensions_group)?; } Ok(()) diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index ae60b5246..3db2db54e 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -276,7 +276,7 @@ fn compute_bad_extensions( (ColumnsBadExtensions::Name as u32, &file), (ColumnsBadExtensions::Path as u32, &directory), (ColumnsBadExtensions::CurrentExtension as u32, &file_entry.current_extension), - (ColumnsBadExtensions::ValidExtensions as u32, &file_entry.proper_extensions), + (ColumnsBadExtensions::ValidExtensions as u32, &file_entry.proper_extensions_group), ( ColumnsBadExtensions::Modification as u32, &(DateTime::from_timestamp(file_entry.modified_date as i64, 0) diff --git a/krokiet/src/common.rs b/krokiet/src/common.rs index 09c9c34b8..7934ee7fe 100644 --- a/krokiet/src/common.rs +++ b/krokiet/src/common.rs @@ -190,6 +190,7 @@ pub enum StrDataBadExtensions { Name, Path, CurrentExtension, + ProperExtensionsGroup, ProperExtension, } @@ -228,6 +229,14 @@ pub fn get_str_name_idx(active_tab: CurrentTab) -> usize { } } +pub fn get_str_proper_extension(active_tab: CurrentTab) -> usize { + match active_tab { + CurrentTab::BadExtensions => StrDataBadExtensions::ProperExtension as usize, + CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"), + _ => panic!("Unable to get proper extension from this tab"), + } +} + pub fn get_int_modification_date_idx(active_tab: CurrentTab) -> usize { match active_tab { CurrentTab::EmptyFiles => IntDataEmptyFiles::ModificationDatePart1 as usize, diff --git a/krokiet/src/connect_rename.rs b/krokiet/src/connect_rename.rs index e25174b31..b922c8604 100644 --- a/krokiet/src/connect_rename.rs +++ b/krokiet/src/connect_rename.rs @@ -1,15 +1,12 @@ -use std::path::{Path, PathBuf}; -use std::{fs, path}; +use std::fs; +use std::path::{Path, MAIN_SEPARATOR}; use czkawka_core::common_messages::Messages; -use rayon::prelude::*; -use rfd::FileDialog; use slint::{ComponentHandle, ModelRc, VecModel}; use crate::common::{get_is_header_mode, get_tool_model, set_tool_model}; -use crate::model_operations::{collect_path_name_from_model, deselect_all_items, filter_out_checked_items}; +use crate::model_operations::{collect_path_name_and_proper_extension_from_model, deselect_all_items, filter_out_checked_items}; use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow}; -use slint::Model; pub fn connect_rename(app: &MainWindow) { let a = app.as_weak(); @@ -18,28 +15,21 @@ pub fn connect_rename(app: &MainWindow) { let active_tab = app.global::().get_active_tab(); let current_model = get_tool_model(&app, active_tab); - dbg!(active_tab, current_model.iter().count()); - // let (errors, new_model) = move_operation(¤t_model, preserve_structure, copy_mode, &output_folder, active_tab); - // if let Some(new_model) = new_model { - // set_tool_model(&app, active_tab, new_model); - // } - // app.global::().set_info_text(Messages::new_from_errors(errors).create_messages_text().into()); + let (errors, new_model) = rename_operation(¤t_model, active_tab); + if let Some(new_model) = new_model { + set_tool_model(&app, active_tab, new_model); + } + app.global::().set_info_text(Messages::new_from_errors(errors).create_messages_text().into()); }); } -fn move_operation( - items: &ModelRc, - preserve_structure: bool, - copy_mode: bool, - output_folder: &str, - active_tab: CurrentTab, -) -> (Vec, Option>) { +fn rename_operation(items: &ModelRc, active_tab: CurrentTab) -> (Vec, Option>) { assert_eq!(active_tab, CurrentTab::BadExtensions); let (entries_to_move, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab)); if !entries_to_move.is_empty() { - let vec_items_to_move = collect_path_name_from_model(&entries_to_move, active_tab); - let errors = move_selected_items(vec_items_to_move, preserve_structure, copy_mode, output_folder); + let vec_items_to_rename = collect_path_name_and_proper_extension_from_model(&entries_to_move, active_tab); + let errors = rename_selected_items(vec_items_to_rename); deselect_all_items(&mut entries_left); let r = ModelRc::new(VecModel::from(entries_left)); @@ -48,82 +38,15 @@ fn move_operation( (vec![], None) } -fn move_selected_items(items_to_move: Vec<(String, String)>, preserve_structure: bool, copy_mode: bool, output_folder: &str) -> Vec { - if let Err(err) = fs::create_dir_all(output_folder) { - return vec![format!("Error while creating folder: {err}")]; +fn rename_selected_items(files_with_new_extensions: Vec<(String, String, String)>) -> Vec { + let mut errors = vec![]; + for (folder, file_name, new_extension) in files_with_new_extensions { + let file_stem = Path::new(&file_name).file_stem().map(|e| e.to_string_lossy().to_string()).unwrap_or_default(); + let new_full_path = format!("{}{}{}.{}", folder, MAIN_SEPARATOR, file_stem, new_extension); + let old_full_path = format!("{}{}{}", folder, MAIN_SEPARATOR, file_name); + if let Err(e) = fs::rename(&old_full_path, &new_full_path) { + errors.push(format!("Failed to rename file {} to {} with error {}", old_full_path, new_full_path, e)); + } } - - // TODO option to override files - if copy_mode { - items_to_move - .into_par_iter() - .filter_map(|(path, name)| { - let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure); - - if output_file.exists() { - return Some(format!("File {output_file:?} already exists, and will not be overridden")); - } - try_to_copy_item(&input_file, &output_file)?; - None - }) - .collect() - } else { - items_to_move - .into_par_iter() - .filter_map(|(path, name)| { - let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure); - - if output_file.exists() { - return Some(format!("File {output_file:?} already exists, and will not be overridden")); - } - - // Try to rename file, may fail due various reasons - if fs::rename(&input_file, &output_file).is_ok() { - return None; - } - - // It is possible that this failed, because file is on different partition, so - // we need to copy file and then remove old - try_to_copy_item(&input_file, &output_file)?; - - if let Err(e) = fs::remove_file(&input_file) { - return Some(format!("Error while removing file {input_file:?}(after copying into different partition), reason {e}")); - } - - None - }) - .collect() - } -} - -// Tries to copy file/folder, and returns error if it fails -fn try_to_copy_item(input_file: &Path, output_file: &Path) -> Option { - let res = if input_file.is_dir() { - let options = fs_extra::dir::CopyOptions::new(); - fs_extra::dir::copy(input_file, output_file, &options) // TODO consider to use less buggy library - } else { - let options = fs_extra::file::CopyOptions::new(); - fs_extra::file::copy(input_file, output_file, &options) - }; - if let Err(e) = res { - return Some(format!("Error while copying {input_file:?} to {output_file:?}, reason {e}")); - } - None -} - -// Create input/output paths, and create output folder -fn collect_path_and_create_folders(input_path: &str, input_file: &str, output_path: &str, preserve_structure: bool) -> (PathBuf, PathBuf) { - let mut input_full_path = PathBuf::from(input_path); - input_full_path.push(input_file); - - let mut output_full_path = PathBuf::from(output_path); - if preserve_structure { - output_full_path.extend(Path::new(input_path).components().filter(|c| matches!(c, path::Component::Normal(_)))); - }; - let _ = fs::create_dir_all(&output_full_path); - output_full_path.push(input_file); - - println!("input_full_path: {input_full_path:?}, output_full_path: {output_full_path:?}, output_path: {output_path:?}, input_path: {input_path:?}"); - - (input_full_path, output_full_path) + errors } diff --git a/krokiet/src/connect_scan.rs b/krokiet/src/connect_scan.rs index a618b743a..4324b47cf 100644 --- a/krokiet/src/connect_scan.rs +++ b/krokiet/src/connect_scan.rs @@ -844,7 +844,13 @@ fn write_bad_extensions_results(app: &MainWindow, vector: Vec, mes fn prepare_data_model_bad_extensions(fe: &BadFileEntry) -> (ModelRc, ModelRc) { let (directory, file) = split_path(&fe.path); - let data_model_str = VecModel::from_slice(&[file.into(), directory.into(), fe.current_extension.clone().into(), fe.proper_extensions.clone().into()]); + let data_model_str = VecModel::from_slice(&[ + file.into(), + directory.into(), + fe.current_extension.clone().into(), + fe.proper_extensions_group.clone().into(), + fe.proper_extension.clone().into(), + ]); let modification_split = split_u64_into_i32s(fe.get_modified_date()); let size_split = split_u64_into_i32s(fe.size); let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1, size_split.0, size_split.1]); diff --git a/krokiet/src/model_operations.rs b/krokiet/src/model_operations.rs index c589a6f98..574f5db0d 100644 --- a/krokiet/src/model_operations.rs +++ b/krokiet/src/model_operations.rs @@ -1,8 +1,8 @@ use std::path::MAIN_SEPARATOR; -use slint::{Model, ModelRc}; +use slint::{Model, ModelRc, SharedString}; -use crate::common::{get_str_name_idx, get_str_path_idx}; +use crate::common::{get_str_name_idx, get_str_path_idx, get_str_proper_extension}; use crate::{CurrentTab, MainListModel}; pub fn deselect_all_items(items: &mut [MainListModel]) { @@ -26,8 +26,8 @@ pub fn collect_full_path_from_model(items: &[MainListModel], active_tab: Current items .iter() .map(|item| { - let path = item.val_str.iter().nth(path_idx).unwrap_or_else(|| panic!("Failed to get {path_idx} element")); - let name = item.val_str.iter().nth(name_idx).unwrap_or_else(|| panic!("Failed to get {name_idx} element")); + let path = get_shared_str_item(item, path_idx); + let name = get_shared_str_item(item, name_idx); format!("{path}{MAIN_SEPARATOR}{name}") }) .collect::>() @@ -35,16 +35,28 @@ pub fn collect_full_path_from_model(items: &[MainListModel], active_tab: Current pub fn collect_path_name_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<(String, String)> { let path_idx = get_str_path_idx(active_tab); let name_idx = get_str_name_idx(active_tab); + items.iter().map(|item| (get_str_item(item, path_idx), get_str_item(item, name_idx))).collect::>() +} + +pub fn collect_path_name_and_proper_extension_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<(String, String, String)> { + let path_idx = get_str_path_idx(active_tab); + let name_idx = get_str_name_idx(active_tab); + let ext_idx = get_str_proper_extension(active_tab); items .iter() - .map(|item| { - let path = item.val_str.iter().nth(path_idx).unwrap_or_else(|| panic!("Failed to get {path_idx} element")); - let name = item.val_str.iter().nth(name_idx).unwrap_or_else(|| panic!("Failed to get {name_idx} element")); - (path.to_string(), name.to_string()) - }) + .map(|item| (get_str_item(item, path_idx), get_str_item(item, name_idx), get_str_item(item, ext_idx))) .collect::>() } +#[inline] +pub fn get_str_item(main_list_model: &MainListModel, idx: usize) -> String { + main_list_model.val_str.iter().nth(idx).unwrap_or_else(|| panic!("Failed to get {idx} element")).to_string() +} +#[inline] +pub fn get_shared_str_item(main_list_model: &MainListModel, idx: usize) -> SharedString { + main_list_model.val_str.iter().nth(idx).unwrap_or_else(|| panic!("Failed to get {idx} element")) +} + pub fn filter_out_checked_items(items: &ModelRc, have_header: bool) -> (Vec, Vec) { if cfg!(debug_assertions) { check_if_header_is_checked(items); diff --git a/krokiet/ui/main_lists.slint b/krokiet/ui/main_lists.slint index ed433509d..2b58d2ace 100644 --- a/krokiet/ui/main_lists.slint +++ b/krokiet/ui/main_lists.slint @@ -203,12 +203,6 @@ export component MainList { } else { debug("Non handled key in main_lists.slint", event, GuiState.active_tab); } - - - - // else { - // debug("Non handled key in main_lists.slint", event, GuiState.active_tab); - // } accept } } diff --git a/krokiet/ui/popup_rename_files.slint b/krokiet/ui/popup_rename_files.slint index 0b20ff9a9..9b0434561 100644 --- a/krokiet/ui/popup_rename_files.slint +++ b/krokiet/ui/popup_rename_files.slint @@ -53,7 +53,6 @@ export component PopupRenameFiles inherits Rectangle { clicked => { popup_window.close(); Callabler.rename_files(); - debug("Renaming files"); } } Rectangle { @@ -71,7 +70,6 @@ export component PopupRenameFiles inherits Rectangle { } show_popup() => { - debug("Showing popup"); popup_window.show(); } } \ No newline at end of file