1
0
Fork 0
mirror of synced 2024-05-17 19:03:08 +12:00

Similar images and proper tool type

This commit is contained in:
Rafał Mikrut 2023-11-11 19:10:07 +01:00
parent 9225025157
commit b67a98f8ca
21 changed files with 184 additions and 26 deletions

1
Cargo.lock generated
View file

@ -3316,6 +3316,7 @@ dependencies = [
"czkawka_core",
"handsome_logger",
"home",
"humansize",
"log",
"open",
"rand",

View file

@ -9,6 +9,7 @@
### Core
- Using normal crossbeam channels instead of asyncio tokio channel - [#1102](https://github.com/qarmin/czkawka/pull/1102)
- Fixed tool type when using progress of empty directories
## Version 6.1.0 - 15.10.2023r
- BREAKING CHANGE - Changed cache saving method, deduplicated, optimized and simplified procedure(all files needs to be hashed again) - [#1072](https://github.com/qarmin/czkawka/pull/1072), [#1086](https://github.com/qarmin/czkawka/pull/1086)

View file

@ -220,6 +220,7 @@ impl BadExtensions {
.allowed_extensions(self.common_data.allowed_extensions.clone())
.excluded_items(self.common_data.excluded_items.clone())
.recursive_search(self.common_data.recursive_search)
.tool_type(self.common_data.tool_type)
.build()
.run();

View file

@ -467,6 +467,7 @@ pub fn prepare_thread_handler_common(
checking_method: CheckingMethod,
tool_type: ToolType,
) -> (JoinHandle<()>, Arc<AtomicBool>, Arc<AtomicUsize>, AtomicBool) {
assert_ne!(tool_type, ToolType::None, "ToolType::None should not exist");
let progress_thread_run = Arc::new(AtomicBool::new(true));
let atomic_counter = Arc::new(AtomicUsize::new(0));
let check_was_stopped = AtomicBool::new(false);

View file

@ -187,7 +187,7 @@ impl<'a, 'b> DirTraversalBuilder<'a, 'b, ()> {
directories: None,
allowed_extensions: None,
excluded_items: None,
tool_type: ToolType::BadExtensions,
tool_type: ToolType::None,
}
}
}
@ -341,6 +341,8 @@ where
{
#[fun_time(message = "run(collecting files/dirs)", level = "debug")]
pub fn run(self) -> DirTraversalResult<T> {
assert!(self.tool_type != ToolType::None, "Tool type cannot be None");
let mut all_warnings = vec![];
let mut grouped_file_entries: BTreeMap<T, Vec<FileEntry>> = BTreeMap::new();
let mut folder_entries: BTreeMap<PathBuf, FolderEntry> = BTreeMap::new();

View file

@ -169,6 +169,7 @@ impl DuplicateFinder {
.recursive_search(self.common_data.recursive_search)
.minimal_file_size(self.common_data.minimal_file_size)
.maximal_file_size(self.common_data.maximal_file_size)
.tool_type(self.common_data.tool_type)
.build()
.run();
@ -244,6 +245,7 @@ impl DuplicateFinder {
.recursive_search(self.common_data.recursive_search)
.minimal_file_size(self.common_data.minimal_file_size)
.maximal_file_size(self.common_data.maximal_file_size)
.tool_type(self.common_data.tool_type)
.build()
.run();
@ -321,6 +323,7 @@ impl DuplicateFinder {
.recursive_search(self.common_data.recursive_search)
.minimal_file_size(self.common_data.minimal_file_size)
.maximal_file_size(self.common_data.maximal_file_size)
.tool_type(self.common_data.tool_type)
.build()
.run();

View file

@ -63,6 +63,7 @@ impl EmptyFiles {
.allowed_extensions(self.common_data.allowed_extensions.clone())
.excluded_items(self.common_data.excluded_items.clone())
.recursive_search(self.common_data.recursive_search)
.tool_type(self.common_data.tool_type)
.build()
.run();

View file

@ -83,6 +83,7 @@ impl EmptyFolder {
.excluded_items(self.common_data.excluded_items.clone())
.collect(Collect::EmptyFolders)
.max_stage(0)
.tool_type(self.common_data.tool_type)
.build()
.run();

View file

@ -52,6 +52,7 @@ impl InvalidSymlinks {
.allowed_extensions(self.common_data.allowed_extensions.clone())
.excluded_items(self.common_data.excluded_items.clone())
.recursive_search(self.common_data.recursive_search)
.tool_type(self.common_data.tool_type)
.build()
.run();

View file

@ -203,6 +203,7 @@ impl SameMusic {
.allowed_extensions(self.common_data.allowed_extensions.clone())
.excluded_items(self.common_data.excluded_items.clone())
.recursive_search(self.common_data.recursive_search)
.tool_type(self.common_data.tool_type)
.max_stage(max_stage)
.build()
.run();

View file

@ -94,7 +94,7 @@ pub struct SimilarImages {
// Hashmap with image hashes and Vector with names of files
similarity: u32,
images_to_check: BTreeMap<String, FileEntry>,
hash_size: u8,
pub hash_size: u8, // TODO to remove pub, this is needeed by new gui, because there is no way to check what exactly was seelected
hash_alg: HashAlg,
image_filter: FilterType,
exclude_images_with_same_size: bool,

View file

@ -31,6 +31,7 @@ home = "0.5.5"
log = "0.4.20"
serde = "1.0"
serde_json = "1.0"
humansize = "2.1.3"
[build-dependencies]
slint-build = "1.3.0"

21
krokiet/LICENSE_MIT_CODE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2023 Rafał Mikrut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -73,14 +73,13 @@ should print something like
Slint: Build config: debug; Backend: software
```
## Dark/Different theme
## Different theme
App was created with white fluent theme by default, but is easily possible to use dark theme by setting `SLINT_STYLE`
environment variable to `fluent-dark` during compilation
e.g.
App was created with fluent theme in mind, but is possible to use dark theme by setting `SLINT_STYLE` environment
variable to `fluent-dark` during compilation e.g.
```
SLINT_STYLE=fluent-dark cargo run -- --path .
SLINT_STYLE=fluent-light cargo run -- --path .
```
Slint supports also other themes, but they are not officially supported by this app and may be broken.
@ -97,7 +96,8 @@ SLINT_STYLE=material-dark cargo run -- --path .
- Suggesting possible design changes in the gui - of course, they should be possible to be simply implemented in the
slint keeping in mind the performance aspect as well
- Modifying user interface - gui is written in simple language similar to qml, that can be modified in vscode/web with
live preview - [slint live preview example](https://slint.dev/releases/1.3.0/editor/?load_demo=examples/printerdemo/ui/printerdemo.slint)
live
preview - [slint live preview example](https://slint.dev/releases/1.3.0/editor/?load_demo=examples/printerdemo/ui/printerdemo.slint)
- Improving libraries used by Krokiet e.g. czkawka_core, image-rs etc.
- Improving app rust code
@ -120,6 +120,8 @@ SLINT_STYLE=material-dark cargo run -- --path .
- proper popup windows - slint not handle them properly
- logo
- about window
- reference folders
- translations(problem is only with interface, messages like "Checking {x} file" can be easily translated from rust side)
## Why Slint?
@ -145,10 +147,13 @@ errors.
So only Slint left with its cons and pros.
## License
Code is licensed under MIT license but entire project is licensed under GPL-3.0 license, due Slint license restrictions.
## Name
Why Krokiet(eng. Croquette)?
Because I like croquettes(Polish version), the ones with meat, mushrooms wrapped in breadcrumbs... it makes my mouth
water.
I considered also other dishes which I like to eat like pierogi, żurek, pączek, schabowy or zapiekanka.
This name should be a lot of easier to remember than Czkawka.
This name should be a lot of easier to remember than czkawka or szyszka.

View file

@ -1,7 +1,7 @@
use std::env;
fn main() {
if env::var("SLINT_STYLE").is_err() || env::var("SLINT_STYLE") == Ok("".into()) {
if env::var("SLINT_STYLE").is_err() || env::var("SLINT_STYLE") == Ok(String::new()) {
slint_build::compile_with_config("ui/main_window.slint", slint_build::CompilerConfiguration::new().with_style("fluent-dark".into())).unwrap();
} else {
slint_build::compile("ui/main_window.slint").unwrap();

View file

@ -23,3 +23,7 @@ pub fn create_string_standard_list_view_from_pathbuf(items: &[PathBuf]) -> Model
.collect::<Vec<_>>();
ModelRc::new(VecModel::from(new_folders_standard_list_view))
}
pub fn create_vec_model_from_vec_string(items: Vec<String>) -> VecModel<SharedString> {
VecModel::from(items.into_iter().map(SharedString::from).collect::<Vec<_>>())
}

View file

@ -1,7 +1,7 @@
use crate::{MainWindow, ProgressToSend};
use crossbeam_channel::Receiver;
use czkawka_core::common_dir_traversal::ProgressData;
use czkawka_core::common_dir_traversal::{ProgressData, ToolType};
use slint::{ComponentHandle, SharedString};
use std::thread;
@ -14,29 +14,80 @@ pub fn connect_progress_gathering(app: &MainWindow, progress_receiver: Receiver<
};
a.upgrade_in_event_loop(move |app| {
let (all_stages, current_stage) = common_get_data(&progress_data);
let to_send = ProgressToSend {
all_progress: (all_stages * 100.0) as i32,
current_progress: (current_stage * 100.0) as i32,
step_name: SharedString::from(format!("Checked {} folders", progress_data.entries_checked)),
};
let to_send;
match progress_data.tool_type {
ToolType::EmptyFiles => {
let (all_progress, current_progress) = no_current_stage_get_data(&progress_data);
to_send = ProgressToSend {
all_progress,
current_progress,
step_name: SharedString::from(format!("Checked {} files", progress_data.entries_checked)),
};
}
ToolType::EmptyFolders => {
let (all_progress, current_progress) = no_current_stage_get_data(&progress_data);
to_send = ProgressToSend {
all_progress,
current_progress,
step_name: SharedString::from(format!("Checked {} folders", progress_data.entries_checked)),
};
}
ToolType::SimilarImages => {
let step_name;
let all_progress;
let current_progress;
match progress_data.current_stage {
0 => {
(all_progress, current_progress) = no_current_stage_get_data(&progress_data);
step_name = format!("Scanning {} file", progress_data.entries_checked);
}
1 => {
(all_progress, current_progress) = common_get_data(&progress_data);
step_name = format!("Hashing {}/{} image", progress_data.entries_checked, progress_data.entries_to_check);
}
2 => {
(all_progress, current_progress) = common_get_data(&progress_data);
step_name = format!("Comparing {}/{} image hash", progress_data.entries_checked, progress_data.entries_to_check);
}
_ => panic!(),
}
to_send = ProgressToSend {
all_progress,
current_progress,
step_name: SharedString::from(step_name),
};
}
_ => {
panic!("Invalid tool type {:?}", progress_data.tool_type);
}
}
app.set_progress_datas(to_send);
})
.unwrap();
});
}
fn common_get_data(item: &ProgressData) -> (f64, f64) {
// Used when current stage not have enough data to show status, so we show only all_stages
// Happens if we searching files and we don't know how many files we need to check
fn no_current_stage_get_data(item: &ProgressData) -> (i32, i32) {
let all_stages = (item.current_stage as f64) / (item.max_stage + 1) as f64;
((all_stages * 100.0) as i32, -1)
}
// Used to calculate number of files to check and also to calculate current progress according to number of files to check and checked
fn common_get_data(item: &ProgressData) -> (i32, i32) {
if item.entries_to_check != 0 {
let all_stages = (item.current_stage as f64 + (item.entries_checked) as f64 / item.entries_to_check as f64) / (item.max_stage + 1) as f64;
let all_stages = if all_stages > 0.99 { 0.99 } else { all_stages };
let current_stage = (item.entries_checked) as f64 / item.entries_to_check as f64;
let current_stage = if current_stage > 0.99 { 0.99 } else { current_stage };
(all_stages, current_stage)
((all_stages * 100.0) as i32, (current_stage * 100.0) as i32)
} else {
let all_stages = (item.current_stage as f64) / (item.max_stage + 1) as f64;
let all_stages = if all_stages > 0.99 { 0.99 } else { all_stages };
(all_stages, 0f64)
((all_stages * 100.0) as i32, 0)
}
}

View file

@ -1,3 +1,4 @@
use crate::common::create_vec_model_from_vec_string;
use crate::settings::{collect_settings, SettingsCustom};
use crate::Settings;
use crate::{CurrentTab, MainListModel, MainWindow, ProgressToSend};
@ -9,6 +10,10 @@ use czkawka_core::common_tool::CommonData;
use czkawka_core::common_traits::ResultEntry;
use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::similar_images;
use czkawka_core::similar_images::SimilarImages;
use humansize::format_size;
use humansize::BINARY;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel, Weak};
use std::path::PathBuf;
use std::rc::Rc;
@ -23,7 +28,7 @@ pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressDat
app.set_progress_datas(ProgressToSend {
all_progress: 0,
current_progress: 0,
current_progress: -1,
step_name: SharedString::from(""),
});
@ -37,11 +42,67 @@ pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressDat
CurrentTab::EmptyFiles => {
scan_empty_files(a, progress_sender, stop_receiver, custom_settings);
}
_ => panic!(),
CurrentTab::SimilarImages => {
scan_similar_images(a, progress_sender, stop_receiver, custom_settings);
} // _ => panic!(),
}
});
}
// TODO handle referenced folders
fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::spawn(move || {
let mut finder = SimilarImages::new();
finder.set_included_directory(custom_settings.included_directories.clone());
finder.set_excluded_directory(custom_settings.excluded_directories.clone());
finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender));
let mut vector = finder.get_similar_images().clone();
let messages = finder.get_text_messages().create_messages_text();
for vec_fe in &mut vector {
vec_fe.sort_unstable_by_key(|e| e.similarity);
}
let hash_size = finder.hash_size;
a.upgrade_in_event_loop(move |app| {
let number_of_empty_files = vector.len();
let items = Rc::new(VecModel::default());
for vec_fe in vector {
items.push(MainListModel {
checked: false,
header_row: true,
selected_row: false,
val: ModelRc::new(VecModel::default()),
});
for fe in vec_fe {
let (directory, file) = split_path(fe.get_path());
let data_model = create_vec_model_from_vec_string(vec![
similar_images::get_string_from_similarity(&fe.similarity, hash_size),
format_size(fe.size, BINARY),
fe.dimensions.clone(),
file,
directory,
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string(),
]);
let main = MainListModel {
checked: false,
header_row: false,
selected_row: false,
val: ModelRc::new(data_model),
};
items.push(main);
}
}
app.set_similar_images_model(items.into());
app.invoke_scan_ended(format!("Found {} similar images files", number_of_empty_files).into());
app.global::<Settings>().set_info_text(messages.into());
})
});
}
fn scan_empty_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::spawn(move || {
let mut finder = EmptyFiles::new();

View file

@ -97,6 +97,7 @@ component DirectoriesPanel {
component TextErrorsPanel inherits TextEdit {
height: 20px;
read-only: true;
wrap: TextWrap.no-wrap;
text <=> Settings.info_text;
}

View file

@ -39,12 +39,12 @@ export component MainList {
if root.active-tab == CurrentTab.SimilarImages: SelectableTableView {
min-width: 200px;
columns: ["Selection", "File Name", "Path"];
columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path"];
last-column: "Modification Date";
column-sizes: [35px, 100px, 350px, 100px];
column-sizes: [35px, 80px, 80px, 80px, 350px, 100px, 100px];
values <=> similar-images-model;
parentPathIdx: 2;
fileNameIdx: 1;
parentPathIdx: 5;
fileNameIdx: 4;
item_opened(item) => {
item_opened(item)
}

View file

@ -53,6 +53,7 @@ export component Progress {
VerticalLayout {
spacing: 5px;
Text {
visible: progress_datas.current-progress >= -0.001;
vertical-alignment: TextVerticalAlignment.center;
text: progress_datas.current-progress + "%";
}