mirror of
https://github.com/qarmin/czkawka
synced 2024-09-30 04:54:30 +00:00
More work on Krokiet (#1210)
* Disable flaky tests * More * More * About * TODO * More * Heh * Progress * A little * Music * Models * Subsettings * Water * Header * Saving * Poprawa elementów * Ad * Names
This commit is contained in:
parent
9599ed5377
commit
0446ff366c
|
@ -1,7 +1,7 @@
|
|||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -169,6 +169,18 @@ pub struct BadFileEntry {
|
|||
pub proper_extensions: String,
|
||||
}
|
||||
|
||||
impl ResultEntry for BadFileEntry {
|
||||
fn get_path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
fn get_modified_date(&self) -> u64 {
|
||||
self.modified_date
|
||||
}
|
||||
fn get_size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Info {
|
||||
pub number_of_files_with_bad_extension: usize,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::fs;
|
||||
use std::fs::{DirEntry, FileType, Metadata};
|
||||
#[cfg(target_family = "unix")]
|
||||
|
@ -87,6 +88,15 @@ pub enum ErrorType {
|
|||
NonExistentFile,
|
||||
}
|
||||
|
||||
impl Display for ErrorType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorType::InfiniteRecursion => write!(f, "Infinite recursion"),
|
||||
ErrorType::NonExistentFile => write!(f, "Non existent file"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Collect {
|
||||
InvalidSymlinks,
|
||||
|
@ -318,7 +328,7 @@ 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");
|
||||
assert_ne!(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();
|
||||
|
|
|
@ -32,11 +32,20 @@ const TEMP_EXTENSIONS: &[&str] = &[
|
|||
];
|
||||
|
||||
#[derive(Clone, Serialize, Debug)]
|
||||
pub struct FileEntry {
|
||||
pub struct TemporaryFileEntry {
|
||||
pub path: PathBuf,
|
||||
pub modified_date: u64,
|
||||
}
|
||||
|
||||
impl TemporaryFileEntry {
|
||||
pub fn get_path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
pub fn get_modified_date(&self) -> u64 {
|
||||
self.modified_date
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Info {
|
||||
pub number_of_temporary_files: usize,
|
||||
|
@ -45,7 +54,7 @@ pub struct Info {
|
|||
pub struct Temporary {
|
||||
common_data: CommonToolData,
|
||||
information: Info,
|
||||
temporary_files: Vec<FileEntry>,
|
||||
temporary_files: Vec<TemporaryFileEntry>,
|
||||
}
|
||||
|
||||
impl Temporary {
|
||||
|
@ -138,7 +147,7 @@ impl Temporary {
|
|||
|
||||
true
|
||||
}
|
||||
pub fn get_file_entry(&self, atomic_counter: &Arc<AtomicUsize>, entry_data: &DirEntry, warnings: &mut Vec<String>) -> Option<FileEntry> {
|
||||
pub fn get_file_entry(&self, atomic_counter: &Arc<AtomicUsize>, entry_data: &DirEntry, warnings: &mut Vec<String>) -> Option<TemporaryFileEntry> {
|
||||
atomic_counter.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let current_file_name = entry_data.path();
|
||||
|
@ -158,7 +167,7 @@ impl Temporary {
|
|||
};
|
||||
|
||||
// Creating new file entry
|
||||
Some(FileEntry {
|
||||
Some(TemporaryFileEntry {
|
||||
modified_date: get_modified_time(&metadata, warnings, ¤t_file_name, false),
|
||||
path: current_file_name,
|
||||
})
|
||||
|
@ -234,7 +243,7 @@ impl CommonData for Temporary {
|
|||
}
|
||||
|
||||
impl Temporary {
|
||||
pub const fn get_temporary_files(&self) -> &Vec<FileEntry> {
|
||||
pub const fn get_temporary_files(&self) -> &Vec<TemporaryFileEntry> {
|
||||
&self.temporary_files
|
||||
}
|
||||
|
||||
|
|
|
@ -1282,7 +1282,7 @@ fn compute_duplicate_finder(
|
|||
fn vector_sort_unstable_entry_by_path<T>(vector: &[T]) -> Vec<T>
|
||||
where
|
||||
T: ResultEntry + Clone,
|
||||
T: std::marker::Send,
|
||||
T: Send,
|
||||
{
|
||||
if vector.len() >= 2 {
|
||||
let mut vector = vector.to_vec();
|
||||
|
@ -1296,7 +1296,7 @@ where
|
|||
fn vector_sort_simple_unstable_entry_by_path<T>(vector: &[T]) -> Vec<T>
|
||||
where
|
||||
T: ResultEntry + Clone,
|
||||
T: std::marker::Send,
|
||||
T: Send,
|
||||
{
|
||||
let mut vector = vector.to_vec();
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.get_path(), b.get_path()));
|
||||
|
|
|
@ -77,8 +77,8 @@ pub fn connect_button_search(gui_data: &GuiData, result_sender: Sender<Message>,
|
|||
entry_info.set_text(&flg!("searching_for_data"));
|
||||
|
||||
// Resets progress bars
|
||||
progress_bar_all_stages.set_fraction(0 as f64);
|
||||
progress_bar_current_stage.set_fraction(0 as f64);
|
||||
progress_bar_all_stages.set_fraction(0f64);
|
||||
progress_bar_current_stage.set_fraction(0f64);
|
||||
|
||||
reset_text_view(&text_view_errors);
|
||||
|
||||
|
@ -162,7 +162,7 @@ impl LoadedCommonItems {
|
|||
.as_str()
|
||||
.to_string()
|
||||
.split(',')
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
let allowed_extensions = entry_allowed_extensions.text().as_str().to_string();
|
||||
let excluded_extensions = entry_excluded_extensions.text().as_str().to_string();
|
||||
|
|
|
@ -217,10 +217,10 @@ fn progress_default(gui_data: &GuiData, item: &ProgressData) {
|
|||
|
||||
fn common_set_data(item: &ProgressData, progress_bar_all_stages: &ProgressBar, progress_bar_current_stage: &ProgressBar, taskbar_state: &Rc<RefCell<TaskbarProgress>>) {
|
||||
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 = (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 };
|
||||
progress_bar_all_stages.set_fraction(all_stages);
|
||||
progress_bar_current_stage.set_fraction((item.entries_checked) as f64 / item.entries_to_check as f64);
|
||||
progress_bar_current_stage.set_fraction(item.entries_checked as f64 / item.entries_to_check as f64);
|
||||
taskbar_state.borrow().set_progress_value(
|
||||
((item.current_stage as usize) * item.entries_to_check + item.entries_checked) as u64,
|
||||
item.entries_to_check as u64 * (item.max_stage + 1) as u64,
|
||||
|
|
|
@ -212,7 +212,7 @@ fn add_manually_directories(window_main: &Window, tree_view: &TreeView, excluded
|
|||
let list_store = get_list_store(&tree_view);
|
||||
|
||||
if excluded_items {
|
||||
if !(check_if_value_is_in_list_store(&list_store, ColumnsExcludedDirectory::Path as i32, &text)) {
|
||||
if !check_if_value_is_in_list_store(&list_store, ColumnsExcludedDirectory::Path as i32, &text) {
|
||||
let values: [(u32, &dyn ToValue); 1] = [(ColumnsExcludedDirectory::Path as u32, &text)];
|
||||
list_store.set(&list_store.append(), &values);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ This program is free to use and will always be.
|
|||
<property name="license-type">mit-x11</property>
|
||||
<property name="logo-icon-name">help-about-symbolic</property>
|
||||
<property name="program-name">Czkawka</property>
|
||||
<property name="version">6.1.0</property>
|
||||
<property name="version">7.0.0</property>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
|
@ -688,7 +688,7 @@
|
|||
(5,177,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None),
|
||||
(5,177,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkEditable","editable","0",None,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkEditable","text","Czkawka 6.1.0",1,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkEditable","text","Czkawka 7.0.0",1,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkEditable","xalign","1",None,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkEntry","has-frame","0",None,None,None,None,None,None,None,None,None),
|
||||
(5,178,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None),
|
||||
|
|
|
@ -1160,7 +1160,7 @@
|
|||
<property name="editable">0</property>
|
||||
<property name="focusable">1</property>
|
||||
<property name="has-frame">0</property>
|
||||
<property name="text" translatable="yes">Czkawka 6.1.0</property>
|
||||
<property name="text" translatable="yes">Czkawka 7.0.0</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
</child>
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
<svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg"><path d="m39.6 27.2c.1-.7.2-1.4.2-2.2s-.1-1.5-.2-2.2l4.5-3.2c.4-.3.6-.9.3-1.4l-4.4-7.4c-.3-.5-.8-.7-1.3-.4l-5 2.3c-1.2-.9-2.4-1.6-3.8-2.2l-.5-5.5c-.1-.5-.5-.9-1-.9h-8.6c-.5 0-1 .4-1 .9l-.5 5.5c-1.4.6-2.7 1.3-3.8 2.2l-5-2.3c-.5-.2-1.1 0-1.3.4l-4.3 7.4c-.3.5-.1 1.1.3 1.4l4.5 3.2c-.1.7-.2 1.4-.2 2.2s.1 1.5.2 2.2l-4.7 3.2c-.4.3-.6.9-.3 1.4l4.3 7.4c.3.5.8.7 1.3.4l5-2.3c1.2.9 2.4 1.6 3.8 2.2l.5 5.5c.1.5.5.9 1 .9h8.6c.5 0 1-.4 1-.9l.5-5.5c1.4-.6 2.7-1.3 3.8-2.2l5 2.3c.5.2 1.1 0 1.3-.4l4.3-7.4c.3-.5.1-1.1-.3-1.4zm-15.6 7.8c-5.5 0-10-4.5-10-10s4.5-10 10-10 10 4.5 10 10-4.5 10-10 10z" fill="#607d8b"/><path d="m24 13c-6.6 0-12 5.4-12 12s5.4 12 12 12 12-5.4 12-12-5.4-12-12-12zm0 17c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5z" fill="#455a64"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m296 186.72a80 80 0 1 0 29.282 109.28 80.24 80.24 0 0 0-29.282-109.28zm109.58 155.64a165.53 165.53 0 0 1-12.59 18.527l23.107 57.358a11.59 11.59 0 0 1-5.1124 14.115l-79.649 45.836a11.64 11.64 0 0 1-14.711-2.8005l-38.08-48.544a176.56 176.56 0 0 1-44.954 0.2228l-37.803 48.357a11.93 11.93 0 0 1-14.898 2.784l-79.778-46.06a12 12 0 0 1-5.203-14.008l22.977-56.917a169.3 169.3 0 0 1-22.274-39.06l-61.08-8.7064a11.64 11.64 0 0 1-9.7856-11.331l-0.13413-91.888a11.59 11.59 0 0 1 9.6676-11.485l61.227-8.6679a174.58 174.58 0 0 1 9.9147-20.453 165.53 165.53 0 0 1 12.59-18.527l-23.107-57.358a11.59 11.59 0 0 1 5.1124-14.115l79.649-45.836a11.64 11.64 0 0 1 14.711 2.8005l38.08 48.544a176.56 176.56 0 0 1 44.954-0.2228l37.803-48.357a11.93 11.93 0 0 1 14.898-2.784l79.778 46.06a12 12 0 0 1 5.203 14.008l-22.977 56.917a169.3 169.3 0 0 1 22.317 39.085l61.037 8.6814a11.64 11.64 0 0 1 9.7856 11.331l0.12913 91.896a11.59 11.59 0 0 1-9.6676 11.485l-61.227 8.6679a174.58 174.58 0 0 1-9.9097 20.444z"/>
|
||||
</svg>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 1.1 KiB |
17
krokiet/icons/subsettings.svg
Normal file
17
krokiet/icons/subsettings.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m259.96 178.63c-116.38 0.46054-109.81 154.42-4.8963 157.07 110.82 2.7975 108.63-157.48 4.8963-157.07zm-4.5114 238.17-59.661 58.623c-3.663 4.3684-9.9047 5.5348-14.898 2.784l-79.778-46.06c-4.7847-2.888-6.9422-8.6968-5.203-14.008l22.977-56.917c-9.1283-11.972-16.619-25.108-22.274-39.06l-61.08-8.7064c-5.5804-0.89998-9.7077-5.679-9.7856-11.331l-0.13413-91.888c-0.02721-5.6801 4.0661-10.543 9.6676-11.485l61.227-8.6679c2.8571-7.0258 6.169-13.858 9.9147-20.453 3.7735-6.4529 7.9798-12.643 12.59-18.527l-23.107-57.358c-1.985-5.3221 0.17966-11.298 5.1124-14.115l79.649-45.836c4.933-2.7634 11.138-1.5821 14.711 2.8005l66.966 73.793c-6.8372 161.75-1.2386 214.48-6.8946 306.41z"/>
|
||||
<rect x="275" y="50" width="200" height="20" stroke-width=".8921"/>
|
||||
<rect x="275" y="85" width="200" height="20" stroke-width=".8921"/>
|
||||
<rect x="275" y="120" width="200" height="20" stroke-width=".8921"/>
|
||||
<rect x="275" y="155" width="200" height="20" stroke-width=".8921"/>
|
||||
<rect x="335" y="190" width="140" height="20" stroke-width=".74638"/>
|
||||
<rect x="345" y="225" width="130" height="20" stroke-width=".71923"/>
|
||||
<rect transform="scale(1,-1)" x="273.16" y="-466.69" width="199.81" height="20" stroke-width=".89168"/>
|
||||
<rect transform="scale(1,-1)" x="273.16" y="-431.69" width="199.81" height="20" stroke-width=".89168"/>
|
||||
<rect transform="scale(1,-1)" x="273.16" y="-396.69" width="199.81" height="20" stroke-width=".89168"/>
|
||||
<rect transform="scale(1,-1)" x="273.16" y="-361.69" width="199.81" height="20" stroke-width=".89168"/>
|
||||
<rect transform="scale(1,-1)" x="333.1" y="-326.69" width="139.87" height="20" stroke-width=".74603"/>
|
||||
<rect transform="scale(1,-1)" x="343.09" y="-291.69" width="129.88" height="20" stroke-width=".71889"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,63 +1,291 @@
|
|||
#![allow(dead_code)] // TODO later remove
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{CurrentTab, ExcludedDirectoriesModel, IncludedDirectoriesModel, MainListModel, MainWindow};
|
||||
use slint::{ModelRc, SharedString, VecModel};
|
||||
|
||||
// Int model is used to store data in unchanged(* except that we need to split u64 into two i32) form and is used to sort/select data
|
||||
// Str model is used to display data in gui
|
||||
|
||||
// Duplicates
|
||||
#[repr(u8)]
|
||||
pub enum IntDataDuplicateFiles {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
#[repr(u8)]
|
||||
pub enum StrDataDuplicateFiles {
|
||||
Size,
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Empty Folders
|
||||
#[repr(u8)]
|
||||
pub enum IntDataEmptyFolders {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataEmptyFolders {
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
// Big Files
|
||||
#[repr(u8)]
|
||||
pub enum IntDataBigFiles {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataBigFiles {
|
||||
Size,
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Empty files
|
||||
#[repr(u8)]
|
||||
pub enum IntDataEmptyFiles {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataEmptyFiles {
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
// Temporary Files
|
||||
#[repr(u8)]
|
||||
pub enum IntDataTemporaryFiles {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
}
|
||||
#[repr(u8)]
|
||||
pub enum StrDataTemporaryFiles {
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Similar Images
|
||||
#[repr(u8)]
|
||||
pub enum IntDataSimilarImages {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
Width,
|
||||
Height,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataSimilarImages {
|
||||
Similarity,
|
||||
Size,
|
||||
Resolution,
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Similar Videos
|
||||
#[repr(u8)]
|
||||
pub enum IntDataSimilarVideos {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataSimilarVideos {
|
||||
Size,
|
||||
Name,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Similar Music
|
||||
#[repr(u8)]
|
||||
pub enum IntDataSimilarMusic {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataSimilarMusic {
|
||||
Size,
|
||||
Name,
|
||||
Title,
|
||||
Artist,
|
||||
Year,
|
||||
Bitrate,
|
||||
Length,
|
||||
Genre,
|
||||
Path,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Invalid Symlinks
|
||||
#[repr(u8)]
|
||||
pub enum IntDataInvalidSymlinks {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataInvalidSymlinks {
|
||||
SymlinkName,
|
||||
SymlinkFolder,
|
||||
DestinationPath,
|
||||
TypeOfError,
|
||||
ModificationDate,
|
||||
}
|
||||
|
||||
// Broken Files
|
||||
#[repr(u8)]
|
||||
pub enum IntDataBrokenFiles {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataBrokenFiles {
|
||||
Name,
|
||||
Path,
|
||||
TypeOfError,
|
||||
Size,
|
||||
ModificationDate,
|
||||
}
|
||||
// Bad Extensions
|
||||
#[repr(u8)]
|
||||
pub enum IntDataBadExtensions {
|
||||
ModificationDatePart1,
|
||||
ModificationDatePart2,
|
||||
SizePart1,
|
||||
SizePart2,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum StrDataBadExtensions {
|
||||
Name,
|
||||
Path,
|
||||
CurrentExtension,
|
||||
ProperExtension,
|
||||
}
|
||||
|
||||
// Remember to match updated this according to ui/main_lists.slint and connect_scan.rs files
|
||||
pub fn get_str_path_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::EmptyFolders => 1,
|
||||
CurrentTab::EmptyFiles => 1,
|
||||
CurrentTab::SimilarImages => 4,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFolders => StrDataEmptyFolders::Path as usize,
|
||||
CurrentTab::EmptyFiles => StrDataEmptyFiles::Path as usize,
|
||||
CurrentTab::SimilarImages => StrDataSimilarImages::Path as usize,
|
||||
CurrentTab::DuplicateFiles => StrDataDuplicateFiles::Path as usize,
|
||||
CurrentTab::BigFiles => StrDataBigFiles::Path as usize,
|
||||
CurrentTab::TemporaryFiles => StrDataTemporaryFiles::Path as usize,
|
||||
CurrentTab::SimilarVideos => StrDataSimilarVideos::Path as usize,
|
||||
CurrentTab::SimilarMusic => StrDataSimilarMusic::Path as usize,
|
||||
CurrentTab::InvalidSymlinks => StrDataInvalidSymlinks::SymlinkFolder as usize,
|
||||
CurrentTab::BrokenFiles => StrDataBrokenFiles::Path as usize,
|
||||
CurrentTab::BadExtensions => StrDataBadExtensions::Path as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_str_name_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::EmptyFolders => 0,
|
||||
CurrentTab::EmptyFiles => 0,
|
||||
CurrentTab::SimilarImages => 3,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFolders => StrDataEmptyFolders::Name as usize,
|
||||
CurrentTab::EmptyFiles => StrDataEmptyFiles::Name as usize,
|
||||
CurrentTab::SimilarImages => StrDataSimilarImages::Name as usize,
|
||||
CurrentTab::DuplicateFiles => StrDataDuplicateFiles::Name as usize,
|
||||
CurrentTab::BigFiles => StrDataBigFiles::Name as usize,
|
||||
CurrentTab::TemporaryFiles => StrDataTemporaryFiles::Name as usize,
|
||||
CurrentTab::SimilarVideos => StrDataSimilarVideos::Name as usize,
|
||||
CurrentTab::SimilarMusic => StrDataSimilarMusic::Name as usize,
|
||||
CurrentTab::InvalidSymlinks => StrDataInvalidSymlinks::SymlinkName as usize,
|
||||
CurrentTab::BrokenFiles => StrDataBrokenFiles::Name as usize,
|
||||
CurrentTab::BadExtensions => StrDataBadExtensions::Name as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_modification_date_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::EmptyFiles => 0,
|
||||
CurrentTab::SimilarImages => 0,
|
||||
CurrentTab::EmptyFolders => 0,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFiles => IntDataEmptyFiles::ModificationDatePart1 as usize,
|
||||
CurrentTab::EmptyFolders => IntDataEmptyFolders::ModificationDatePart1 as usize,
|
||||
CurrentTab::SimilarImages => IntDataSimilarImages::ModificationDatePart1 as usize,
|
||||
CurrentTab::DuplicateFiles => IntDataDuplicateFiles::ModificationDatePart1 as usize,
|
||||
CurrentTab::BigFiles => IntDataBigFiles::ModificationDatePart1 as usize,
|
||||
CurrentTab::TemporaryFiles => IntDataTemporaryFiles::ModificationDatePart1 as usize,
|
||||
CurrentTab::SimilarVideos => IntDataSimilarVideos::ModificationDatePart1 as usize,
|
||||
CurrentTab::SimilarMusic => IntDataSimilarMusic::ModificationDatePart1 as usize,
|
||||
CurrentTab::InvalidSymlinks => IntDataInvalidSymlinks::ModificationDatePart1 as usize,
|
||||
CurrentTab::BrokenFiles => IntDataBrokenFiles::ModificationDatePart1 as usize,
|
||||
CurrentTab::BadExtensions => IntDataBadExtensions::ModificationDatePart1 as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_size_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::EmptyFiles => 2,
|
||||
CurrentTab::SimilarImages => 2,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFolders => panic!("Unable to get size from this tab"),
|
||||
CurrentTab::EmptyFiles => IntDataEmptyFiles::SizePart1 as usize,
|
||||
CurrentTab::SimilarImages => IntDataSimilarImages::SizePart1 as usize,
|
||||
CurrentTab::DuplicateFiles => IntDataDuplicateFiles::SizePart1 as usize,
|
||||
CurrentTab::BigFiles => IntDataBigFiles::SizePart1 as usize,
|
||||
CurrentTab::SimilarVideos => IntDataSimilarVideos::SizePart1 as usize,
|
||||
CurrentTab::SimilarMusic => IntDataSimilarMusic::SizePart1 as usize,
|
||||
CurrentTab::BrokenFiles => IntDataBrokenFiles::SizePart1 as usize,
|
||||
CurrentTab::BadExtensions => IntDataBadExtensions::SizePart1 as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFolders | CurrentTab::InvalidSymlinks | CurrentTab::TemporaryFiles => panic!("Unable to get size from this tab"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_width_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::SimilarImages => 4,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::SimilarImages => IntDataSimilarImages::Width as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
_ => panic!("Unable to get height from this tab"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_height_idx(active_tab: CurrentTab) -> usize {
|
||||
match active_tab {
|
||||
CurrentTab::SimilarImages => 5,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::SimilarImages => IntDataSimilarImages::Height as usize,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
_ => panic!("Unable to get height from this tab"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_is_header_mode(active_tab: CurrentTab) -> bool {
|
||||
match active_tab {
|
||||
CurrentTab::EmptyFolders | CurrentTab::EmptyFiles => false,
|
||||
CurrentTab::SimilarImages => true,
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::EmptyFolders
|
||||
| CurrentTab::EmptyFiles
|
||||
| CurrentTab::BrokenFiles
|
||||
| CurrentTab::BigFiles
|
||||
| CurrentTab::TemporaryFiles
|
||||
| CurrentTab::InvalidSymlinks
|
||||
| CurrentTab::BadExtensions => false,
|
||||
CurrentTab::SimilarImages | CurrentTab::DuplicateFiles | CurrentTab::SimilarVideos | CurrentTab::SimilarMusic => true,
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +294,15 @@ pub fn get_tool_model(app: &MainWindow, tab: CurrentTab) -> ModelRc<MainListMode
|
|||
CurrentTab::EmptyFolders => app.get_empty_folder_model(),
|
||||
CurrentTab::SimilarImages => app.get_similar_images_model(),
|
||||
CurrentTab::EmptyFiles => app.get_empty_files_model(),
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::DuplicateFiles => app.get_duplicate_files_model(),
|
||||
CurrentTab::BigFiles => app.get_big_files_model(),
|
||||
CurrentTab::TemporaryFiles => app.get_temporary_files_model(),
|
||||
CurrentTab::SimilarVideos => app.get_similar_videos_model(),
|
||||
CurrentTab::SimilarMusic => app.get_similar_music_model(),
|
||||
CurrentTab::InvalidSymlinks => app.get_invalid_symlinks_model(),
|
||||
CurrentTab::BrokenFiles => app.get_broken_files_model(),
|
||||
CurrentTab::BadExtensions => app.get_bad_extensions_model(),
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +311,15 @@ pub fn set_tool_model(app: &MainWindow, tab: CurrentTab, model: ModelRc<MainList
|
|||
CurrentTab::EmptyFolders => app.set_empty_folder_model(model),
|
||||
CurrentTab::SimilarImages => app.set_similar_images_model(model),
|
||||
CurrentTab::EmptyFiles => app.set_empty_files_model(model),
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::DuplicateFiles => app.set_duplicate_files_model(model),
|
||||
CurrentTab::BigFiles => app.set_big_files_model(model),
|
||||
CurrentTab::TemporaryFiles => app.set_temporary_files_model(model),
|
||||
CurrentTab::SimilarVideos => app.set_similar_videos_model(model),
|
||||
CurrentTab::SimilarMusic => app.set_similar_music_model(model),
|
||||
CurrentTab::InvalidSymlinks => app.set_invalid_symlinks_model(model),
|
||||
CurrentTab::BrokenFiles => app.set_broken_files_model(model),
|
||||
CurrentTab::BadExtensions => app.set_bad_extensions_model(model),
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,6 +384,7 @@ pub fn split_u64_into_i32s(value: u64) -> (i32, i32) {
|
|||
let part2: i32 = value as i32;
|
||||
(part1, part2)
|
||||
}
|
||||
|
||||
pub fn connect_i32_into_u64(part1: i32, part2: i32) -> u64 {
|
||||
((part1 as u64) << 32) | (part2 as u64 & 0xFFFFFFFF)
|
||||
}
|
||||
|
@ -155,6 +400,7 @@ mod test {
|
|||
assert_eq!(part1, 0);
|
||||
assert_eq!(part2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_u64_into_i32s_big() {
|
||||
let value = u64::MAX;
|
||||
|
@ -162,6 +408,7 @@ mod test {
|
|||
assert_eq!(part1, -1);
|
||||
assert_eq!(part2, -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_i32_into_u64_small() {
|
||||
let part1 = 0;
|
||||
|
@ -169,6 +416,7 @@ mod test {
|
|||
let value = super::connect_i32_into_u64(part1, part2);
|
||||
assert_eq!(value, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_i32_into_u64_big() {
|
||||
let part1 = -1;
|
||||
|
@ -176,6 +424,7 @@ mod test {
|
|||
let value = super::connect_i32_into_u64(part1, part2);
|
||||
assert_eq!(value, u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_split_zero() {
|
||||
for start_value in [0, 1, 10, u32::MAX as u64, i32::MAX as u64, u64::MAX] {
|
||||
|
|
|
@ -6,7 +6,7 @@ use czkawka_core::common_messages::Messages;
|
|||
|
||||
use crate::common::{get_is_header_mode, get_tool_model, set_tool_model};
|
||||
use crate::model_operations::{collect_full_path_from_model, deselect_all_items, filter_out_checked_items};
|
||||
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow};
|
||||
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow, Settings};
|
||||
|
||||
pub fn connect_delete_button(app: &MainWindow) {
|
||||
let a = app.as_weak();
|
||||
|
@ -17,9 +17,9 @@ pub fn connect_delete_button(app: &MainWindow) {
|
|||
|
||||
let model = get_tool_model(&app, active_tab);
|
||||
|
||||
let remove_to_trash = false;
|
||||
let settings = app.global::<Settings>();
|
||||
|
||||
let (errors, new_model) = handle_delete_items(&model, active_tab, remove_to_trash);
|
||||
let (errors, new_model) = handle_delete_items(&app, &model, active_tab, settings.get_move_to_trash());
|
||||
|
||||
if let Some(new_model) = new_model {
|
||||
set_tool_model(&app, active_tab, new_model);
|
||||
|
@ -31,13 +31,14 @@ pub fn connect_delete_button(app: &MainWindow) {
|
|||
});
|
||||
}
|
||||
|
||||
fn handle_delete_items(items: &ModelRc<MainListModel>, active_tab: CurrentTab, remove_to_trash: bool) -> (Vec<String>, Option<ModelRc<MainListModel>>) {
|
||||
fn handle_delete_items(app: &MainWindow, items: &ModelRc<MainListModel>, active_tab: CurrentTab, remove_to_trash: bool) -> (Vec<String>, Option<ModelRc<MainListModel>>) {
|
||||
let (entries_to_delete, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab));
|
||||
|
||||
if !entries_to_delete.is_empty() {
|
||||
let vec_items_to_remove = collect_full_path_from_model(&entries_to_delete, active_tab);
|
||||
let errors = remove_selected_items(vec_items_to_remove, active_tab, remove_to_trash);
|
||||
deselect_all_items(&mut entries_left);
|
||||
app.set_text_summary_text(format!("Deleted {} items, failed to remove {} items", entries_to_delete.len() - errors.len(), errors.len()).into());
|
||||
|
||||
let r = ModelRc::new(VecModel::from(entries_left)); // TODO here maybe should also stay old model if entries cannot be removed
|
||||
return (errors, Some(r));
|
||||
|
@ -48,7 +49,6 @@ fn handle_delete_items(items: &ModelRc<MainListModel>, active_tab: CurrentTab, r
|
|||
// TODO delete in parallel items, consider to add progress bar
|
||||
// For empty folders double check if folders are really empty - this function probably should be run in thread
|
||||
// and at the end should be send signal to main thread to update model
|
||||
// TODO handle also situations where cannot delete file/folder
|
||||
fn remove_selected_items(items_to_remove: Vec<String>, active_tab: CurrentTab, remove_to_trash: bool) -> Vec<String> {
|
||||
// Iterate over empty folders and not delete them if they are not empty
|
||||
if active_tab == CurrentTab::EmptyFolders {
|
||||
|
|
|
@ -37,4 +37,13 @@ pub fn connect_open_items(app: &MainWindow) {
|
|||
error!("Failed to open cache folder {:?}: {e}", cache_folder);
|
||||
}
|
||||
});
|
||||
|
||||
app.global::<Callabler>().on_open_link(move |link| {
|
||||
match open::that(link.as_str()) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to open link: {e}");
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::thread;
|
|||
use crossbeam_channel::Receiver;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
use czkawka_core::common_dir_traversal::{ProgressData, ToolType};
|
||||
use czkawka_core::common_dir_traversal::{CheckingMethod, ProgressData, ToolType};
|
||||
|
||||
use crate::{MainWindow, ProgressToSend};
|
||||
|
||||
|
@ -16,60 +16,117 @@ pub fn connect_progress_gathering(app: &MainWindow, progress_receiver: Receiver<
|
|||
};
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
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: format!("Checked {} files", progress_data.entries_checked).into(),
|
||||
let to_send = if progress_data.current_stage == 0 {
|
||||
progress_collect_items(&progress_data, progress_data.tool_type != ToolType::EmptyFolders)
|
||||
} else if check_if_loading_saving_cache(&progress_data) {
|
||||
progress_save_load_cache(&progress_data)
|
||||
} else {
|
||||
progress_default(&progress_data)
|
||||
};
|
||||
}
|
||||
ToolType::EmptyFolders => {
|
||||
let (all_progress, current_progress) = no_current_stage_get_data(&progress_data);
|
||||
to_send = ProgressToSend {
|
||||
all_progress,
|
||||
current_progress,
|
||||
step_name: format!("Checked {} folders", progress_data.entries_checked).into(),
|
||||
};
|
||||
}
|
||||
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: step_name.into(),
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid tool type {:?}", progress_data.tool_type);
|
||||
}
|
||||
}
|
||||
app.set_progress_datas(to_send);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn check_if_loading_saving_cache(progress_data: &ProgressData) -> bool {
|
||||
matches!(
|
||||
(progress_data.tool_type, progress_data.current_stage),
|
||||
(ToolType::SameMusic, 1 | 3) | (ToolType::Duplicate, 1 | 3 | 4 | 6)
|
||||
)
|
||||
}
|
||||
|
||||
fn progress_save_load_cache(item: &ProgressData) -> ProgressToSend {
|
||||
let step_name = match (item.tool_type, item.checking_method, item.current_stage) {
|
||||
(ToolType::SameMusic, CheckingMethod::AudioTags | CheckingMethod::AudioContent, 1) => "Loading cache",
|
||||
(ToolType::SameMusic, CheckingMethod::AudioTags | CheckingMethod::AudioContent, 3) => "Saving cache",
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 1) => "Loading prehash cache",
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 3) => "Saving prehash cache",
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 4) => "Loading hash cache",
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 6) => "Saving hash cache",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let (all_progress, current_progress) = common_get_data(item);
|
||||
ProgressToSend {
|
||||
all_progress,
|
||||
current_progress,
|
||||
step_name: step_name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_collect_items(item: &ProgressData, files: bool) -> ProgressToSend {
|
||||
let step_name = match (item.tool_type, item.checking_method) {
|
||||
(ToolType::Duplicate, CheckingMethod::Name) => {
|
||||
format!("Scanning name of {} file", item.entries_checked)
|
||||
}
|
||||
(ToolType::Duplicate, CheckingMethod::SizeName) => {
|
||||
format!("Scanning size and name of {} file", item.entries_checked)
|
||||
}
|
||||
(ToolType::Duplicate, CheckingMethod::Size | CheckingMethod::Hash) => {
|
||||
format!("Scanning size of {} file", item.entries_checked)
|
||||
}
|
||||
_ => {
|
||||
if files {
|
||||
format!("Scanning {} file", item.entries_checked)
|
||||
} else {
|
||||
format!("Scanning {} folder", item.entries_checked)
|
||||
}
|
||||
}
|
||||
};
|
||||
let (all_progress, current_progress) = no_current_stage_get_data(item);
|
||||
ProgressToSend {
|
||||
all_progress,
|
||||
current_progress,
|
||||
step_name: step_name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn progress_default(item: &ProgressData) -> ProgressToSend {
|
||||
let step_name = match (item.tool_type, item.checking_method, item.current_stage) {
|
||||
(ToolType::SameMusic, CheckingMethod::AudioTags, 2) | (ToolType::SameMusic, CheckingMethod::AudioContent, 5) => {
|
||||
format!("Checking tags of {}/{} audio file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SameMusic, CheckingMethod::AudioContent, 2) => {
|
||||
format!("Checking content of {}/{} audio file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SameMusic, CheckingMethod::AudioTags, 4) => {
|
||||
format!("Scanning tags of {}/{} audio file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SameMusic, CheckingMethod::AudioContent, 4) => {
|
||||
format!("Scanning content of {}/{} audio file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SimilarImages, _, 1) => {
|
||||
format!("Hashing of {}/{} image", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SimilarImages, _, 2) => {
|
||||
format!("Comparing {}/{} image hash", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::SimilarVideos, _, 1) => {
|
||||
format!("Hashing of {}/{} video", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::BrokenFiles, _, 1) => {
|
||||
format!("Checking {}/{} file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::BadExtensions, _, 1) => {
|
||||
format!("Checking {}/{} file", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 2) => {
|
||||
format!("Analyzing partial hash of {}/{} files", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
(ToolType::Duplicate, CheckingMethod::Hash, 5) => {
|
||||
format!("Analyzing full hash of {}/{} files", item.entries_checked, item.entries_to_check)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let (all_progress, current_progress) = common_get_data(item);
|
||||
ProgressToSend {
|
||||
all_progress,
|
||||
current_progress,
|
||||
step_name: step_name.into(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -81,10 +138,10 @@ fn no_current_stage_get_data(item: &ProgressData) -> (i32, i32) {
|
|||
// 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 = (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 = 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 * 100.0) as i32, (current_stage * 100.0) as i32)
|
||||
} else {
|
||||
|
|
|
@ -3,21 +3,33 @@ use std::thread;
|
|||
|
||||
use chrono::NaiveDateTime;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use czkawka_core::bad_extensions::{BadExtensions, BadFileEntry};
|
||||
use czkawka_core::big_file::{BigFile, SearchMode};
|
||||
use czkawka_core::broken_files::{BrokenEntry, BrokenFiles, CheckedTypes};
|
||||
use humansize::{format_size, BINARY};
|
||||
use rayon::prelude::*;
|
||||
use slint::{ComponentHandle, ModelRc, SharedString, VecModel, Weak};
|
||||
|
||||
use czkawka_core::common::{split_path, split_path_compare, DEFAULT_THREAD_SIZE};
|
||||
use czkawka_core::common_dir_traversal::{FileEntry, ProgressData};
|
||||
use czkawka_core::common_dir_traversal::{CheckingMethod, FileEntry, ProgressData};
|
||||
use czkawka_core::common_tool::CommonData;
|
||||
use czkawka_core::common_traits::ResultEntry;
|
||||
use czkawka_core::duplicate::{DuplicateEntry, DuplicateFinder};
|
||||
use czkawka_core::empty_files::EmptyFiles;
|
||||
use czkawka_core::empty_folder::{EmptyFolder, FolderEntry};
|
||||
use czkawka_core::invalid_symlinks::{InvalidSymlinks, SymlinksFileEntry};
|
||||
use czkawka_core::same_music::{MusicEntry, MusicSimilarity, SameMusic};
|
||||
use czkawka_core::similar_images;
|
||||
use czkawka_core::similar_images::{ImagesEntry, SimilarImages};
|
||||
use czkawka_core::similar_videos::{SimilarVideos, VideosEntry};
|
||||
use czkawka_core::temporary::{Temporary, TemporaryFileEntry};
|
||||
|
||||
use crate::common::split_u64_into_i32s;
|
||||
use crate::settings::{collect_settings, SettingsCustom, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES};
|
||||
use crate::settings::{
|
||||
collect_settings, get_audio_check_type_idx, get_biggest_item_idx, get_duplicates_check_method_idx, get_duplicates_hash_type_idx, get_image_hash_alg_idx,
|
||||
get_resize_algorithm_idx, SettingsCustom, ALLOWED_AUDIO_CHECK_TYPE_VALUES, ALLOWED_BIG_FILE_SIZE_VALUES, ALLOWED_DUPLICATES_CHECK_METHOD_VALUES,
|
||||
ALLOWED_DUPLICATES_HASH_TYPE_VALUES, ALLOWED_IMAGE_HASH_ALG_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES,
|
||||
};
|
||||
use crate::{CurrentTab, GuiState, MainListModel, MainWindow, ProgressToSend};
|
||||
|
||||
pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>) {
|
||||
|
@ -37,155 +49,141 @@ pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressDat
|
|||
|
||||
let a = app.as_weak();
|
||||
match active_tab {
|
||||
CurrentTab::DuplicateFiles => {
|
||||
scan_duplicates(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::EmptyFolders => {
|
||||
scan_empty_folders(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::BigFiles => {
|
||||
scan_big_files(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::EmptyFiles => {
|
||||
scan_empty_files(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::SimilarImages => {
|
||||
scan_similar_images(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::Settings => panic!("Button should be disabled"),
|
||||
CurrentTab::SimilarVideos => {
|
||||
scan_similar_videos(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::SimilarMusic => {
|
||||
scan_similar_music(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::InvalidSymlinks => {
|
||||
scan_invalid_symlinks(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::BadExtensions => {
|
||||
scan_bad_extensions(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::BrokenFiles => {
|
||||
scan_broken_files(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::TemporaryFiles => {
|
||||
scan_temporary_files(a, progress_sender, stop_receiver, custom_settings);
|
||||
}
|
||||
CurrentTab::Settings | CurrentTab::About => panic!("Button should be disabled"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
// Scan Duplicates
|
||||
|
||||
fn scan_duplicates(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = SimilarImages::new();
|
||||
let mut finder = DuplicateFinder::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
finder.set_hash_size(custom_settings.similar_images_sub_hash_size);
|
||||
let resize_algortithm = ALLOWED_RESIZE_ALGORITHM_VALUES
|
||||
.iter()
|
||||
.find(|(setting_name, _gui_name, _resize_alg)| setting_name == &custom_settings.similar_images_sub_resize_algorithm)
|
||||
.expect("Resize algorithm not found")
|
||||
.2;
|
||||
finder.set_image_filter(resize_algortithm);
|
||||
let hash_type = ALLOWED_HASH_TYPE_VALUES
|
||||
.iter()
|
||||
.find(|(setting_name, _gui_name, _resize_alg)| setting_name == &custom_settings.similar_images_sub_hash_type)
|
||||
.expect("Hash type not found")
|
||||
.2;
|
||||
finder.set_hash_alg(hash_type);
|
||||
dbg!(&custom_settings.similar_images_sub_ignore_same_size);
|
||||
finder.set_exclude_images_with_same_size(custom_settings.similar_images_sub_ignore_same_size);
|
||||
finder.set_similarity(custom_settings.similar_images_sub_similarity as u32);
|
||||
finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender));
|
||||
finder.set_check_method(CheckingMethod::Hash);
|
||||
finder.set_minimal_cache_file_size(custom_settings.duplicate_minimal_hash_cache_size as u64);
|
||||
finder.set_minimal_prehash_cache_file_size(custom_settings.duplicate_minimal_prehash_cache_size as u64);
|
||||
let check_method = ALLOWED_DUPLICATES_CHECK_METHOD_VALUES[get_duplicates_check_method_idx(&custom_settings.duplicates_sub_check_method).unwrap()].2;
|
||||
finder.set_check_method(check_method);
|
||||
let hash_type = ALLOWED_DUPLICATES_HASH_TYPE_VALUES[get_duplicates_hash_type_idx(&custom_settings.duplicates_sub_available_hash_type).unwrap()].2;
|
||||
finder.set_hash_type(hash_type);
|
||||
// finder.set_ignore_hard_links(custom_settings.ignore); // TODO
|
||||
finder.set_use_prehash_cache(custom_settings.duplicate_use_prehash);
|
||||
finder.set_delete_outdated_cache(custom_settings.duplicate_delete_outdated_entries);
|
||||
finder.set_case_sensitive_name_comparison(custom_settings.duplicates_sub_name_case_sensitive);
|
||||
finder.find_duplicates(Some(&stop_receiver), Some(&progress_sender));
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
let mut vector;
|
||||
if finder.get_use_reference() {
|
||||
let mut vector = finder.get_similar_images_referenced().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
let hash_size = custom_settings.similar_images_sub_hash_size;
|
||||
|
||||
for (_first_entry, vec_fe) in &mut vector {
|
||||
vec_fe.par_sort_unstable_by_key(|e| e.similarity);
|
||||
match finder.get_check_method() {
|
||||
CheckingMethod::Hash => {
|
||||
vector = finder
|
||||
.get_files_with_identical_hashes_referenced()
|
||||
.values()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.map(|(original, other)| (Some(original), other))
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
CheckingMethod::Name | CheckingMethod::Size | CheckingMethod::SizeName => {
|
||||
let values: Vec<_> = match finder.get_check_method() {
|
||||
CheckingMethod::Name => finder.get_files_with_identical_name_referenced().values().cloned().collect(),
|
||||
CheckingMethod::Size => finder.get_files_with_identical_size_referenced().values().cloned().collect(),
|
||||
CheckingMethod::SizeName => finder.get_files_with_identical_size_names_referenced().values().cloned().collect(),
|
||||
_ => unreachable!("Invalid check method."),
|
||||
};
|
||||
vector = values.into_iter().map(|(original, other)| (Some(original), other)).collect::<Vec<_>>();
|
||||
}
|
||||
_ => unreachable!("Invalid check method."),
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_similar_images_results_referenced(&app, vector, messages, hash_size);
|
||||
})
|
||||
} else {
|
||||
let mut vector = finder.get_similar_images().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
match finder.get_check_method() {
|
||||
CheckingMethod::Hash => {
|
||||
vector = finder.get_files_sorted_by_hash().values().flatten().cloned().map(|items| (None, items)).collect::<Vec<_>>();
|
||||
}
|
||||
CheckingMethod::Name | CheckingMethod::Size | CheckingMethod::SizeName => {
|
||||
let values: Vec<_> = match finder.get_check_method() {
|
||||
CheckingMethod::Name => finder.get_files_sorted_by_names().values().cloned().collect(),
|
||||
CheckingMethod::Size => finder.get_files_sorted_by_size().values().cloned().collect(),
|
||||
CheckingMethod::SizeName => finder.get_files_sorted_by_size_name().values().cloned().collect(),
|
||||
_ => unreachable!("Invalid check method."),
|
||||
};
|
||||
vector = values.into_iter().map(|items| (None, items)).collect::<Vec<_>>();
|
||||
}
|
||||
_ => unreachable!("Invalid check method."),
|
||||
}
|
||||
}
|
||||
|
||||
let hash_size = custom_settings.similar_images_sub_hash_size;
|
||||
|
||||
for vec_fe in &mut vector {
|
||||
vec_fe.par_sort_unstable_by_key(|e| e.similarity);
|
||||
for (_first, vec) in &mut vector {
|
||||
vec.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_similar_images_results(&app, vector, messages, hash_size);
|
||||
write_duplicate_results(&app, vector, messages);
|
||||
})
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_similar_images_results_referenced(app: &MainWindow, vector: Vec<(ImagesEntry, Vec<ImagesEntry>)>, messages: String, hash_size: u8) {
|
||||
fn write_duplicate_results(app: &MainWindow, vector: Vec<(Option<DuplicateEntry>, Vec<DuplicateEntry>)>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for (ref_fe, vec_fe) in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_images(&ref_fe, hash_size);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, true);
|
||||
if let Some(ref_fe) = ref_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_duplicates(&ref_fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, Some(true));
|
||||
} else {
|
||||
insert_data_to_model(&items, ModelRc::new(VecModel::default()), ModelRc::new(VecModel::default()), Some(false));
|
||||
}
|
||||
|
||||
for fe in vec_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_images(&fe, hash_size);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, false);
|
||||
let (data_model_str, data_model_int) = prepare_data_model_duplicates(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
}
|
||||
app.set_similar_images_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar images files").into());
|
||||
app.set_duplicate_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar duplicates files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
fn write_similar_images_results(app: &MainWindow, vector: Vec<Vec<ImagesEntry>>, messages: String, hash_size: u8) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for vec_fe in vector {
|
||||
insert_data_to_model(&items, ModelRc::new(VecModel::default()), ModelRc::new(VecModel::default()), true);
|
||||
for fe in vec_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_images(&fe, hash_size);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, false);
|
||||
}
|
||||
}
|
||||
app.set_similar_images_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar images files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
fn prepare_data_model_similar_images(fe: &ImagesEntry, hash_size: u8) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
fn prepare_data_model_duplicates(fe: &DuplicateEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
similar_images::get_string_from_similarity(&fe.similarity, hash_size).into(),
|
||||
format_size(fe.size, BINARY).into(),
|
||||
format!("{}x{}", fe.width, fe.height).into(),
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().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, fe.width as i32, fe.height as i32]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
|
||||
///////////////////////////////// Empty Files
|
||||
fn scan_empty_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = EmptyFiles::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
finder.find_empty_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_empty_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_empty_files_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_empty_files_results(app: &MainWindow, vector: Vec<FileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_empty_files(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, false);
|
||||
}
|
||||
app.set_empty_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} empty files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_empty_files(fe: &FileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
|
||||
|
@ -221,7 +219,7 @@ fn write_empty_folders_results(app: &MainWindow, vector: Vec<FolderEntry>, messa
|
|||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_empty_folders(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, false);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_empty_folder_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} empty folders").into());
|
||||
|
@ -240,11 +238,566 @@ fn prepare_data_model_empty_folders(fe: &FolderEntry) -> (ModelRc<SharedString>,
|
|||
(data_model_str, data_model_int)
|
||||
}
|
||||
|
||||
////////////////////////////////////////// Big files
|
||||
fn scan_big_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = BigFile::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
finder.set_number_of_files_to_check(custom_settings.biggest_files_sub_number_of_files as usize);
|
||||
let big_files_mode = ALLOWED_BIG_FILE_SIZE_VALUES[get_biggest_item_idx(&custom_settings.biggest_files_sub_method).unwrap()].2;
|
||||
finder.set_search_mode(big_files_mode);
|
||||
finder.find_big_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_big_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
if big_files_mode == SearchMode::BiggestFiles {
|
||||
vector.par_sort_unstable_by_key(|fe| u64::MAX - fe.size);
|
||||
} else {
|
||||
vector.par_sort_unstable_by_key(|fe| fe.size);
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_big_files_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_big_files_results(app: &MainWindow, vector: Vec<FileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_big_files(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_big_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_big_files(fe: &FileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(&fe.path);
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
format_size(fe.size, BINARY).into(),
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
|
||||
///////////////////////////////// Empty Files
|
||||
fn scan_empty_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = EmptyFiles::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
finder.find_empty_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_empty_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_empty_files_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_empty_files_results(app: &MainWindow, vector: Vec<FileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_empty_files(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_empty_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} empty files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_empty_files(fe: &FileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
// Scan Similar Images
|
||||
|
||||
fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = SimilarImages::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
finder.set_similarity(custom_settings.similar_images_sub_similarity as u32);
|
||||
let hash_alg = ALLOWED_IMAGE_HASH_ALG_VALUES[get_image_hash_alg_idx(&custom_settings.similar_images_sub_hash_alg).unwrap()].2;
|
||||
finder.set_hash_alg(hash_alg);
|
||||
finder.set_hash_size(custom_settings.similar_images_sub_hash_size);
|
||||
let resize_algorithm = ALLOWED_RESIZE_ALGORITHM_VALUES[get_resize_algorithm_idx(&custom_settings.similar_images_sub_resize_algorithm).unwrap()].2;
|
||||
finder.set_image_filter(resize_algorithm);
|
||||
finder.set_delete_outdated_cache(custom_settings.similar_images_delete_outdated_entries);
|
||||
finder.set_exclude_images_with_same_size(custom_settings.similar_images_sub_ignore_same_size);
|
||||
|
||||
finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
let hash_size = custom_settings.similar_images_sub_hash_size;
|
||||
|
||||
let mut vector;
|
||||
if finder.get_use_reference() {
|
||||
vector = finder
|
||||
.get_similar_images_referenced()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(original, others)| (Some(original), others))
|
||||
.collect::<Vec<_>>();
|
||||
} else {
|
||||
vector = finder.get_similar_images().iter().cloned().map(|items| (None, items)).collect::<Vec<_>>();
|
||||
}
|
||||
for (_first_entry, vec_fe) in &mut vector {
|
||||
vec_fe.par_sort_unstable_by_key(|e| e.similarity);
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_similar_images_results(&app, vector, messages, hash_size);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_similar_images_results(app: &MainWindow, vector: Vec<(Option<ImagesEntry>, Vec<ImagesEntry>)>, messages: String, hash_size: u8) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for (ref_fe, vec_fe) in vector {
|
||||
if let Some(ref_fe) = ref_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_images(&ref_fe, hash_size);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, Some(true));
|
||||
} else {
|
||||
insert_data_to_model(&items, ModelRc::new(VecModel::default()), ModelRc::new(VecModel::default()), Some(false));
|
||||
}
|
||||
|
||||
for fe in vec_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_images(&fe, hash_size);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
}
|
||||
app.set_similar_images_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar image files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
fn prepare_data_model_similar_images(fe: &ImagesEntry, hash_size: u8) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
similar_images::get_string_from_similarity(&fe.similarity, hash_size).into(),
|
||||
format_size(fe.size, BINARY).into(),
|
||||
format!("{}x{}", fe.width, fe.height).into(),
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().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, fe.width as i32, fe.height as i32]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
|
||||
// Scan Similar Videos
|
||||
|
||||
fn scan_similar_videos(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = SimilarVideos::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
finder.set_tolerance(custom_settings.similar_videos_sub_similarity);
|
||||
finder.set_delete_outdated_cache(custom_settings.similar_videos_delete_outdated_entries);
|
||||
finder.set_exclude_videos_with_same_size(custom_settings.similar_videos_sub_ignore_same_size);
|
||||
|
||||
finder.find_similar_videos(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
let mut vector;
|
||||
if finder.get_use_reference() {
|
||||
vector = finder
|
||||
.get_similar_videos_referenced()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(original, others)| (Some(original), others))
|
||||
.collect::<Vec<_>>();
|
||||
} else {
|
||||
vector = finder.get_similar_videos().iter().cloned().map(|items| (None, items)).collect::<Vec<_>>();
|
||||
}
|
||||
for (_first_entry, vec_fe) in &mut vector {
|
||||
vec_fe.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_similar_videos_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_similar_videos_results(app: &MainWindow, vector: Vec<(Option<VideosEntry>, Vec<VideosEntry>)>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for (ref_fe, vec_fe) in vector {
|
||||
if let Some(ref_fe) = ref_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_videos(&ref_fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, Some(true));
|
||||
} else {
|
||||
insert_data_to_model(&items, ModelRc::new(VecModel::default()), ModelRc::new(VecModel::default()), Some(false));
|
||||
}
|
||||
|
||||
for fe in vec_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_videos(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
}
|
||||
app.set_similar_videos_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar video files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
fn prepare_data_model_similar_videos(fe: &VideosEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
format_size(fe.size, BINARY).into(),
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
// Scan Similar Music
|
||||
fn scan_similar_music(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = SameMusic::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
let mut music_similarity: MusicSimilarity = MusicSimilarity::NONE;
|
||||
if custom_settings.similar_music_sub_title {
|
||||
music_similarity |= MusicSimilarity::TRACK_TITLE;
|
||||
}
|
||||
if custom_settings.similar_music_sub_artist {
|
||||
music_similarity |= MusicSimilarity::TRACK_ARTIST;
|
||||
}
|
||||
if custom_settings.similar_music_sub_bitrate {
|
||||
music_similarity |= MusicSimilarity::BITRATE;
|
||||
}
|
||||
if custom_settings.similar_music_sub_length {
|
||||
music_similarity |= MusicSimilarity::LENGTH;
|
||||
}
|
||||
if custom_settings.similar_music_sub_year {
|
||||
music_similarity |= MusicSimilarity::YEAR;
|
||||
}
|
||||
if custom_settings.similar_music_sub_genre {
|
||||
music_similarity |= MusicSimilarity::GENRE;
|
||||
}
|
||||
|
||||
if music_similarity == MusicSimilarity::NONE {
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
app.set_text_summary_text("Cannot find similar music files without any similarity method selected.".into());
|
||||
})
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
finder.set_music_similarity(music_similarity);
|
||||
finder.set_maximum_difference(custom_settings.similar_music_sub_maximum_difference_value as f64);
|
||||
finder.set_minimum_segment_duration(custom_settings.similar_music_sub_minimal_fragment_duration_value);
|
||||
let audio_check_type = ALLOWED_AUDIO_CHECK_TYPE_VALUES[get_audio_check_type_idx(&custom_settings.similar_music_sub_audio_check_type).unwrap()].2;
|
||||
finder.set_check_type(audio_check_type);
|
||||
finder.set_approximate_comparison(custom_settings.similar_music_sub_approximate_comparison);
|
||||
|
||||
finder.find_same_music(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
let mut vector;
|
||||
if finder.get_use_reference() {
|
||||
vector = finder
|
||||
.get_similar_music_referenced()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(original, others)| (Some(original), others))
|
||||
.collect::<Vec<_>>();
|
||||
} else {
|
||||
vector = finder.get_duplicated_music_entries().iter().cloned().map(|items| (None, items)).collect::<Vec<_>>();
|
||||
}
|
||||
for (_first_entry, vec_fe) in &mut vector {
|
||||
vec_fe.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
}
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_similar_music_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_similar_music_results(app: &MainWindow, vector: Vec<(Option<MusicEntry>, Vec<MusicEntry>)>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for (ref_fe, vec_fe) in vector {
|
||||
if let Some(ref_fe) = ref_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_music(&ref_fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, Some(true));
|
||||
} else {
|
||||
insert_data_to_model(&items, ModelRc::new(VecModel::default()), ModelRc::new(VecModel::default()), Some(false));
|
||||
}
|
||||
|
||||
for fe in vec_fe {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_similar_music(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
}
|
||||
app.set_similar_music_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} similar music files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
fn prepare_data_model_similar_music(fe: &MusicEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
format_size(fe.size, BINARY).into(),
|
||||
file.into(),
|
||||
fe.track_title.clone().into(),
|
||||
fe.track_artist.clone().into(),
|
||||
fe.year.clone().into(),
|
||||
fe.bitrate.to_string().into(),
|
||||
fe.length.clone().into(),
|
||||
fe.genre.clone().into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
// Invalid Symlinks
|
||||
fn scan_invalid_symlinks(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = InvalidSymlinks::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
finder.find_invalid_links(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_invalid_symlinks().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_invalid_symlinks_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_invalid_symlinks_results(app: &MainWindow, vector: Vec<SymlinksFileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_invalid_symlinks(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_invalid_symlinks_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} invalid symlinks").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_invalid_symlinks(fe: &SymlinksFileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(fe.get_path());
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
file.into(),
|
||||
directory.into(),
|
||||
fe.symlink_info.destination_path.to_string_lossy().to_string().into(),
|
||||
fe.symlink_info.type_of_error.to_string().into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
|
||||
]);
|
||||
let modification_split = split_u64_into_i32s(fe.get_modified_date());
|
||||
let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1]);
|
||||
(data_model_str, data_model_int)
|
||||
} ////////////////////////////////////////// Temporary Files
|
||||
fn scan_temporary_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = Temporary::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
finder.find_temporary_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_temporary_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_temporary_files_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_temporary_files_results(app: &MainWindow, vector: Vec<TemporaryFileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_temporary_files(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_temporary_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_temporary_files(fe: &TemporaryFileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(&fe.path);
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
file.into(),
|
||||
directory.into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().into(),
|
||||
]);
|
||||
let modification_split = split_u64_into_i32s(fe.get_modified_date());
|
||||
let data_model_int = VecModel::from_slice(&[modification_split.0, modification_split.1]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
////////////////////////////////////////// Broken Files
|
||||
fn scan_broken_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = BrokenFiles::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
|
||||
let mut checked_types: CheckedTypes = CheckedTypes::NONE;
|
||||
if custom_settings.broken_files_sub_audio {
|
||||
checked_types |= CheckedTypes::AUDIO;
|
||||
}
|
||||
if custom_settings.broken_files_sub_pdf {
|
||||
checked_types |= CheckedTypes::PDF;
|
||||
}
|
||||
if custom_settings.broken_files_sub_image {
|
||||
checked_types |= CheckedTypes::IMAGE;
|
||||
}
|
||||
if custom_settings.broken_files_sub_archive {
|
||||
checked_types |= CheckedTypes::ARCHIVE;
|
||||
}
|
||||
|
||||
if checked_types == CheckedTypes::NONE {
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
app.set_text_summary_text("Cannot find broken files without any file type selected.".into());
|
||||
})
|
||||
.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
finder.set_checked_types(checked_types);
|
||||
finder.find_broken_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_broken_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_broken_files_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_broken_files_results(app: &MainWindow, vector: Vec<BrokenEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_broken_files(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_broken_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} files").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_broken_files(fe: &BrokenEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
let (directory, file) = split_path(&fe.path);
|
||||
let data_model_str = VecModel::from_slice(&[
|
||||
file.into(),
|
||||
directory.into(),
|
||||
fe.error_string.clone().into(),
|
||||
format_size(fe.size, BINARY).into(),
|
||||
NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
////////////////////////////////////////// Bad Extensions
|
||||
fn scan_bad_extensions(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
|
||||
thread::Builder::new()
|
||||
.stack_size(DEFAULT_THREAD_SIZE)
|
||||
.spawn(move || {
|
||||
let mut finder = BadExtensions::new();
|
||||
set_common_settings(&mut finder, &custom_settings);
|
||||
finder.find_bad_extensions_files(Some(&stop_receiver), Some(&progress_sender));
|
||||
|
||||
let mut vector = finder.get_bad_extensions_files().clone();
|
||||
let messages = finder.get_text_messages().create_messages_text();
|
||||
|
||||
vector.par_sort_unstable_by(|a, b| split_path_compare(a.path.as_path(), b.path.as_path()));
|
||||
|
||||
a.upgrade_in_event_loop(move |app| {
|
||||
write_bad_extensions_results(&app, vector, messages);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn write_bad_extensions_results(app: &MainWindow, vector: Vec<BadFileEntry>, messages: String) {
|
||||
let items_found = vector.len();
|
||||
let items = Rc::new(VecModel::default());
|
||||
for fe in vector {
|
||||
let (data_model_str, data_model_int) = prepare_data_model_bad_extensions(&fe);
|
||||
insert_data_to_model(&items, data_model_str, data_model_int, None);
|
||||
}
|
||||
app.set_bad_extensions_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {items_found} files with bad extensions").into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
}
|
||||
|
||||
fn prepare_data_model_bad_extensions(fe: &BadFileEntry) -> (ModelRc<SharedString>, ModelRc<i32>) {
|
||||
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 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]);
|
||||
(data_model_str, data_model_int)
|
||||
}
|
||||
////////////////////////////////////////// Common
|
||||
fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model_str: ModelRc<SharedString>, data_model_int: ModelRc<i32>, header_row: bool) {
|
||||
fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model_str: ModelRc<SharedString>, data_model_int: ModelRc<i32>, full_header_row: Option<bool>) {
|
||||
let main = MainListModel {
|
||||
checked: false,
|
||||
header_row,
|
||||
header_row: full_header_row.is_some(),
|
||||
full_header_row: full_header_row.unwrap_or(false),
|
||||
selected_row: false,
|
||||
val_str: ModelRc::new(data_model_str),
|
||||
val_int: ModelRc::new(data_model_int),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::panic;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
|
@ -21,7 +22,9 @@ 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() {
|
||||
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;
|
||||
}
|
||||
|
@ -70,7 +73,7 @@ fn convert_into_slint_image(img: DynamicImage) -> slint::Image {
|
|||
slint::Image::from_rgba8(buffer)
|
||||
}
|
||||
|
||||
fn load_image(image_path: &Path) -> Option<(Duration, image::DynamicImage)> {
|
||||
fn load_image(image_path: &Path) -> Option<(Duration, DynamicImage)> {
|
||||
if !image_path.is_file() {
|
||||
return None;
|
||||
}
|
||||
|
@ -80,13 +83,10 @@ fn load_image(image_path: &Path) -> Option<(Duration, image::DynamicImage)> {
|
|||
let is_raw_image = RAW_IMAGE_EXTENSIONS.contains(&image_extension.as_str());
|
||||
let is_normal_image = IMAGE_RS_EXTENSIONS.contains(&image_extension.as_str());
|
||||
|
||||
if !is_raw_image && !is_normal_image {
|
||||
return None;
|
||||
}
|
||||
let load_img_start_timer = Instant::now();
|
||||
|
||||
// TODO this needs to be run inside closure
|
||||
let img = if is_normal_image {
|
||||
let img = panic::catch_unwind(|| {
|
||||
let int_img = if is_normal_image {
|
||||
match image::open(image_name) {
|
||||
Ok(img) => img,
|
||||
Err(e) => {
|
||||
|
@ -102,8 +102,13 @@ fn load_image(image_path: &Path) -> Option<(Duration, image::DynamicImage)> {
|
|||
return None;
|
||||
}
|
||||
} else {
|
||||
panic!("Used not supported image extension");
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(int_img)
|
||||
})
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Error while loading image: {e:?}");
|
||||
None
|
||||
})?;
|
||||
Some((load_img_start_timer.elapsed(), img))
|
||||
}
|
||||
|
|
|
@ -64,10 +64,6 @@ fn main() {
|
|||
let (progress_sender, progress_receiver): (Sender<ProgressData>, Receiver<ProgressData>) = unbounded();
|
||||
let (stop_sender, stop_receiver): (Sender<()>, Receiver<()>) = unbounded();
|
||||
|
||||
// to_remove_debug(&app);
|
||||
|
||||
// Slint files may already contains data in models, so clear them before starting - todo,
|
||||
// check if non zeroed models are useful
|
||||
zeroing_all_models(&app);
|
||||
|
||||
set_initial_gui_infos(&app);
|
||||
|
@ -97,57 +93,12 @@ pub fn zeroing_all_models(app: &MainWindow) {
|
|||
app.set_empty_folder_model(Rc::new(VecModel::default()).into());
|
||||
app.set_empty_files_model(Rc::new(VecModel::default()).into());
|
||||
app.set_similar_images_model(Rc::new(VecModel::default()).into());
|
||||
app.set_duplicate_files_model(Rc::new(VecModel::default()).into());
|
||||
app.set_similar_music_model(Rc::new(VecModel::default()).into());
|
||||
app.set_big_files_model(Rc::new(VecModel::default()).into());
|
||||
app.set_bad_extensions_model(Rc::new(VecModel::default()).into());
|
||||
app.set_broken_files_model(Rc::new(VecModel::default()).into());
|
||||
app.set_similar_videos_model(Rc::new(VecModel::default()).into());
|
||||
app.set_invalid_symlinks_model(Rc::new(VecModel::default()).into());
|
||||
app.set_temporary_files_model(Rc::new(VecModel::default()).into());
|
||||
}
|
||||
|
||||
// // TODO remove this after debugging - or leave commented
|
||||
// pub fn to_remove_debug(app: &MainWindow) {
|
||||
// app.set_empty_folder_model(to_remove_create_without_header("@@").into());
|
||||
// app.set_empty_files_model(to_remove_create_without_header("%%").into());
|
||||
// app.set_similar_images_model(to_remove_create_with_header().into());
|
||||
// }
|
||||
|
||||
// fn to_remove_create_with_header() -> Rc<VecModel<MainListModel>> {
|
||||
// let header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
|
||||
// for r in 0..10_000 {
|
||||
// let items = VecModel::default();
|
||||
//
|
||||
// for c in 0..3 {
|
||||
// items.push(slint::format!("Item {r}.{c}"));
|
||||
// }
|
||||
//
|
||||
// let is_header = r % 3 == 0;
|
||||
// let is_checked = (r % 2 == 0) && !is_header;
|
||||
//
|
||||
// let item = MainListModel {
|
||||
// checked: is_checked,
|
||||
// header_row: is_header,
|
||||
// selected_row: false,
|
||||
// val: ModelRc::new(items),
|
||||
// };
|
||||
//
|
||||
// header_row_data.push(item);
|
||||
// }
|
||||
// header_row_data
|
||||
// }
|
||||
// fn to_remove_create_without_header(s: &str) -> Rc<VecModel<MainListModel>> {
|
||||
// let non_header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
|
||||
// for r in 0..100_000 {
|
||||
// let items = VecModel::default();
|
||||
//
|
||||
// for c in 0..3 {
|
||||
// items.push(slint::format!("Item {r}.{c}.{s}"));
|
||||
// }
|
||||
//
|
||||
// let is_checked = r % 2 == 0;
|
||||
//
|
||||
// let item = MainListModel {
|
||||
// checked: is_checked,
|
||||
// header_row: false,
|
||||
// selected_row: false,
|
||||
// val: ModelRc::new(items),
|
||||
// };
|
||||
//
|
||||
// non_header_row_data.push(item);
|
||||
// }
|
||||
// non_header_row_data
|
||||
// }
|
||||
|
|
|
@ -34,8 +34,6 @@ pub fn collect_path_name_from_model(items: &[MainListModel], active_tab: Current
|
|||
items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
dbg!(item.val_str.iter().nth(path_idx).unwrap().to_string());
|
||||
dbg!(item.val_str.iter().nth(name_idx).unwrap().to_string());
|
||||
(
|
||||
item.val_str.iter().nth(path_idx).unwrap().to_string(),
|
||||
item.val_str.iter().nth(name_idx).unwrap().to_string(),
|
||||
|
@ -228,6 +226,7 @@ mod tests {
|
|||
model.push(MainListModel {
|
||||
checked: item.0,
|
||||
header_row: item.1,
|
||||
full_header_row: false, // TODO - this needs to be calculated
|
||||
selected_row: item.2,
|
||||
val_str: ModelRc::new(all_items),
|
||||
val_int: ModelRc::new(VecModel::default()),
|
||||
|
|
|
@ -2,11 +2,13 @@ use slint::{ComponentHandle, SharedString, VecModel};
|
|||
|
||||
use czkawka_core::common::get_all_available_threads;
|
||||
|
||||
use crate::settings::{ALLOWED_HASH_SIZE_VALUES, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES};
|
||||
use crate::settings::{
|
||||
ALLOWED_BIG_FILE_SIZE_VALUES, ALLOWED_DUPLICATES_CHECK_METHOD_VALUES, ALLOWED_DUPLICATES_HASH_TYPE_VALUES, ALLOWED_HASH_SIZE_VALUES, ALLOWED_IMAGE_HASH_ALG_VALUES,
|
||||
ALLOWED_RESIZE_ALGORITHM_VALUES,
|
||||
};
|
||||
use crate::{GuiState, MainWindow, Settings};
|
||||
|
||||
// Some info needs to be send to gui at the start like available thread number in OS.
|
||||
//
|
||||
pub fn set_initial_gui_infos(app: &MainWindow) {
|
||||
let threads = get_all_available_threads();
|
||||
let settings = app.global::<Settings>();
|
||||
|
@ -20,7 +22,19 @@ pub fn set_initial_gui_infos(app: &MainWindow) {
|
|||
.iter()
|
||||
.map(|(_settings_key, gui_name, _filter_type)| (*gui_name).into())
|
||||
.collect::<Vec<_>>();
|
||||
let available_hash_type: Vec<SharedString> = ALLOWED_HASH_TYPE_VALUES
|
||||
let available_hash_type: Vec<SharedString> = ALLOWED_IMAGE_HASH_ALG_VALUES
|
||||
.iter()
|
||||
.map(|(_settings_key, gui_name, _hash_type)| (*gui_name).into())
|
||||
.collect::<Vec<_>>();
|
||||
let available_big_file_search_mode: Vec<SharedString> = ALLOWED_BIG_FILE_SIZE_VALUES
|
||||
.iter()
|
||||
.map(|(_settings_key, gui_name, _search_mode)| (*gui_name).into())
|
||||
.collect::<Vec<_>>();
|
||||
let available_duplicates_check_method: Vec<SharedString> = ALLOWED_DUPLICATES_CHECK_METHOD_VALUES
|
||||
.iter()
|
||||
.map(|(_settings_key, gui_name, _checking_method)| (*gui_name).into())
|
||||
.collect::<Vec<_>>();
|
||||
let available_duplicates_hash_type: Vec<SharedString> = ALLOWED_DUPLICATES_HASH_TYPE_VALUES
|
||||
.iter()
|
||||
.map(|(_settings_key, gui_name, _hash_type)| (*gui_name).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -28,4 +42,7 @@ pub fn set_initial_gui_infos(app: &MainWindow) {
|
|||
settings.set_similar_images_sub_available_hash_size(VecModel::from_slice(&available_hash_size));
|
||||
settings.set_similar_images_sub_available_resize_algorithm(VecModel::from_slice(&available_resize_algorithm));
|
||||
settings.set_similar_images_sub_available_hash_type(VecModel::from_slice(&available_hash_type));
|
||||
settings.set_biggest_files_sub_method(VecModel::from_slice(&available_big_file_search_mode));
|
||||
settings.set_duplicates_sub_check_method(VecModel::from_slice(&available_duplicates_check_method));
|
||||
settings.set_duplicates_sub_available_hash_type(VecModel::from_slice(&available_duplicates_hash_type));
|
||||
}
|
||||
|
|
|
@ -2,15 +2,18 @@ use std::cmp::{max, min};
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use czkawka_core::big_file::SearchMode;
|
||||
use directories_next::ProjectDirs;
|
||||
use home::home_dir;
|
||||
use image_hasher::{FilterType, HashAlg};
|
||||
use log::{debug, error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slint::{ComponentHandle, Model, ModelRc};
|
||||
use slint::{ComponentHandle, Model, ModelRc, SharedString, VecModel};
|
||||
|
||||
use czkawka_core::common::{get_all_available_threads, set_number_of_threads};
|
||||
use czkawka_core::common_dir_traversal::CheckingMethod;
|
||||
use czkawka_core::common_items::{DEFAULT_EXCLUDED_DIRECTORIES, DEFAULT_EXCLUDED_ITEMS};
|
||||
use czkawka_core::duplicate::HashType;
|
||||
|
||||
use crate::common::{create_excluded_directories_model_from_pathbuf, create_included_directories_model_from_pathbuf, create_vec_model_from_vec_string};
|
||||
use crate::{Callabler, GuiState, MainWindow, Settings};
|
||||
|
@ -19,6 +22,12 @@ pub const DEFAULT_MINIMUM_SIZE_KB: i32 = 16;
|
|||
pub const DEFAULT_MAXIMUM_SIZE_KB: i32 = i32::MAX / 1024;
|
||||
pub const DEFAULT_MINIMUM_CACHE_SIZE: i32 = 256;
|
||||
pub const DEFAULT_MINIMUM_PREHASH_CACHE_SIZE: i32 = 256;
|
||||
pub const DEFAULT_BIGGEST_FILES: i32 = 50;
|
||||
pub const DEFAULT_IMAGE_SIMILARITY: i32 = 10;
|
||||
pub const DEFAULT_VIDEO_SIMILARITY: i32 = 15;
|
||||
pub const DEFAULT_HASH_SIZE: u8 = 16;
|
||||
pub const DEFAULT_MAXIMUM_DIFFERENCE_VALUE: f32 = 3.0;
|
||||
pub const DEFAULT_MINIMAL_FRAGMENT_DURATION_VALUE: f32 = 5.0;
|
||||
|
||||
// (Hash size, Maximum difference) - Ehh... to simplify it, just use everywhere 40 as maximum similarity - for now I'm to lazy to change it, when hash size changes
|
||||
// So if you want to change it, you need to change it in multiple places
|
||||
|
@ -32,13 +41,31 @@ pub const ALLOWED_RESIZE_ALGORITHM_VALUES: &[(&str, &str, FilterType)] = &[
|
|||
("nearest", "Nearest", FilterType::Nearest),
|
||||
];
|
||||
|
||||
pub const ALLOWED_HASH_TYPE_VALUES: &[(&str, &str, HashAlg)] = &[
|
||||
pub const ALLOWED_IMAGE_HASH_ALG_VALUES: &[(&str, &str, HashAlg)] = &[
|
||||
("mean", "Mean", HashAlg::Mean),
|
||||
("gradient", "Gradient", HashAlg::Gradient),
|
||||
("blockhash", "BlockHash", HashAlg::Blockhash),
|
||||
("vertgradient", "VertGradient", HashAlg::VertGradient),
|
||||
("doublegradient", "DoubleGradient", HashAlg::DoubleGradient),
|
||||
];
|
||||
pub const ALLOWED_BIG_FILE_SIZE_VALUES: &[(&str, &str, SearchMode)] = &[
|
||||
("biggest", "The Biggest", SearchMode::BiggestFiles),
|
||||
("smallest", "The Smallest", SearchMode::SmallestFiles),
|
||||
];
|
||||
pub const ALLOWED_AUDIO_CHECK_TYPE_VALUES: &[(&str, &str, CheckingMethod)] =
|
||||
&[("tags", "Tags", CheckingMethod::AudioTags), ("fingerprint", "Fingerprint", CheckingMethod::AudioContent)];
|
||||
|
||||
pub const ALLOWED_DUPLICATES_CHECK_METHOD_VALUES: &[(&str, &str, CheckingMethod)] = &[
|
||||
("hash", "Hash", CheckingMethod::Hash),
|
||||
("size", "Size", CheckingMethod::Size),
|
||||
("name", "Name", CheckingMethod::Name),
|
||||
("size_and_name", "Size and Name", CheckingMethod::SizeName),
|
||||
];
|
||||
pub const ALLOWED_DUPLICATES_HASH_TYPE_VALUES: &[(&str, &str, HashType)] = &[
|
||||
("blake3", "Blake3", HashType::Blake3),
|
||||
("crc32", "CRC32", HashType::Crc32),
|
||||
("xxh3", "XXH3", HashType::Xxh3),
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SettingsCustom {
|
||||
|
@ -93,17 +120,55 @@ pub struct SettingsCustom {
|
|||
#[serde(default = "default_sub_hash_size")]
|
||||
pub similar_images_sub_hash_size: u8,
|
||||
#[serde(default = "default_hash_type")]
|
||||
pub similar_images_sub_hash_type: String,
|
||||
pub similar_images_sub_hash_alg: String,
|
||||
#[serde(default = "default_resize_algorithm")]
|
||||
pub similar_images_sub_resize_algorithm: String,
|
||||
#[serde(default)]
|
||||
pub similar_images_sub_ignore_same_size: bool,
|
||||
#[serde(default = "default_similarity")]
|
||||
#[serde(default = "default_image_similarity")]
|
||||
pub similar_images_sub_similarity: i32,
|
||||
}
|
||||
|
||||
pub fn default_similarity() -> i32 {
|
||||
10
|
||||
#[serde(default = "default_duplicates_check_method")]
|
||||
pub duplicates_sub_check_method: String,
|
||||
#[serde(default = "default_duplicates_hash_type")]
|
||||
pub duplicates_sub_available_hash_type: String,
|
||||
#[serde(default)]
|
||||
pub duplicates_sub_name_case_sensitive: bool,
|
||||
#[serde(default = "default_biggest_method")]
|
||||
pub biggest_files_sub_method: String,
|
||||
#[serde(default = "default_biggest_files")]
|
||||
pub biggest_files_sub_number_of_files: i32,
|
||||
#[serde(default)]
|
||||
pub similar_videos_sub_ignore_same_size: bool,
|
||||
#[serde(default = "default_video_similarity")]
|
||||
pub similar_videos_sub_similarity: i32,
|
||||
#[serde(default = "default_audio_check_type")]
|
||||
pub similar_music_sub_audio_check_type: String,
|
||||
#[serde(default)]
|
||||
pub similar_music_sub_approximate_comparison: bool,
|
||||
#[serde(default = "ttrue")]
|
||||
pub similar_music_sub_title: bool,
|
||||
#[serde(default = "ttrue")]
|
||||
pub similar_music_sub_artist: bool,
|
||||
#[serde(default)]
|
||||
pub similar_music_sub_year: bool,
|
||||
#[serde(default)]
|
||||
pub similar_music_sub_bitrate: bool,
|
||||
#[serde(default)]
|
||||
pub similar_music_sub_genre: bool,
|
||||
#[serde(default)]
|
||||
pub similar_music_sub_length: bool,
|
||||
#[serde(default = "default_maximum_difference_value")]
|
||||
pub similar_music_sub_maximum_difference_value: f32,
|
||||
#[serde(default = "default_minimal_fragment_duration_value")]
|
||||
pub similar_music_sub_minimal_fragment_duration_value: f32,
|
||||
#[serde(default = "ttrue")]
|
||||
pub broken_files_sub_audio: bool,
|
||||
#[serde(default)]
|
||||
pub broken_files_sub_pdf: bool,
|
||||
#[serde(default)]
|
||||
pub broken_files_sub_archive: bool,
|
||||
#[serde(default)]
|
||||
pub broken_files_sub_image: bool,
|
||||
}
|
||||
|
||||
impl Default for SettingsCustom {
|
||||
|
@ -199,7 +264,7 @@ pub fn create_default_settings_files() {
|
|||
}
|
||||
}
|
||||
|
||||
for i in 1..=10 {
|
||||
for i in 0..10 {
|
||||
let config_file = get_config_file(i);
|
||||
if let Some(config_file) = config_file {
|
||||
if !config_file.is_file() {
|
||||
|
@ -377,57 +442,103 @@ pub fn set_settings_to_gui(app: &MainWindow, custom_settings: &SettingsCustom) {
|
|||
settings.set_duplicate_minimal_hash_cache_size(custom_settings.duplicate_minimal_hash_cache_size.to_string().into());
|
||||
settings.set_duplicate_minimal_prehash_cache_size(custom_settings.duplicate_minimal_prehash_cache_size.to_string().into());
|
||||
settings.set_duplicate_delete_outdated_entries(custom_settings.duplicate_delete_outdated_entries);
|
||||
settings.set_duplicates_sub_name_case_sensitive(custom_settings.duplicates_sub_name_case_sensitive);
|
||||
settings.set_similar_images_show_image_preview(custom_settings.similar_images_show_image_preview);
|
||||
settings.set_similar_images_delete_outdated_entries(custom_settings.similar_images_delete_outdated_entries);
|
||||
settings.set_similar_videos_delete_outdated_entries(custom_settings.similar_videos_delete_outdated_entries);
|
||||
settings.set_similar_music_delete_outdated_entries(custom_settings.similar_music_delete_outdated_entries);
|
||||
|
||||
let similar_images_sub_hash_size_idx = if let Some(idx) = ALLOWED_HASH_SIZE_VALUES
|
||||
.iter()
|
||||
.position(|(hash_size, _max_similarity)| *hash_size == custom_settings.similar_images_sub_hash_size)
|
||||
{
|
||||
idx
|
||||
} else {
|
||||
let similar_images_sub_hash_size_idx = get_allowed_hash_size_idx(custom_settings.similar_images_sub_hash_size).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of hash size \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.similar_images_sub_hash_size
|
||||
);
|
||||
0
|
||||
};
|
||||
});
|
||||
settings.set_similar_images_sub_hash_size_index(similar_images_sub_hash_size_idx as i32);
|
||||
|
||||
let similar_images_sub_hash_type_idx = if let Some(idx) = ALLOWED_HASH_TYPE_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, _gui_name, _hash_type)| *settings_key == custom_settings.similar_images_sub_hash_type)
|
||||
{
|
||||
idx
|
||||
} else {
|
||||
settings.set_similar_images_sub_hash_size_value(ALLOWED_HASH_SIZE_VALUES[similar_images_sub_hash_size_idx].1.to_string().into());
|
||||
// TODO all items with _value are not necessary, but due bug in slint are required, because combobox is not updated properly
|
||||
let similar_images_sub_hash_alg_idx = get_image_hash_alg_idx(&custom_settings.similar_images_sub_hash_alg).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of hash type \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.similar_images_sub_hash_type
|
||||
custom_settings.similar_images_sub_hash_alg
|
||||
);
|
||||
0
|
||||
};
|
||||
settings.set_similar_images_sub_hash_type_index(similar_images_sub_hash_type_idx as i32);
|
||||
|
||||
let similar_images_sub_resize_algorithm_idx = if let Some(idx) = ALLOWED_RESIZE_ALGORITHM_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, _gui_name, _resize_alg)| *settings_key == custom_settings.similar_images_sub_resize_algorithm)
|
||||
{
|
||||
idx
|
||||
} else {
|
||||
});
|
||||
settings.set_similar_images_sub_hash_alg_index(similar_images_sub_hash_alg_idx as i32);
|
||||
let similar_images_sub_resize_algorithm_idx = get_resize_algorithm_idx(&custom_settings.similar_images_sub_resize_algorithm).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of resize algorithm \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.similar_images_sub_resize_algorithm
|
||||
);
|
||||
0
|
||||
};
|
||||
});
|
||||
settings.set_similar_images_sub_resize_algorithm_index(similar_images_sub_resize_algorithm_idx as i32);
|
||||
|
||||
settings.set_similar_images_sub_resize_algorithm_value(ALLOWED_RESIZE_ALGORITHM_VALUES[similar_images_sub_resize_algorithm_idx].1.to_string().into());
|
||||
settings.set_similar_images_sub_ignore_same_size(custom_settings.similar_images_sub_ignore_same_size);
|
||||
settings.set_similar_images_sub_max_similarity(40.0); // TODO this is now set to stable 40
|
||||
settings.set_similar_images_sub_max_similarity(40.0);
|
||||
settings.set_similar_images_sub_current_similarity(custom_settings.similar_images_sub_similarity as f32);
|
||||
|
||||
let duplicates_sub_check_method_idx = get_duplicates_check_method_idx(&custom_settings.duplicates_sub_check_method).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of duplicates check method \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.duplicates_sub_check_method
|
||||
);
|
||||
0
|
||||
});
|
||||
settings.set_duplicates_sub_check_method_index(duplicates_sub_check_method_idx as i32);
|
||||
settings.set_duplicates_sub_check_method_value(ALLOWED_DUPLICATES_CHECK_METHOD_VALUES[duplicates_sub_check_method_idx].1.to_string().into());
|
||||
let duplicates_sub_available_hash_type_idx = get_duplicates_hash_type_idx(&custom_settings.duplicates_sub_available_hash_type).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of duplicates hash type \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.duplicates_sub_available_hash_type
|
||||
);
|
||||
0
|
||||
});
|
||||
settings.set_duplicates_sub_available_hash_type_index(duplicates_sub_available_hash_type_idx as i32);
|
||||
settings.set_duplicates_sub_available_hash_type_value(ALLOWED_DUPLICATES_HASH_TYPE_VALUES[duplicates_sub_available_hash_type_idx].1.to_string().into());
|
||||
|
||||
let biggest_files_sub_method_idx = get_biggest_item_idx(&custom_settings.biggest_files_sub_method).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of biggest files method \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.biggest_files_sub_method
|
||||
);
|
||||
0
|
||||
});
|
||||
settings.set_biggest_files_sub_method_index(biggest_files_sub_method_idx as i32);
|
||||
settings.set_biggest_files_sub_method_value(ALLOWED_BIG_FILE_SIZE_VALUES[biggest_files_sub_method_idx].1.to_string().into());
|
||||
settings.set_biggest_files_sub_number_of_files(custom_settings.biggest_files_sub_number_of_files.to_string().into());
|
||||
let all_gui_items: Vec<SharedString> = ALLOWED_BIG_FILE_SIZE_VALUES.iter().map(|(_, gui_name, _)| (*gui_name).into()).collect::<Vec<_>>();
|
||||
settings.set_biggest_files_sub_method(ModelRc::new(VecModel::from(all_gui_items)));
|
||||
|
||||
settings.set_similar_videos_sub_ignore_same_size(custom_settings.similar_videos_sub_ignore_same_size);
|
||||
settings.set_similar_videos_sub_current_similarity(custom_settings.similar_videos_sub_similarity as f32);
|
||||
settings.set_similar_videos_sub_max_similarity(20.0);
|
||||
|
||||
let similar_music_sub_audio_check_type_idx = get_audio_check_type_idx(&custom_settings.similar_music_sub_audio_check_type).unwrap_or_else(|| {
|
||||
warn!(
|
||||
"Value of audio check type \"{}\" is invalid, setting it to default value",
|
||||
custom_settings.similar_music_sub_audio_check_type
|
||||
);
|
||||
0
|
||||
});
|
||||
settings.set_similar_music_sub_audio_check_type_index(similar_music_sub_audio_check_type_idx as i32);
|
||||
settings.set_similar_music_sub_audio_check_type_value(ALLOWED_AUDIO_CHECK_TYPE_VALUES[similar_music_sub_audio_check_type_idx].1.to_string().into());
|
||||
settings.set_similar_music_sub_approximate_comparison(custom_settings.similar_music_sub_approximate_comparison);
|
||||
settings.set_similar_music_sub_title(custom_settings.similar_music_sub_title);
|
||||
settings.set_similar_music_sub_artist(custom_settings.similar_music_sub_artist);
|
||||
settings.set_similar_music_sub_year(custom_settings.similar_music_sub_year);
|
||||
settings.set_similar_music_sub_bitrate(custom_settings.similar_music_sub_bitrate);
|
||||
settings.set_similar_music_sub_genre(custom_settings.similar_music_sub_genre);
|
||||
settings.set_similar_music_sub_length(custom_settings.similar_music_sub_length);
|
||||
settings.set_similar_music_sub_maximum_difference_value(custom_settings.similar_music_sub_maximum_difference_value);
|
||||
settings.set_similar_music_sub_minimal_fragment_duration_value(custom_settings.similar_music_sub_minimal_fragment_duration_value);
|
||||
|
||||
settings.set_broken_files_sub_audio(custom_settings.broken_files_sub_audio);
|
||||
settings.set_broken_files_sub_pdf(custom_settings.broken_files_sub_pdf);
|
||||
settings.set_broken_files_sub_archive(custom_settings.broken_files_sub_archive);
|
||||
settings.set_broken_files_sub_image(custom_settings.broken_files_sub_image);
|
||||
|
||||
// Clear text
|
||||
app.global::<GuiState>().set_info_text("".into());
|
||||
}
|
||||
|
@ -468,6 +579,7 @@ pub fn collect_settings(app: &MainWindow) -> SettingsCustom {
|
|||
.parse::<i32>()
|
||||
.unwrap_or(DEFAULT_MINIMUM_PREHASH_CACHE_SIZE);
|
||||
let duplicate_delete_outdated_entries = settings.get_duplicate_delete_outdated_entries();
|
||||
let duplicates_sub_name_case_sensitive = settings.get_duplicates_sub_name_case_sensitive();
|
||||
|
||||
let similar_images_show_image_preview = settings.get_similar_images_show_image_preview();
|
||||
let similar_images_delete_outdated_entries = settings.get_similar_images_delete_outdated_entries();
|
||||
|
@ -478,15 +590,42 @@ pub fn collect_settings(app: &MainWindow) -> SettingsCustom {
|
|||
|
||||
let similar_images_sub_hash_size_idx = settings.get_similar_images_sub_hash_size_index();
|
||||
let similar_images_sub_hash_size = ALLOWED_HASH_SIZE_VALUES[similar_images_sub_hash_size_idx as usize].0;
|
||||
|
||||
let similar_images_sub_hash_type_idx = settings.get_similar_images_sub_hash_type_index();
|
||||
let similar_images_sub_hash_type = ALLOWED_HASH_TYPE_VALUES[similar_images_sub_hash_type_idx as usize].0.to_string();
|
||||
|
||||
let similar_images_sub_hash_alg_idx = settings.get_similar_images_sub_hash_alg_index();
|
||||
let similar_images_sub_hash_alg = ALLOWED_IMAGE_HASH_ALG_VALUES[similar_images_sub_hash_alg_idx as usize].0.to_string();
|
||||
let similar_images_sub_resize_algorithm_idx = settings.get_similar_images_sub_resize_algorithm_index();
|
||||
let similar_images_sub_resize_algorithm = ALLOWED_RESIZE_ALGORITHM_VALUES[similar_images_sub_resize_algorithm_idx as usize].0.to_string();
|
||||
|
||||
let similar_images_sub_ignore_same_size = settings.get_similar_images_sub_ignore_same_size();
|
||||
let similar_images_sub_similarity = settings.get_similar_images_sub_current_similarity().round() as i32;
|
||||
|
||||
let duplicates_sub_check_method_idx = settings.get_duplicates_sub_check_method_index();
|
||||
let duplicates_sub_check_method = ALLOWED_DUPLICATES_CHECK_METHOD_VALUES[duplicates_sub_check_method_idx as usize].0.to_string();
|
||||
let duplicates_sub_available_hash_type_idx = settings.get_duplicates_sub_available_hash_type_index();
|
||||
let duplicates_sub_available_hash_type = ALLOWED_DUPLICATES_HASH_TYPE_VALUES[duplicates_sub_available_hash_type_idx as usize].0.to_string();
|
||||
|
||||
let biggest_files_sub_method_idx = settings.get_biggest_files_sub_method_index();
|
||||
let biggest_files_sub_method = ALLOWED_BIG_FILE_SIZE_VALUES[biggest_files_sub_method_idx as usize].0.to_string();
|
||||
let biggest_files_sub_number_of_files = settings.get_biggest_files_sub_number_of_files().parse().unwrap_or(DEFAULT_BIGGEST_FILES);
|
||||
|
||||
let similar_videos_sub_ignore_same_size = settings.get_similar_videos_sub_ignore_same_size();
|
||||
let similar_videos_sub_similarity = settings.get_similar_videos_sub_current_similarity().round() as i32;
|
||||
|
||||
let similar_music_sub_audio_check_type_idx = settings.get_similar_music_sub_audio_check_type_index();
|
||||
let similar_music_sub_audio_check_type = ALLOWED_AUDIO_CHECK_TYPE_VALUES[similar_music_sub_audio_check_type_idx as usize].0.to_string();
|
||||
let similar_music_sub_approximate_comparison = settings.get_similar_music_sub_approximate_comparison();
|
||||
let similar_music_sub_title = settings.get_similar_music_sub_title();
|
||||
let similar_music_sub_artist = settings.get_similar_music_sub_artist();
|
||||
let similar_music_sub_year = settings.get_similar_music_sub_year();
|
||||
let similar_music_sub_bitrate = settings.get_similar_music_sub_bitrate();
|
||||
let similar_music_sub_genre = settings.get_similar_music_sub_genre();
|
||||
let similar_music_sub_length = settings.get_similar_music_sub_length();
|
||||
let similar_music_sub_maximum_difference_value = settings.get_similar_music_sub_maximum_difference_value();
|
||||
let similar_music_sub_minimal_fragment_duration_value = settings.get_similar_music_sub_minimal_fragment_duration_value();
|
||||
|
||||
let broken_files_sub_audio = settings.get_broken_files_sub_audio();
|
||||
let broken_files_sub_pdf = settings.get_broken_files_sub_pdf();
|
||||
let broken_files_sub_archive = settings.get_broken_files_sub_archive();
|
||||
let broken_files_sub_image = settings.get_broken_files_sub_image();
|
||||
|
||||
SettingsCustom {
|
||||
included_directories,
|
||||
included_directories_referenced,
|
||||
|
@ -513,10 +652,31 @@ pub fn collect_settings(app: &MainWindow) -> SettingsCustom {
|
|||
similar_videos_delete_outdated_entries,
|
||||
similar_music_delete_outdated_entries,
|
||||
similar_images_sub_hash_size,
|
||||
similar_images_sub_hash_type,
|
||||
similar_images_sub_hash_alg,
|
||||
similar_images_sub_resize_algorithm,
|
||||
similar_images_sub_ignore_same_size,
|
||||
similar_images_sub_similarity,
|
||||
duplicates_sub_check_method,
|
||||
duplicates_sub_available_hash_type,
|
||||
duplicates_sub_name_case_sensitive,
|
||||
biggest_files_sub_method,
|
||||
biggest_files_sub_number_of_files,
|
||||
similar_videos_sub_ignore_same_size,
|
||||
similar_videos_sub_similarity,
|
||||
similar_music_sub_audio_check_type,
|
||||
similar_music_sub_approximate_comparison,
|
||||
similar_music_sub_title,
|
||||
similar_music_sub_artist,
|
||||
similar_music_sub_year,
|
||||
similar_music_sub_bitrate,
|
||||
similar_music_sub_genre,
|
||||
similar_music_sub_length,
|
||||
similar_music_sub_maximum_difference_value,
|
||||
similar_music_sub_minimal_fragment_duration_value,
|
||||
broken_files_sub_audio,
|
||||
broken_files_sub_pdf,
|
||||
broken_files_sub_archive,
|
||||
broken_files_sub_image,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -555,7 +715,34 @@ fn default_excluded_directories() -> Vec<PathBuf> {
|
|||
excluded_directories.sort();
|
||||
excluded_directories
|
||||
}
|
||||
fn default_duplicates_check_method() -> String {
|
||||
ALLOWED_DUPLICATES_CHECK_METHOD_VALUES[0].0.to_string()
|
||||
}
|
||||
fn default_maximum_difference_value() -> f32 {
|
||||
DEFAULT_MAXIMUM_DIFFERENCE_VALUE
|
||||
}
|
||||
fn default_minimal_fragment_duration_value() -> f32 {
|
||||
DEFAULT_MINIMAL_FRAGMENT_DURATION_VALUE
|
||||
}
|
||||
fn default_duplicates_hash_type() -> String {
|
||||
ALLOWED_DUPLICATES_HASH_TYPE_VALUES[0].0.to_string()
|
||||
}
|
||||
fn default_biggest_method() -> String {
|
||||
ALLOWED_BIG_FILE_SIZE_VALUES[0].0.to_string()
|
||||
}
|
||||
fn default_audio_check_type() -> String {
|
||||
ALLOWED_AUDIO_CHECK_TYPE_VALUES[0].0.to_string()
|
||||
}
|
||||
fn default_video_similarity() -> i32 {
|
||||
DEFAULT_VIDEO_SIMILARITY
|
||||
}
|
||||
fn default_biggest_files() -> i32 {
|
||||
DEFAULT_BIGGEST_FILES
|
||||
}
|
||||
|
||||
pub fn default_image_similarity() -> i32 {
|
||||
DEFAULT_IMAGE_SIMILARITY
|
||||
}
|
||||
fn default_excluded_items() -> String {
|
||||
DEFAULT_EXCLUDED_ITEMS.to_string()
|
||||
}
|
||||
|
@ -588,8 +775,44 @@ pub fn default_resize_algorithm() -> String {
|
|||
ALLOWED_RESIZE_ALGORITHM_VALUES[0].0.to_string()
|
||||
}
|
||||
pub fn default_hash_type() -> String {
|
||||
ALLOWED_HASH_TYPE_VALUES[0].0.to_string()
|
||||
ALLOWED_IMAGE_HASH_ALG_VALUES[0].0.to_string()
|
||||
}
|
||||
pub fn default_sub_hash_size() -> u8 {
|
||||
16
|
||||
DEFAULT_HASH_SIZE
|
||||
}
|
||||
|
||||
fn get_allowed_hash_size_idx(h_size: u8) -> Option<usize> {
|
||||
ALLOWED_HASH_SIZE_VALUES.iter().position(|(hash_size, _max_similarity)| *hash_size == h_size)
|
||||
}
|
||||
|
||||
pub fn get_image_hash_alg_idx(string_hash_type: &str) -> Option<usize> {
|
||||
ALLOWED_IMAGE_HASH_ALG_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _hash_type)| *settings_key == string_hash_type || *gui_name == string_hash_type)
|
||||
}
|
||||
pub fn get_resize_algorithm_idx(string_resize_algorithm: &str) -> Option<usize> {
|
||||
ALLOWED_RESIZE_ALGORITHM_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _resize_alg)| *settings_key == string_resize_algorithm || *gui_name == string_resize_algorithm)
|
||||
}
|
||||
pub fn get_biggest_item_idx(string_biggest_item: &str) -> Option<usize> {
|
||||
ALLOWED_BIG_FILE_SIZE_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _search_mode)| *settings_key == string_biggest_item || *gui_name == string_biggest_item)
|
||||
}
|
||||
|
||||
pub fn get_duplicates_check_method_idx(string_duplicates_check_method: &str) -> Option<usize> {
|
||||
ALLOWED_DUPLICATES_CHECK_METHOD_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _check_method)| *settings_key == string_duplicates_check_method || *gui_name == string_duplicates_check_method)
|
||||
}
|
||||
pub fn get_duplicates_hash_type_idx(string_duplicates_hash_type: &str) -> Option<usize> {
|
||||
ALLOWED_DUPLICATES_HASH_TYPE_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _hash_type)| *settings_key == string_duplicates_hash_type || *gui_name == string_duplicates_hash_type)
|
||||
}
|
||||
pub fn get_audio_check_type_idx(string_audio_check_type: &str) -> Option<usize> {
|
||||
ALLOWED_AUDIO_CHECK_TYPE_VALUES
|
||||
.iter()
|
||||
.position(|(settings_key, gui_name, _audio_check_type)| *settings_key == string_audio_check_type || *gui_name == string_audio_check_type)
|
||||
}
|
||||
|
|
67
krokiet/ui/about.slint
Normal file
67
krokiet/ui/about.slint
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, ScrollView, LineEdit, SpinBox, ComboBox, TextEdit, Slider} from "std-widgets.slint";
|
||||
import { Settings } from "settings.slint";
|
||||
import { Callabler } from "callabler.slint";
|
||||
import { GuiState } from "gui_state.slint";
|
||||
|
||||
export component About inherits VerticalLayout {
|
||||
preferred-height: 300px;
|
||||
preferred-width: 400px;
|
||||
|
||||
img := Image {
|
||||
source: @image-url("../icons/logo.png");
|
||||
image-fit: ImageFit.contain;
|
||||
}
|
||||
Text {
|
||||
text: "7.0.0";
|
||||
horizontal-alignment: center;
|
||||
font-size: max(min(img.width / 20, 17px), 10px);
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 10px;
|
||||
padding-bottom: 10px;
|
||||
Text {
|
||||
text: "2020 - 2024 Rafał Mikrut(qarmin)";
|
||||
horizontal-alignment: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
Text {
|
||||
text: "This program is free to use and will always be.\nSee the The MIT/GPL License for details.";
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
Text {
|
||||
text: "You may not look at unicorn, but unicorn always looks at you.";
|
||||
horizontal-alignment: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 5px;
|
||||
Button {
|
||||
text: "Repository";
|
||||
clicked => {
|
||||
Callabler.open_link("https://github.com/qarmin/czkawka");
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Instruction";
|
||||
clicked => {
|
||||
Callabler.open_link("https://github.com/qarmin/czkawka/blob/master/instructions/Instruction.md");
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Donation";
|
||||
clicked => {
|
||||
Callabler.open_link("https://github.com/sponsors/qarmin");
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Translation";
|
||||
clicked => {
|
||||
Callabler.open_link("https://crwd.in/czkawka");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,10 +23,10 @@ export component ActionButtons inherits HorizontalLayout {
|
|||
callback show_select_popup(length, length);
|
||||
callback show_remove_popup();
|
||||
callback request_folder_to_move();
|
||||
in-out property <BottomPanelVisibility> bottom_panel_visibility: BottomPanelVisibility.Directories;
|
||||
in-out property <BottomPanelVisibility> bottom_panel_visibility <=> GuiState.bottom_panel_visibility;
|
||||
in-out property <bool> stop_requested: false;
|
||||
in-out property <bool> scanning;
|
||||
in-out property <bool> lists_enabled: GuiState.active_tab != CurrentTab.Settings;
|
||||
in-out property <bool> lists_enabled: GuiState.is_tool_tab_active;
|
||||
out property <int> name;
|
||||
height: 30px;
|
||||
spacing: 4px;
|
||||
|
@ -35,7 +35,7 @@ export component ActionButtons inherits HorizontalLayout {
|
|||
scan_button := Button {
|
||||
height: parent.height;
|
||||
enabled: !scanning && lists_enabled;
|
||||
visible: !scanning;
|
||||
visible: !scanning && lists_enabled;
|
||||
text: "Scan";
|
||||
clicked => {
|
||||
root.scanning = true;
|
||||
|
@ -60,6 +60,7 @@ export component ActionButtons inherits HorizontalLayout {
|
|||
}
|
||||
|
||||
move_button := Button {
|
||||
visible: lists_enabled;
|
||||
height: parent.height;
|
||||
enabled: !scanning && lists_enabled;
|
||||
text: "Move";
|
||||
|
@ -69,6 +70,7 @@ export component ActionButtons inherits HorizontalLayout {
|
|||
}
|
||||
|
||||
select_button := Button {
|
||||
visible: lists_enabled;
|
||||
height: parent.height;
|
||||
enabled: !scanning && lists_enabled;
|
||||
text: "Select";
|
||||
|
@ -78,6 +80,7 @@ export component ActionButtons inherits HorizontalLayout {
|
|||
}
|
||||
|
||||
delete_button := Button {
|
||||
visible: lists_enabled;
|
||||
height: parent.height;
|
||||
enabled: !scanning && lists_enabled;
|
||||
text: "Delete";
|
||||
|
|
|
@ -32,4 +32,6 @@ export global Callabler {
|
|||
|
||||
callback open_config_folder();
|
||||
callback open_cache_folder();
|
||||
|
||||
callback open_link(string);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
export enum CurrentTab {
|
||||
DuplicateFiles,
|
||||
EmptyFolders,
|
||||
BigFiles,
|
||||
EmptyFiles,
|
||||
TemporaryFiles,
|
||||
SimilarImages,
|
||||
Settings
|
||||
SimilarVideos,
|
||||
SimilarMusic,
|
||||
InvalidSymlinks,
|
||||
BrokenFiles,
|
||||
BadExtensions,
|
||||
Settings,
|
||||
About
|
||||
}
|
||||
|
||||
export enum TypeOfOpenedItem {
|
||||
|
@ -19,6 +28,7 @@ export struct ProgressToSend {
|
|||
export struct MainListModel {
|
||||
checked: bool,
|
||||
header_row: bool,
|
||||
full_header_row: bool,
|
||||
selected_row: bool,
|
||||
val_str: [string],
|
||||
val_int: [int]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {CurrentTab} from "common.slint";
|
||||
import {SelectModel, SelectMode} from "common.slint";
|
||||
import {SelectModel, SelectMode, BottomPanelVisibility} from "common.slint";
|
||||
|
||||
// State Gui state that shows the current state of the GUI
|
||||
// It extends Settings global state with settings that are not saved to the settings file
|
||||
|
@ -17,7 +17,10 @@ export global GuiState {
|
|||
in-out property <bool> choosing_include_directories;
|
||||
in-out property <bool> visible_tool_settings;
|
||||
|
||||
in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages;
|
||||
in-out property <CurrentTab> active_tab: CurrentTab.EmptyFiles;
|
||||
in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages || active_tab == CurrentTab.DuplicateFiles || active_tab == CurrentTab.SimilarVideos || active_tab == CurrentTab.SimilarMusic || active_tab == CurrentTab.BigFiles || active_tab == CurrentTab.BrokenFiles;
|
||||
in-out property <CurrentTab> active_tab: CurrentTab.DuplicateFiles;
|
||||
in-out property <bool> is_tool_tab_active: active_tab != CurrentTab.Settings && active_tab != CurrentTab.About;
|
||||
in-out property <[SelectModel]> select_results_list: [{data: SelectMode.SelectAll, name: "Select All"}, {data: SelectMode.UnselectAll, name: "Deselect All"}, {data: SelectMode.SelectTheSmallestResolution, name: "Select the smallest resolution"}];
|
||||
|
||||
in-out property <BottomPanelVisibility> bottom_panel_visibility: BottomPanelVisibility.Directories;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox} from "std-widgets.slint";
|
||||
import {CurrentTab} from "common.slint";
|
||||
import {CurrentTab, BottomPanelVisibility} from "common.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
|
@ -68,40 +68,46 @@ export component LeftSidePanel {
|
|||
callback changed_current_tab();
|
||||
width: 120px;
|
||||
VerticalLayout {
|
||||
spacing: 20px;
|
||||
spacing: 2px;
|
||||
Rectangle {
|
||||
height: 100px;
|
||||
visible: GuiState.active_tab != CurrentTab.About;
|
||||
height: 80px;
|
||||
Image {
|
||||
width: root.width;
|
||||
width: parent.height;
|
||||
source: @image-url("../icons/logo.png");
|
||||
image-fit: ImageFit.contain;
|
||||
}
|
||||
touch_area := TouchArea {
|
||||
clicked => {
|
||||
GuiState.active_tab = CurrentTab.About;
|
||||
Callabler.tab_changed();
|
||||
root.changed_current_tab();
|
||||
GuiState.bottom_panel_visibility = BottomPanelVisibility.NotVisible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
// spacing: 3px;
|
||||
alignment: center;
|
||||
ListView {
|
||||
out property <length> element-size: 25px;
|
||||
TabItem {
|
||||
height: parent.element-size;
|
||||
scanning: scanning;
|
||||
text: "Empty Folders";
|
||||
curr_tab: CurrentTab.EmptyFolders;
|
||||
changed_current_tab() => {root.changed_current_tab();}
|
||||
}
|
||||
out property <[{name: string, tab: CurrentTab}]> speed_model: [
|
||||
{name: "Duplicate Files", tab: CurrentTab.DuplicateFiles},
|
||||
{name: "Empty Folders", tab: CurrentTab.EmptyFolders},
|
||||
{name: "Big Files", tab: CurrentTab.BigFiles},
|
||||
{name: "Empty Files", tab: CurrentTab.EmptyFiles},
|
||||
{name: "Temporary Files", tab: CurrentTab.TemporaryFiles},
|
||||
{name: "Similar Images", tab: CurrentTab.SimilarImages},
|
||||
{name: "Similar Videos", tab: CurrentTab.SimilarVideos},
|
||||
{name: "Music Duplicates", tab: CurrentTab.SimilarMusic},
|
||||
{name: "Invalid Symlinks", tab: CurrentTab.InvalidSymlinks},
|
||||
{name: "Broken Files", tab: CurrentTab.BrokenFiles},
|
||||
{name: "Bad Extensions", tab: CurrentTab.BadExtensions}
|
||||
];
|
||||
|
||||
TabItem {
|
||||
for r[idx] in speed_model: TabItem {
|
||||
height: parent.element-size;
|
||||
scanning: scanning;
|
||||
text: "Empty Files";
|
||||
curr_tab: CurrentTab.EmptyFiles;
|
||||
changed_current_tab() => {root.changed_current_tab();}
|
||||
}
|
||||
|
||||
TabItem {
|
||||
height: parent.element-size;
|
||||
scanning: scanning;
|
||||
text: "Similar Images";
|
||||
curr_tab: CurrentTab.SimilarImages;
|
||||
text: r.name;
|
||||
curr_tab: r.tab;
|
||||
changed_current_tab() => {root.changed_current_tab();}
|
||||
}
|
||||
}
|
||||
|
@ -109,20 +115,6 @@ export component LeftSidePanel {
|
|||
Rectangle {
|
||||
HorizontalLayout {
|
||||
alignment: start;
|
||||
Button {
|
||||
visible: GuiState.active_tab != CurrentTab.Settings && GuiState.available_subsettings;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: self.width;
|
||||
preferred-height: self.width;
|
||||
icon: @image-url("../icons/settings.svg");
|
||||
clicked => {
|
||||
GuiState.visible_tool_settings = !GuiState.visible-tool-settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalLayout {
|
||||
alignment: end;
|
||||
Button {
|
||||
visible: GuiState.active_tab != CurrentTab.Settings;
|
||||
min-width: 20px;
|
||||
|
@ -137,6 +129,20 @@ export component LeftSidePanel {
|
|||
}
|
||||
}
|
||||
}
|
||||
HorizontalLayout {
|
||||
alignment: end;
|
||||
Button {
|
||||
visible: GuiState.available_subsettings;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
max-height: self.width;
|
||||
preferred-height: self.width;
|
||||
icon: @image-url("../icons/subsettings.svg");
|
||||
clicked => {
|
||||
GuiState.visible_tool_settings = !GuiState.visible-tool-settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,56 +5,170 @@ import {CurrentTab, TypeOfOpenedItem} from "common.slint";
|
|||
import {MainListModel} from "common.slint";
|
||||
import {SettingsList} from "settings_list.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import {About} from "about.slint";
|
||||
|
||||
export component MainList {
|
||||
in-out property <[MainListModel]> duplicate_files_model: [];
|
||||
in-out property <[MainListModel]> empty_folder_model: [
|
||||
{checked: false, selected_row: false, header_row: true, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: true, selected_row: false, header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []},
|
||||
{checked: true, selected_row: false, header_row: false, full_header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[MainListModel]> empty_files_model;
|
||||
in-out property <[MainListModel]> similar_images_model;
|
||||
in-out property <[MainListModel]> big_files_model: [];
|
||||
in-out property <[MainListModel]> empty_files_model: [
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []},
|
||||
{checked: true, selected_row: false, header_row: false, full_header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[MainListModel]> temporary_files_model: [];
|
||||
in-out property <[MainListModel]> similar_images_model: [
|
||||
{checked: false, selected_row: false, header_row: true, full_header_row: false, val_str: ["Original", "500KB", "100x100", "kropkarz", "/Xd1", "24.10.2023"], val_int: []},
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["Similar", "500KB", "100x100", "witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []},
|
||||
{checked: true, selected_row: false, header_row: false, full_header_row: false, val_str: ["Similar", "500KB", "100x100", "lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[MainListModel]> similar_videos_model: [];
|
||||
in-out property <[MainListModel]> similar_music_model: [];
|
||||
in-out property <[MainListModel]> invalid_symlinks_model: [];
|
||||
in-out property <[MainListModel]> broken_files_model: [];
|
||||
in-out property <[MainListModel]> bad_extensions_model: [];
|
||||
|
||||
callback changed_current_tab();
|
||||
callback released_key(string);
|
||||
|
||||
out property <length> path_px: 350px;
|
||||
out property <length> name_px: 100px;
|
||||
out property <length> mod_px: 150px;
|
||||
out property <length> size_px: 75px;
|
||||
|
||||
duplicates := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.DuplicateFiles;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Size", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, size_px, name_px, path_px, mod_px];
|
||||
values <=> duplicate_files_model;
|
||||
parentPathIdx: 3;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
empty_folders := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.EmptyFolders;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Folder Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, 100px, 350px, 150px];
|
||||
column-sizes: [35px, name_px, path_px, mod_px];
|
||||
values <=> empty-folder-model;
|
||||
parentPathIdx: 2;
|
||||
fileNameIdx: 1;
|
||||
}
|
||||
|
||||
big_files := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.BigFiles;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Size", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, size_px, name_px, path_px, mod_px];
|
||||
values <=> big_files_model;
|
||||
parentPathIdx: 3;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
empty_files := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.EmptyFiles;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, 100px, 350px, 150px];
|
||||
column-sizes: [35px, name_px, path_px, mod_px];
|
||||
values <=> empty-files-model;
|
||||
parentPathIdx: 2;
|
||||
fileNameIdx: 1;
|
||||
}
|
||||
|
||||
temporary_files := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.TemporaryFiles;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, name_px, path_px, mod_px];
|
||||
values <=> temporary_files_model;
|
||||
parentPathIdx: 3;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
similar_images := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarImages;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, 80px, 80px, 80px, 100px, 350px, 150px];
|
||||
column-sizes: [35px, 80px, 80px, 80px, name_px, path_px, mod_px];
|
||||
values <=> similar-images-model;
|
||||
parentPathIdx: 5;
|
||||
fileNameIdx: 4;
|
||||
}
|
||||
|
||||
similar_videos := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarVideos;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Size", "File Name", "Path", "Modification Date"];
|
||||
column-sizes: [35px, size_px, name_px, path_px, mod_px];
|
||||
values <=> similar_videos_model;
|
||||
parentPathIdx: 3;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
similar_music := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarMusic;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Size", "File Name", "Title","Artist", "Year", "Bitrate", "Length", "Genre", "Path", "Modification Date"];
|
||||
column-sizes: [35px, size_px, name_px, 80px, 80px, 80px, 80px, 80px, 80px, path_px, mod_px];
|
||||
values <=> similar_music_model;
|
||||
parentPathIdx: 9;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
invalid_symlink := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.InvalidSymlinks;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "Symlink Name", "Symlink Folder", "Destination Path", "Modification Date"];
|
||||
column-sizes: [35px, name_px, path_px, path_px, mod_px];
|
||||
values <=> invalid_symlinks_model;
|
||||
parentPathIdx: 2;
|
||||
fileNameIdx: 1;
|
||||
}
|
||||
|
||||
broken_files := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.BrokenFiles;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "File Name", "Path", "Type of Error", "Size", "Modification Date"];
|
||||
column-sizes: [35px, name_px, path_px, 200px, size_px, mod_px];
|
||||
values <=> broken_files_model;
|
||||
parentPathIdx: 4;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
bad_extensions := SelectableTableView {
|
||||
visible: GuiState.active_tab == CurrentTab.BadExtensions;
|
||||
min-width: 200px;
|
||||
height: parent.height;
|
||||
columns: ["Selection", "File Name", "Path", "Current Extension", "Proper Extension"];
|
||||
column-sizes: [35px, name_px, path_px, 40px, 200px];
|
||||
values <=> bad_extensions_model;
|
||||
parentPathIdx: 4;
|
||||
fileNameIdx: 2;
|
||||
}
|
||||
|
||||
settings_list := SettingsList {
|
||||
visible: GuiState.active_tab == CurrentTab.Settings;
|
||||
}
|
||||
|
||||
about_app := About {
|
||||
visible: GuiState.active_tab == CurrentTab.About;
|
||||
}
|
||||
|
||||
focus_item := FocusScope {
|
||||
width: 0px; // Hack to not steal first click from other components - https://github.com/slint-ui/slint/issues/3503
|
||||
// Hack not works https://github.com/slint-ui/slint/issues/3503#issuecomment-1817809834 because disables key-released event
|
||||
|
|
|
@ -28,6 +28,8 @@ export component MainWindow inherits Window {
|
|||
callback show_move_folders_dialog(string);
|
||||
callback folders_move_choose_requested();
|
||||
|
||||
title: "Krokiet - Data Cleaner";
|
||||
|
||||
min-width: 300px;
|
||||
preferred-width: 800px;
|
||||
min-height: 300px;
|
||||
|
@ -41,19 +43,17 @@ export component MainWindow inherits Window {
|
|||
all_progress: 20,
|
||||
step_name: "Cache",
|
||||
};
|
||||
in-out property <[MainListModel]> empty_folder_model: [
|
||||
{checked: false, selected_row: false, header_row: true, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: true, selected_row: false, header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[MainListModel]> empty_files_model: [
|
||||
{checked: false, selected_row: false, header_row: true, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: true, selected_row: false, header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[MainListModel]> duplicate_files_model: [];
|
||||
in-out property <[MainListModel]> empty_folder_model: [];
|
||||
in-out property <[MainListModel]> big_files_model: [];
|
||||
in-out property <[MainListModel]> empty_files_model: [];
|
||||
in-out property <[MainListModel]> temporary-files_model: [];
|
||||
in-out property <[MainListModel]> similar_images_model: [];
|
||||
in-out property <[MainListModel]> similar_videos_model: [];
|
||||
in-out property <[MainListModel]> similar_music_model: [];
|
||||
in-out property <[MainListModel]> invalid_symlinks_model: [];
|
||||
in-out property <[MainListModel]> broken_files_model: [];
|
||||
in-out property <[MainListModel]> bad_extensions_model: [];
|
||||
|
||||
VerticalBox {
|
||||
HorizontalBox {
|
||||
|
@ -78,12 +78,20 @@ export component MainWindow inherits Window {
|
|||
width: preview_or_tool_settings.visible ? parent.width / 2 : parent.width;
|
||||
height: parent.height;
|
||||
horizontal-stretch: 0.5;
|
||||
duplicate_files_model <=> root.duplicate_files_model;
|
||||
empty_folder_model <=> root.empty_folder_model;
|
||||
big_files_model <=> root.big_files_model;
|
||||
empty_files_model <=> root.empty_files_model;
|
||||
temporary-files_model <=> root.temporary-files_model;
|
||||
similar_images_model <=> root.similar_images_model;
|
||||
similar_videos_model <=> root.similar_videos_model;
|
||||
similar_music_model <=> root.similar_music_model;
|
||||
invalid_symlinks_model <=> root.invalid_symlinks_model;
|
||||
broken_files_model <=> root.broken_files_model;
|
||||
bad_extensions_model <=> root.bad_extensions_model;
|
||||
}
|
||||
preview_or_tool_settings := Rectangle {
|
||||
visible: (GuiState.preview_visible || tool_settings.visible) && GuiState.active_tab != CurrentTab.Settings;
|
||||
visible: (GuiState.preview_visible || tool_settings.visible) && GuiState.is_tool_tab_active;
|
||||
height: parent.height;
|
||||
x: parent.width / 2;
|
||||
width: self.visible ? parent.width / 2 : 0;
|
||||
|
@ -134,10 +142,18 @@ export component MainWindow inherits Window {
|
|||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 5px;
|
||||
text_summary := LineEdit {
|
||||
text: text_summary_text;
|
||||
read-only: true;
|
||||
}
|
||||
Text {
|
||||
text: "Krokiet\n7.0.0";
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
bottom_panel := BottomPanel {
|
||||
bottom-panel-visibility <=> action_buttons.bottom_panel_visibility;
|
||||
|
|
|
@ -84,10 +84,6 @@ export component PopupMoveFolders inherits Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
init => {
|
||||
show_popup();
|
||||
}
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
|
|
|
@ -73,13 +73,6 @@ export component PopupNewDirectories inherits Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
// Button {
|
||||
// text:"KKK";
|
||||
// clicked => {
|
||||
// show-popup();
|
||||
// }
|
||||
// }
|
||||
|
||||
show_popup() => {
|
||||
popup_window.show();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export component Preview inherits Image {
|
||||
|
||||
image-rendering: ImageRendering.smooth;
|
||||
}
|
|
@ -9,10 +9,10 @@ export component SelectableTableView inherits Rectangle {
|
|||
callback item_opened(string);
|
||||
in property <[string]> columns;
|
||||
in-out property <[MainListModel]> values: [
|
||||
{checked: false, selected_row: false, header_row: true, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: true, selected_row: false, header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
{checked: false, selected_row: false, header_row: true, full_header_row: false, val_str: ["kropkarz", "/Xd1", "24.10.2023"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: false, selected_row: false, header_row: false, full_header_row: false, val_str: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"], val_int: []} ,
|
||||
{checked: true, selected_row: false, header_row: false, full_header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
|
||||
];
|
||||
in-out property <[length]> column_sizes: [30px, 80px, 150px, 160px];
|
||||
private property <int> column_number: column-sizes.length + 1;
|
||||
|
@ -96,11 +96,17 @@ export component SelectableTableView inherits Rectangle {
|
|||
}
|
||||
}
|
||||
double-clicked => {
|
||||
if (r.header_row && !r.full_header_row) {
|
||||
return;
|
||||
}
|
||||
Callabler.item_opened(r.val_str[root.parentPathIdx - 1] + "/" + r.val_str[root.fileNameIdx - 1])
|
||||
}
|
||||
pointer-event(event) => {
|
||||
// TODO this should be clicked by double-click - https://github.com/slint-ui/slint/issues/4235
|
||||
if (event.button == PointerEventButton.right && event.kind == PointerEventKind.up) {
|
||||
if (r.header_row && !r.full_header_row) {
|
||||
return;
|
||||
}
|
||||
Callabler.item_opened(r.val_str[root.parentPathIdx - 1])
|
||||
} else if (event.button == PointerEventButton.left && event.kind == PointerEventKind.up) {
|
||||
clicked_manual();
|
||||
|
|
|
@ -40,14 +40,60 @@ export global Settings {
|
|||
|
||||
|
||||
// Allowed subsettings
|
||||
// Duplicate
|
||||
// Similar Images
|
||||
in-out property <[string]> similar_images_sub_available_hash_size: ["8", "16", "32", "64"];
|
||||
in-out property <int> similar_images_sub_hash_size_index: 0;
|
||||
in-out property <string> similar_images_sub_hash_size_value: "8";
|
||||
in-out property <[string]> similar_images_sub_available_resize_algorithm: ["Lanczos3", "Nearest", "Triangle", "Gaussian", "CatmullRom"];
|
||||
in-out property <int> similar_images_sub_resize_algorithm_index: 0;
|
||||
in-out property <string> similar_images_sub_resize_algorithm_value: "Lanczos3";
|
||||
in-out property <[string]> similar_images_sub_available_hash_type: ["Gradient", "Mean", "VertGradient", "BlockHash", "DoubleGradient"];
|
||||
in-out property <int> similar_images_sub_hash_type_index: 0;
|
||||
in-out property <int> similar_images_sub_hash_alg_index: 0;
|
||||
in-out property <string> similar_images_sub_hash_alg_value: "Gradient";
|
||||
in-out property <float> similar_images_sub_max_similarity: 40;
|
||||
in-out property <float> similar_images_sub_current_similarity: 20;
|
||||
in-out property <bool> similar_images_sub_ignore_same_size;
|
||||
|
||||
// Duplicates
|
||||
in-out property <[string]> duplicates_sub_check_method: ["Hash", "Size", "Name", "Size and Name"];
|
||||
in-out property <int> duplicates_sub_check_method_index: 0;
|
||||
in-out property <string> duplicates_sub_check_method_value: "Hash";
|
||||
in-out property <[string]> duplicates_sub_available_hash_type: ["Blake3", "CRC32", "XXH3"];
|
||||
in-out property <int> duplicates_sub_available_hash_type_index: 0;
|
||||
in-out property <string> duplicates_sub_available_hash_type_value: "Blake3";
|
||||
in-out property <bool> duplicates_sub_name_case_sensitive: false;
|
||||
|
||||
|
||||
// Big files
|
||||
in-out property <[string]> biggest_files_sub_method: ["The Biggest", "The Smallest"];
|
||||
in-out property <int> biggest_files_sub_method_index: 0;
|
||||
in-out property <string> biggest_files_sub_method_value: "The Biggest";
|
||||
in-out property <string> biggest_files_sub_number_of_files: 50;
|
||||
|
||||
// Similar Videos
|
||||
in-out property <bool> similar_videos_sub_ignore_same_size;
|
||||
in-out property <float> similar_videos_sub_max_similarity: 20;
|
||||
in-out property <float> similar_videos_sub_current_similarity: 15;
|
||||
|
||||
// Same Music
|
||||
in-out property <[string]> similar_music_sub_audio_check_type: ["Tags", "Fingerprint"];
|
||||
in-out property <int> similar_music_sub_audio_check_type_index: 0;
|
||||
in-out property <string> similar_music_sub_audio_check_type_value: "Tags";
|
||||
in-out property <bool> similar_music_sub_approximate_comparison;
|
||||
in-out property <bool> similar_music_sub_title: true;
|
||||
in-out property <bool> similar_music_sub_artist: true;
|
||||
in-out property <bool> similar_music_sub_year: false;
|
||||
in-out property <bool> similar_music_sub_bitrate: false;
|
||||
in-out property <bool> similar_music_sub_genre: false;
|
||||
in-out property <bool> similar_music_sub_length: false;
|
||||
in-out property <float> similar_music_sub_minimal_fragment_duration_value: 5.0;
|
||||
in-out property <float> similar_music_sub_minimal_fragment_duration_max: 180.0;
|
||||
in-out property <float> similar_music_sub_maximum_difference_value: 3.0;
|
||||
in-out property <float> similar_music_sub_maximum_difference_max: 10.0;
|
||||
|
||||
// Broken Files
|
||||
in-out property <bool> broken_files_sub_audio: true;
|
||||
in-out property <bool> broken_files_sub_pdf: false;
|
||||
in-out property <bool> broken_files_sub_archive: false;
|
||||
in-out property <bool> broken_files_sub_image: false;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ global SettingsSize {
|
|||
out property <length> item_height: 30px;
|
||||
}
|
||||
|
||||
component TextComponent inherits HorizontalLayout {
|
||||
export component TextComponent inherits HorizontalLayout {
|
||||
in-out property <string> model;
|
||||
in property <string> name;
|
||||
spacing: 5px;
|
||||
|
|
|
@ -13,11 +13,13 @@ import {ColorPalette} from "color_palette.slint";
|
|||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
import {PopupNewDirectories} from "popup_new_directories.slint";
|
||||
import {TextComponent} from "settings_list.slint";
|
||||
|
||||
component ComboBoxWrapper inherits HorizontalLayout {
|
||||
in-out property <string> text;
|
||||
in-out property <[string]> model;
|
||||
in-out property <int> current_index;
|
||||
in-out property <string> current_value;
|
||||
spacing: 5px;
|
||||
Text {
|
||||
text <=> root.text;
|
||||
|
@ -26,6 +28,7 @@ component ComboBoxWrapper inherits HorizontalLayout {
|
|||
ComboBox {
|
||||
model: root.model;
|
||||
current_index <=> root.current_index;
|
||||
current_value <=> root.current_value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +65,8 @@ component SliderWrapper inherits HorizontalLayout {
|
|||
|
||||
export component ToolSettings {
|
||||
ScrollView {
|
||||
if GuiState.active_tab == CurrentTab.SimilarImages: VerticalLayout {
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarImages;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
|
@ -70,16 +74,19 @@ export component ToolSettings {
|
|||
text: "Hash size";
|
||||
model: Settings.similar_images_sub_available_hash_size;
|
||||
current_index <=> Settings.similar_images_sub_hash_size_index;
|
||||
current_value <=> Settings.similar_images_sub_hash_size_value;
|
||||
}
|
||||
ComboBoxWrapper {
|
||||
text: "Resize Algorithm";
|
||||
model: Settings.similar_images_sub_available_resize_algorithm;
|
||||
current_index <=> Settings.similar_images_sub_resize_algorithm_index;
|
||||
current_value <=> Settings.similar_images_sub_resize_algorithm_value;
|
||||
}
|
||||
ComboBoxWrapper {
|
||||
text: "Hash type";
|
||||
model: Settings.similar_images_sub_available_hash_type;
|
||||
current_index <=> Settings.similar_images_sub_hash_type_index;
|
||||
current_index <=> Settings.similar_images_sub_hash_alg_index;
|
||||
current_value <=> Settings.similar_images_sub_hash_alg_value;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Ignore same size";
|
||||
|
@ -94,5 +101,159 @@ export component ToolSettings {
|
|||
}
|
||||
Rectangle {}
|
||||
}
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.DuplicateFiles;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
ComboBoxWrapper {
|
||||
text: "Check method";
|
||||
model: Settings.duplicates_sub_check_method;
|
||||
current_index <=> Settings.duplicates_sub_check_method_index;
|
||||
current_value <=> Settings.duplicates_sub_check_method_value;
|
||||
}
|
||||
ComboBoxWrapper {
|
||||
text: "Hash type";
|
||||
model: Settings.duplicates_sub_available_hash_type;
|
||||
current_index <=> Settings.duplicates_sub_available_hash_type_index;
|
||||
current_value <=> Settings.duplicates_sub_available_hash_type_value;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Case Sensitive(only name modes)";
|
||||
checked <=> Settings.duplicates_sub_name_case_sensitive;
|
||||
}
|
||||
Rectangle {}
|
||||
}
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.BigFiles;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
ComboBoxWrapper {
|
||||
text: "Checked files";
|
||||
model: Settings.biggest_files_sub_method;
|
||||
current_index <=> Settings.biggest_files_sub_method_index;
|
||||
current_value <=> Settings.biggest_files_sub_method_value;
|
||||
}
|
||||
TextComponent {
|
||||
name: "Number of files";
|
||||
model <=> Settings.biggest_files_sub_number_of_files;
|
||||
}
|
||||
Rectangle {}
|
||||
}
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarVideos;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
SliderWrapper {
|
||||
text: "Max difference";
|
||||
end_text: "(" + round(Settings.similar_videos_sub_current_similarity) + "/" + round(Settings.similar_videos_sub_max_similarity) + ")";
|
||||
end_text_size: 40px;
|
||||
maximum <=> Settings.similar_videos_sub_max_similarity;
|
||||
value <=> Settings.similar_videos_sub_current_similarity;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Ignore same size";
|
||||
checked <=> Settings.similar_images_sub_ignore_same_size;
|
||||
}
|
||||
Rectangle {}
|
||||
}
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.SimilarMusic;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
ComboBoxWrapper {
|
||||
text: "Audio check type";
|
||||
model: Settings.similar_music_sub_audio_check_type;
|
||||
current_index <=> Settings.similar_music_sub_audio_check_type_index;
|
||||
current_value <=> Settings.similar_music_sub_audio_check_type_value;
|
||||
}
|
||||
// A little bit of a hack
|
||||
// Mode should be set with a enum, not index, but it's not possible in slint(maybe yet)
|
||||
if Settings.similar_music_sub_audio_check_type_index == 0: VerticalLayout {
|
||||
spacing: 5px;
|
||||
CheckBoxWrapper {
|
||||
text: "Approximate Tag Comparison";
|
||||
checked <=> Settings.similar_music_sub_approximate_comparison;
|
||||
}
|
||||
Text {
|
||||
text: "Compared tags";
|
||||
font-size: 12px;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Title";
|
||||
checked <=> Settings.similar_music_sub_title;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Artist";
|
||||
checked <=> Settings.similar_music_sub_artist;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Bitrate";
|
||||
checked <=> Settings.similar_music_sub_bitrate;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Genre";
|
||||
checked <=> Settings.similar_music_sub_genre;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Year";
|
||||
checked <=> Settings.similar_music_sub_year;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Length";
|
||||
checked <=> Settings.similar_music_sub_length;
|
||||
}
|
||||
}
|
||||
if Settings.similar_music_sub_audio_check_type_index == 1: VerticalLayout {
|
||||
spacing: 5px;
|
||||
SliderWrapper {
|
||||
text: "Max difference";
|
||||
end_text: "(" + round(Settings.similar_music_sub_maximum_difference_value) + "/" + round(Settings.similar_music_sub_maximum_difference_max) + ")";
|
||||
end_text_size: 40px;
|
||||
maximum <=> Settings.similar_music_sub_maximum_difference_max;
|
||||
value <=> Settings.similar_music_sub_maximum_difference_value;
|
||||
}
|
||||
SliderWrapper {
|
||||
text: "Minimal fragment duration";
|
||||
end_text: round(Settings.similar_music_sub_minimal_fragment_duration_value);
|
||||
end_text_size: 40px;
|
||||
maximum <=> Settings.similar_music_sub_minimal_fragment_duration_max;
|
||||
value <=> Settings.similar_music_sub_minimal_fragment_duration_value;
|
||||
}
|
||||
|
||||
}
|
||||
Rectangle {}
|
||||
}
|
||||
VerticalLayout {
|
||||
visible: GuiState.active_tab == CurrentTab.BrokenFiles;
|
||||
spacing: 5px;
|
||||
padding: 10px;
|
||||
SubsettingsHeader { }
|
||||
Text {
|
||||
text: "Type of files to check";
|
||||
font-size: 12px;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Audio";
|
||||
checked <=> Settings.broken_files_sub_audio;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Pdf";
|
||||
checked <=> Settings.broken_files_sub_pdf;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Archive";
|
||||
checked <=> Settings.broken_files_sub_archive;
|
||||
}
|
||||
CheckBoxWrapper {
|
||||
text: "Image";
|
||||
checked <=> Settings.broken_files_sub_image;
|
||||
}
|
||||
|
||||
Rectangle {}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue