1
0
Fork 0
mirror of synced 2024-05-09 23:12:29 +12:00

Excluded extensions and krokiet new features (#1184)

* AVC

* Import split

* Default thread size

* Hen

* Allowed extensions

* Perf

* Connect

* Excluded

* Zmiany

* Optimization

* 4.10

* At once

* Included

* Chang

* VD

* VD

* Hashes

* Wersja

* SD

* Up

* Up

* 2024

* Dup

* Slint files

* Added  select

* Selections

* Fix

* LTO

* Actions

* Added popup delete

* AB

* V4

* Release

* LTO

* Basic moving

* Commonsy

* Moving probably works

* Popup move
This commit is contained in:
Rafał Mikrut 2024-02-14 17:45:25 +01:00 committed by GitHub
parent b63c631e14
commit 378fa1fd6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 3087 additions and 2520 deletions

View file

@ -12,11 +12,11 @@ jobs:
linux-cli: linux-cli:
strategy: strategy:
matrix: matrix:
toolchain: [ stable, 1.72.1 ] toolchain: [ stable, 1.74.0 ]
type: [ release ] type: [ release ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install basic libraries - name: Install basic libraries
run: sudo apt update || true; sudo apt install libheif-dev ffmpeg -y run: sudo apt update || true; sudo apt install libheif-dev ffmpeg -y
@ -24,12 +24,16 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release - name: Build Release
run: cargo build --release --bin czkawka_cli run: cargo build --release --bin czkawka_cli
if: ${{ (matrix.type == 'release') }} if: ${{ (matrix.type == 'release') }}
- name: Store Linux CLI - name: Store Linux CLI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/czkawka_cli path: target/release/czkawka_cli

View file

@ -16,14 +16,14 @@ jobs:
type: [ release ] type: [ release ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install basic libraries - name: Install basic libraries
run: sudo apt update || true; sudo apt install -y ffmpeg run: sudo apt update || true; sudo apt install -y ffmpeg
# New versions of nightly rust may call new unimplemented in eyra functions, so use const version # New versions of nightly rust may call new unimplemented in eyra functions, so use const version
- name: Setup rust version - name: Setup rust version
run: rustup default nightly-2023-12-14 run: rustup default nightly-2024-02-06
- name: Add eyra - name: Add eyra
run: | run: |
@ -32,12 +32,16 @@ jobs:
echo 'fn main() { println!("cargo:rustc-link-arg=-nostartfiles"); }' > build.rs echo 'fn main() { println!("cargo:rustc-link-arg=-nostartfiles"); }' > build.rs
cd .. cd ..
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release - name: Build Release
run: cargo build --release --bin czkawka_cli run: cargo build --release --bin czkawka_cli
if: ${{ (matrix.type == 'release') }} if: ${{ (matrix.type == 'release') }}
- name: Store Linux CLI - name: Store Linux CLI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/czkawka_cli path: target/release/czkawka_cli

View file

@ -12,21 +12,25 @@ jobs:
linux-krokiet-gui: linux-krokiet-gui:
strategy: strategy:
matrix: matrix:
toolchain: [ stable, 1.72.1 ] toolchain: [ stable, 1.74.0 ]
type: [ release ] type: [ release ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release Krokiet - name: Build Release Krokiet
run: cargo build --release --bin krokiet run: cargo build --release --bin krokiet
if: ${{ (matrix.type == 'release') }} if: ${{ (matrix.type == 'release') }}
- name: Store Linux GUI Krokiet - name: Store Linux GUI Krokiet
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-${{ runner.os }}-${{ matrix.toolchain }} name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/krokiet path: target/release/krokiet
@ -35,11 +39,11 @@ jobs:
linux-krokiet-gui-heif: linux-krokiet-gui-heif:
strategy: strategy:
matrix: matrix:
toolchain: [ stable, 1.72.1 ] toolchain: [ stable, 1.74.0 ]
type: [ release ] type: [ release ]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install basic libraries - name: Install basic libraries
run: sudo apt update || true; sudo apt install libheif-dev libraw-dev -y run: sudo apt update || true; sudo apt install libheif-dev libraw-dev -y
@ -47,12 +51,16 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release Krokiet heif - name: Build Release Krokiet heif
run: cargo build --release --bin krokiet --features "heif,libraw" run: cargo build --release --bin krokiet --features "heif,libraw"
if: ${{ (matrix.type == 'release') }} if: ${{ (matrix.type == 'release') }}
- name: Store Linux GUI Krokiet heif libraw - name: Store Linux GUI Krokiet heif libraw
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}-heif-libraw name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}-heif-libraw
path: target/release/krokiet path: target/release/krokiet
@ -61,11 +69,11 @@ jobs:
linux-gui: linux-gui:
strategy: strategy:
matrix: matrix:
toolchain: [ stable, 1.72.1 ] toolchain: [ stable, 1.74.0 ]
type: [ release ] type: [ release ]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install basic libraries - name: Install basic libraries
run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev libraw-dev -y run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev libraw-dev -y
@ -73,12 +81,16 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release Heif Libraw - name: Build Release Heif Libraw
run: cargo build --release --bin czkawka_gui --features "heif,libraw" run: cargo build --release --bin czkawka_gui --features "heif,libraw"
if: ${{ (matrix.type == 'release') }} if: ${{ (matrix.type == 'release') }}
- name: Store Linux GUI Heif Libraw - name: Store Linux GUI Heif Libraw
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}-heif-libraw name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}-heif-libraw
path: target/release/czkawka_gui path: target/release/czkawka_gui
@ -90,7 +102,7 @@ jobs:
# Only store stable toolchain # Only store stable toolchain
- name: Store Linux GUI - name: Store Linux GUI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/czkawka_gui path: target/release/czkawka_gui
@ -103,7 +115,7 @@ jobs:
type: [ release ] type: [ release ]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev librsvg2-dev wget fuse libfuse2 -y run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev librsvg2-dev wget fuse libfuse2 -y
@ -111,6 +123,10 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release - name: Build Release
run: cargo build --release --bin czkawka_gui run: cargo build --release --bin czkawka_gui
@ -127,7 +143,7 @@ jobs:
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin gtk --output appimage --icon-file data/icons/com.github.qarmin.czkawka.svg --desktop-file data/com.github.qarmin.czkawka.desktop ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin gtk --output appimage --icon-file data/icons/com.github.qarmin.czkawka.svg --desktop-file data/com.github.qarmin.czkawka.desktop
- name: Store Linux Appimage GUI - name: Store Linux Appimage GUI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-appimage-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_gui-appimage-${{ runner.os }}-${{ matrix.toolchain }}
path: Czkawka*.AppImage path: Czkawka*.AppImage
@ -144,7 +160,7 @@ jobs:
mv out/Czkawka*.AppImage out/czkawka_gui-minimal.AppImage mv out/Czkawka*.AppImage out/czkawka_gui-minimal.AppImage
- name: Minimal Appimage Upload - name: Minimal Appimage Upload
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-${{ matrix.toolchain }}_minimal_AppImage name: czkawka_gui-${{ matrix.toolchain }}_minimal_AppImage
path: out/*.AppImage path: out/*.AppImage
@ -156,7 +172,7 @@ jobs:
type: [ debug ] type: [ debug ]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev librsvg2-dev wget fuse libfuse2 -y xvfb run: sudo apt update || true; sudo apt install libgtk-4-dev libheif-dev librsvg2-dev wget fuse libfuse2 -y xvfb

View file

@ -16,7 +16,7 @@ jobs:
type: [ release ] type: [ release ]
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Homebrew - name: Install Homebrew
run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" run: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
@ -30,26 +30,30 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default ${{ matrix.toolchain }} run: rustup default ${{ matrix.toolchain }}
- name: Enable LTO
run: sed -i '' 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
if: ${{ (matrix.type == 'release') }}
- name: Build Release - name: Build Release
run: cargo build --release run: cargo build --release
if: ${{ matrix.type == 'release'}} if: ${{ matrix.type == 'release'}}
- name: Store MacOS CLI - name: Store MacOS CLI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/czkawka_cli path: target/release/czkawka_cli
if: ${{ matrix.type == 'release' }} if: ${{ matrix.type == 'release' }}
- name: Store MacOS GUI - name: Store MacOS GUI
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }} name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/czkawka_gui path: target/release/czkawka_gui
if: ${{ matrix.type == 'release' }} if: ${{ matrix.type == 'release' }}
- name: Store MacOS Krokiet - name: Store MacOS Krokiet
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-${{ runner.os }}-${{ matrix.toolchain }} name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}
path: target/release/krokiet path: target/release/krokiet
@ -60,21 +64,21 @@ jobs:
if: ${{ matrix.type == 'release'}} if: ${{ matrix.type == 'release'}}
- name: Store MacOS CLI Heif - name: Store MacOS CLI Heif
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }}-heif name: czkawka_cli-${{ runner.os }}-${{ matrix.toolchain }}-heif
path: target/release/czkawka_cli path: target/release/czkawka_cli
if: ${{ matrix.type == 'release' }} if: ${{ matrix.type == 'release' }}
- name: Store MacOS GUI Heif - name: Store MacOS GUI Heif
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}-heif name: czkawka_gui-${{ runner.os }}-${{ matrix.toolchain }}-heif
path: target/release/czkawka_gui path: target/release/czkawka_gui
if: ${{ matrix.type == 'release' }} if: ${{ matrix.type == 'release' }}
- name: Store MacOS Krokiet Heif - name: Store MacOS Krokiet Heif
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}-heif name: krokiet-${{ runner.os }}-${{ matrix.toolchain }}-heif
path: target/release/krokiet path: target/release/krokiet

View file

@ -12,7 +12,7 @@ jobs:
quality: quality:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install Gtk 4 - name: Install Gtk 4
run: sudo apt update || true; sudo apt install -y libgtk-4-dev libraw-dev libheif-dev -y run: sudo apt update || true; sudo apt install -y libgtk-4-dev libraw-dev libheif-dev -y

View file

@ -16,7 +16,7 @@ jobs:
use_heif: [ normal ] use_heif: [ normal ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install dependencies(mostly sd) - name: Install dependencies(mostly sd)
run: | run: |
@ -30,11 +30,14 @@ jobs:
run: | run: |
rustup target add x86_64-pc-windows-gnu rustup target add x86_64-pc-windows-gnu
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
- name: Compile Krokiet - name: Compile Krokiet
run: cargo build --release --target x86_64-pc-windows-gnu --bin krokiet run: cargo build --release --target x86_64-pc-windows-gnu --bin krokiet
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-windows-c-on-linux-${{ github.sha }}-${{ matrix.use_heif }} name: krokiet-windows-c-on-linux-${{ github.sha }}-${{ matrix.use_heif }}
path: | path: |
@ -50,7 +53,7 @@ jobs:
run: cargo build --release --target x86_64-pc-windows-gnu --bin krokiet run: cargo build --release --target x86_64-pc-windows-gnu --bin krokiet
- name: Upload artifacts Console - name: Upload artifacts Console
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-windows-c-on-linux-${{ github.sha }}-${{ matrix.use_heif }}-console name: krokiet-windows-c-on-linux-${{ github.sha }}-${{ matrix.use_heif }}-console
path: | path: |
@ -64,7 +67,7 @@ jobs:
use_heif: [ normal ] use_heif: [ normal ]
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install dependencies(mostly sd) - name: Install dependencies(mostly sd)
run: | run: |
@ -75,11 +78,14 @@ jobs:
- name: Setup rust version - name: Setup rust version
run: rustup default stable-x86_64-pc-windows-gnu run: rustup default stable-x86_64-pc-windows-gnu
- name: Enable LTO
run: sed -i 's/#lto = "fat"/lto = "fat"/g' Cargo.toml
- name: Compile Krokiet - name: Compile Krokiet
run: cargo build --release --bin krokiet run: cargo build --release --bin krokiet
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-windows-c-on-windows-${{ github.sha }}-${{ matrix.use_heif }} name: krokiet-windows-c-on-windows-${{ github.sha }}-${{ matrix.use_heif }}
path: | path: |
@ -95,7 +101,7 @@ jobs:
run: cargo build --release --bin krokiet run: cargo build --release --bin krokiet
- name: Upload artifacts Console - name: Upload artifacts Console
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: krokiet-windows-c-on-windows-${{ github.sha }}-${{ matrix.use_heif }}-console name: krokiet-windows-c-on-windows-${{ github.sha }}-${{ matrix.use_heif }}-console
path: | path: |
@ -103,16 +109,16 @@ jobs:
if-no-files-found: error if-no-files-found: error
container: container_4_10:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
use_heif: [ non_heif ] use_heif: [ non_heif ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/piegamesde/gtk4-cross:gtk-4.6 image: ghcr.io/mglolenstine/gtk4-cross:gtk-4.10
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install additional dependencies - name: Install additional dependencies
# gio is for the build script # gio is for the build script
run: | run: |
@ -155,20 +161,79 @@ jobs:
rm gtk4_theme.zip rm gtk4_theme.zip
cd ../.. cd ../..
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka-windows-${{ github.sha }}-${{ matrix.use_heif }} name: czkawka-windows-${{ github.sha }}-${{ matrix.use_heif }}-4.10
path: |
./package
if-no-files-found: error
container_4_6:
strategy:
fail-fast: false
matrix:
use_heif: [ non_heif ]
runs-on: ubuntu-latest
container:
image: ghcr.io/piegamesde/gtk4-cross:gtk-4.6
steps:
- uses: actions/checkout@v4
- name: Install additional dependencies
# gio is for the build script
run: |
dnf install curl wget2 unzip mingw64-bzip2.noarch mingw64-poppler mingw64-poppler-glib mingw32-python3 rust-gio-devel adwaita-icon-theme -y && dnf clean all -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustup target add x86_64-pc-windows-gnu
- name: Cross compile for Windows
run: |
source "$HOME/.cargo/env"
#!/bin/bash
set -euo pipefail
export PKG_CONFIG_PATH=/usr/lib64/pkgconfig:/usr/share/pkgconfig:$MINGW_PREFIX/lib/pkgconfig/:/usr/x86_64-w64-mingw32/lib/pkgconfig/
cargo build --target=x86_64-pc-windows-gnu --release --locked
mkdir -p package
cp target/x86_64-pc-windows-gnu/release/czkawka_gui.exe package/
cp target/x86_64-pc-windows-gnu/release/czkawka_cli.exe package/
- name: Package
run: |
#!/bin/bash
set -euo pipefail
cp -t package $(pds -vv -f package/*.exe)
# Add gdbus which is recommended on Windows (why?)
cp $MINGW_PREFIX/bin/gdbus.exe package
# Handle the glib schema compilation as well
glib-compile-schemas $MINGW_PREFIX/share/glib-2.0/schemas/
mkdir -p package/share/glib-2.0/schemas/
cp -T $MINGW_PREFIX/share/glib-2.0/schemas/gschemas.compiled package/share/glib-2.0/schemas/gschemas.compiled
# Pixbuf stuff, in order to get SVGs (scalable icons) to load
mkdir -p package/lib/gdk-pixbuf-2.0
cp -rT $MINGW_PREFIX/lib/gdk-pixbuf-2.0 package/lib/gdk-pixbuf-2.0
cp -f -t package $(pds -vv -f $MINGW_PREFIX/lib/gdk-pixbuf-2.0/2.10.0/loaders/*)
find package -iname "*.dll" -or -iname "*.exe" -type f -exec mingw-strip {} +
cd package/share
wget2 https://github.com/qarmin/czkawka/files/10832192/gtk4_theme.zip
unzip gtk4_theme.zip
rm gtk4_theme.zip
cd ../..
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: czkawka-windows-${{ github.sha }}-${{ matrix.use_heif }}-4.6
path: | path: |
./package ./package
if-no-files-found: error if-no-files-found: error
# Provide option to log things to windows CLI # Provide option to log things to windows CLI
container_console_window: container_4_6_console_window:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: ghcr.io/piegamesde/gtk4-cross:gtk-4.6 image: ghcr.io/piegamesde/gtk4-cross:gtk-4.6
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install dependencies(mostly sd) - name: Install dependencies(mostly sd)
run: | run: |
dnf install wget -y dnf install wget -y
@ -219,9 +284,9 @@ jobs:
rm gtk4_theme.zip rm gtk4_theme.zip
cd ../.. cd ../..
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: czkawka-windows-${{ github.sha }}-console name: czkawka-windows-${{ github.sha }}-console-4.6
path: | path: |
./package ./package
if-no-files-found: error if-no-files-found: error

5
.gitignore vendored
View file

@ -18,4 +18,7 @@ ci_tester/Cargo.lock
krokiet/Cargo.lock krokiet/Cargo.lock
krokiet/target krokiet/target
*.json *.json
*.mm_profdata *.mm_profdata
perf.data
perf.data.old
krokiet/ui/test.slint

1132
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,7 @@ resolver = "2"
panic = "unwind" panic = "unwind"
# LTO setting is disabled by default, because release mode is usually needed to develop app and compilation with LTO would take a lot of time # LTO setting is disabled by default, because release mode is usually needed to develop app and compilation with LTO would take a lot of time
# But it is used to optimize release builds(and probably also in CI, where time is not so important as in local development)
#lto = "fat" #lto = "fat"
# Optimize all dependencies except application/workspaces, even in debug builds # Optimize all dependencies except application/workspaces, even in debug builds

View file

@ -10,9 +10,9 @@
### CLI ### CLI
- Providing full static rust binary with [Eyra](https://github.com/sunfishcode/eyra) - [#1102](https://github.com/qarmin/czkawka/pull/1102) - Providing full static rust binary with [Eyra](https://github.com/sunfishcode/eyra) - [#1102](https://github.com/qarmin/czkawka/pull/1102)
- Fixed duplicated `-c` argument, now saving as compact json is handled via `-C` - [#1153](https://github.com/qarmin/czkawka/pull/1153) - Fixed duplicated `-c` argument, now saving as compact json is handled via `-C` - [#1153](https://github.com/qarmin/czkawka/pull/1153)
- Added progress bar - [#TODO]() - Added scan progress bar - [#1183](https://github.com/qarmin/czkawka/pull/1183)
- Clean and safe cancelling of scan - [#TODO]() - Clean and safe cancelling of scan - [#1183](https://github.com/qarmin/czkawka/pull/1183)
- Unification of CLI arguments - [#TODO]() - Unification of CLI arguments - [#1183](https://github.com/qarmin/czkawka/pull/1183)
### Krokiet GUI ### Krokiet GUI
- Initial release of new gui - [#1102](https://github.com/qarmin/czkawka/pull/1102) - Initial release of new gui - [#1102](https://github.com/qarmin/czkawka/pull/1102)
@ -31,6 +31,9 @@
- Unifying code for collecting files to scan - [#1159](https://github.com/qarmin/czkawka/pull/1159) - Unifying code for collecting files to scan - [#1159](https://github.com/qarmin/czkawka/pull/1159)
- Decrease memory usage when collecting files by removing unused fields in custom file entries structs - [#1159](https://github.com/qarmin/czkawka/pull/1159) - Decrease memory usage when collecting files by removing unused fields in custom file entries structs - [#1159](https://github.com/qarmin/czkawka/pull/1159)
- Decrease a little size of cache by few percents and improve loading/saving speed - [#1159](https://github.com/qarmin/czkawka/pull/1159) - Decrease a little size of cache by few percents and improve loading/saving speed - [#1159](https://github.com/qarmin/czkawka/pull/1159)
- Added ability to remove from scan files with excluded extensions - [#1184](https://github.com/qarmin/czkawka/pull/1102)
- Fixed not showing in similar images results, files with same hashes when using reference folders - [#1184](https://github.com/qarmin/czkawka/pull/1102)
- Optimize release binaries with LTO(~25% smaller, ~5/10% faster) - [#1184](https://github.com/qarmin/czkawka/pull/1102)
## Version 6.1.0 - 15.10.2023r ## Version 6.1.0 - 15.10.2023r
- BREAKING CHANGE - Changed cache saving method, deduplicated, optimized and simplified procedure(all files needs to be hashed again) - [#1072](https://github.com/qarmin/czkawka/pull/1072), [#1086](https://github.com/qarmin/czkawka/pull/1086) - BREAKING CHANGE - Changed cache saving method, deduplicated, optimized and simplified procedure(all files needs to be hashed again) - [#1072](https://github.com/qarmin/czkawka/pull/1072), [#1086](https://github.com/qarmin/czkawka/pull/1086)

View file

@ -1,9 +1,10 @@
use log::info;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fs; use std::fs;
use std::process::Command; use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
use log::info;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
struct CollectedFiles { struct CollectedFiles {
files: BTreeSet<String>, files: BTreeSet<String>,

View file

@ -3,14 +3,14 @@ name = "czkawka_cli"
version = "6.1.0" version = "6.1.0"
authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"] authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.72.1" rust-version = "1.74.0"
description = "CLI frontend of Czkawka" description = "CLI frontend of Czkawka"
license = "MIT" license = "MIT"
homepage = "https://github.com/qarmin/czkawka" homepage = "https://github.com/qarmin/czkawka"
repository = "https://github.com/qarmin/czkawka" repository = "https://github.com/qarmin/czkawka"
[dependencies] [dependencies]
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
# For enum types # For enum types
image_hasher = "1.2" image_hasher = "1.2"

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 Rafał Mikrut Copyright (c) 2020-2024 Rafał Mikrut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -25,7 +25,7 @@ cargo run --release --bin czkawka_cli --features "heif,libraw"
``` ```
on linux to build fully static binary with eyra you need to use (this is only for crazy people, so just use command above if you don't know what you are doing) on linux to build fully static binary with eyra you need to use (this is only for crazy people, so just use command above if you don't know what you are doing)
```shell ```shell
rustup default nightly-2023-11-16 # or any newer nightly that works fine with eyra rustup default nightly-2024-02-06 # or any newer nightly that works fine with eyra
cd czkawka_cli cd czkawka_cli
cargo add eyra --rename=std cargo add eyra --rename=std
echo 'fn main() { println!("cargo:rustc-link-arg=-nostartfiles"); }' > build.rs echo 'fn main() { println!("cargo:rustc-link-arg=-nostartfiles"); }' > build.rs

View file

@ -428,6 +428,8 @@ pub struct CommonCliItems {
long_help = "List of checked files with provided extension(s). There are also helpful macros which allow to easy use a typical extensions like:\nIMAGE(\"jpg,kra,gif,png,bmp,tiff,hdr,svg\"),\nTEXT(\"txt,doc,docx,odt,rtf\"),\nVIDEO(\"mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp\") or\nMUSIC(\"mp3,flac,ogg,tta,wma,webm\")\n " long_help = "List of checked files with provided extension(s). There are also helpful macros which allow to easy use a typical extensions like:\nIMAGE(\"jpg,kra,gif,png,bmp,tiff,hdr,svg\"),\nTEXT(\"txt,doc,docx,odt,rtf\"),\nVIDEO(\"mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp\") or\nMUSIC(\"mp3,flac,ogg,tta,wma,webm\")\n "
)] )]
pub allowed_extensions: Vec<String>, pub allowed_extensions: Vec<String>,
#[clap(short = 'E', long, help = "Excluded file extension(s)", long_help = "List of extensions, that will be removed from search.\n ")]
pub excluded_extensions: Vec<String>,
#[clap(flatten)] #[clap(flatten)]
pub file_to_save: FileToSave, pub file_to_save: FileToSave,
#[clap(flatten)] #[clap(flatten)]

View file

@ -10,7 +10,7 @@ use commands::Commands;
use czkawka_core::bad_extensions::BadExtensions; use czkawka_core::bad_extensions::BadExtensions;
use czkawka_core::big_file::{BigFile, SearchMode}; use czkawka_core::big_file::{BigFile, SearchMode};
use czkawka_core::broken_files::BrokenFiles; use czkawka_core::broken_files::BrokenFiles;
use czkawka_core::common::{print_version_mode, set_number_of_threads, setup_logger}; use czkawka_core::common::{print_version_mode, set_number_of_threads, setup_logger, DEFAULT_THREAD_SIZE};
use czkawka_core::common_dir_traversal::ProgressData; use czkawka_core::common_dir_traversal::ProgressData;
use czkawka_core::common_tool::{CommonData, DeleteMethod}; use czkawka_core::common_tool::{CommonData, DeleteMethod};
#[allow(unused_imports)] // It is used in release for print_results_to_output(). #[allow(unused_imports)] // It is used in release for print_results_to_output().
@ -46,7 +46,7 @@ fn main() {
let (progress_sender, progress_receiver): (Sender<ProgressData>, Receiver<ProgressData>) = unbounded(); let (progress_sender, progress_receiver): (Sender<ProgressData>, Receiver<ProgressData>) = unbounded();
let (stop_sender, stop_receiver): (Sender<()>, Receiver<()>) = bounded(1); let (stop_sender, stop_receiver): (Sender<()>, Receiver<()>) = bounded(1);
let calculate_thread = thread::spawn(move || match command { let calculate_thread = thread::Builder::new().stack_size(DEFAULT_THREAD_SIZE).spawn(move || match command {
Commands::Duplicates(duplicates_args) => duplicates(duplicates_args, &stop_receiver, &progress_sender), Commands::Duplicates(duplicates_args) => duplicates(duplicates_args, &stop_receiver, &progress_sender),
Commands::EmptyFolders(empty_folders_args) => empty_folders(empty_folders_args, &stop_receiver, &progress_sender), Commands::EmptyFolders(empty_folders_args) => empty_folders(empty_folders_args, &stop_receiver, &progress_sender),
Commands::BiggestFiles(biggest_files_args) => biggest_files(biggest_files_args, &stop_receiver, &progress_sender), Commands::BiggestFiles(biggest_files_args) => biggest_files(biggest_files_args, &stop_receiver, &progress_sender),
@ -70,7 +70,7 @@ fn main() {
connect_progress(&progress_receiver); connect_progress(&progress_receiver);
calculate_thread.join().unwrap(); calculate_thread.unwrap().join().unwrap();
} }
fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender<ProgressData>) { fn duplicates(duplicates: DuplicatesArgs, stop_receiver: &Receiver<()>, progress_sender: &Sender<ProgressData>) {

View file

@ -3,7 +3,7 @@ name = "czkawka_core"
version = "6.1.0" version = "6.1.0"
authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"] authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.72.1" rust-version = "1.74.0"
description = "Core of Czkawka app" description = "Core of Czkawka app"
license = "MIT" license = "MIT"
homepage = "https://github.com/qarmin/czkawka" homepage = "https://github.com/qarmin/czkawka"
@ -34,15 +34,15 @@ audio_checker = "0.1"
pdf = "0.9" pdf = "0.9"
# Needed by audio similarity feature # Needed by audio similarity feature
rusty-chromaprint = "0.1" rusty-chromaprint = "0.2"
symphonia = { version = "0.5", features = ["all"] } symphonia = { version = "0.5", features = ["all"] }
# Hashes for duplicate files # Hashes for duplicate files
blake3 = "1.5" blake3 = "1.5"
crc32fast = "1.3" crc32fast = "1.4"
xxhash-rust = { version = "0.8", features = ["xxh3"] } xxhash-rust = { version = "0.8", features = ["xxh3"] }
tempfile = "3.9" tempfile = "3.10"
# Video Duplicates # Video Duplicates
vid_dup_finder_lib = "0.1" vid_dup_finder_lib = "0.1"
@ -55,7 +55,7 @@ serde_json = "1.0"
# Language # Language
i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] } i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.7" i18n-embed-fl = "0.8"
rust-embed = { version = "8.2", features = ["debug-embed"] } rust-embed = { version = "8.2", features = ["debug-embed"] }
once_cell = "1.19" once_cell = "1.19"
@ -75,6 +75,7 @@ anyhow = { version = "1.0" }
state = "0.6" state = "0.6"
trash = "3.3"
os_info = { version = "3", default-features = false } os_info = { version = "3", default-features = false }
log = "0.4.20" log = "0.4.20"

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 Rafał Mikrut Copyright (c) 2020-2024 Rafał Mikrut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -195,7 +195,7 @@ impl BadExtensions {
#[fun_time(message = "find_bad_extensions_files", level = "info")] #[fun_time(message = "find_bad_extensions_files", level = "info")]
pub fn find_bad_extensions_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_bad_extensions_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;

View file

@ -43,7 +43,7 @@ impl BigFile {
#[fun_time(message = "find_big_files", level = "info")] #[fun_time(message = "find_big_files", level = "info")]
pub fn find_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.look_for_big_files(stop_receiver, progress_sender) { if !self.look_for_big_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;
@ -60,6 +60,7 @@ impl BigFile {
.progress_sender(progress_sender) .progress_sender(progress_sender)
.common_data(&self.common_data) .common_data(&self.common_data)
.minimal_file_size(1) .minimal_file_size(1)
.maximal_file_size(u64::MAX)
.max_stage(0) .max_stage(0)
.build() .build()
.run(); .run();

View file

@ -1,7 +1,6 @@
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::{fs, mem, panic}; use std::{fs, mem, panic};
@ -107,7 +106,7 @@ impl BrokenFiles {
#[fun_time(message = "find_broken_files", level = "info")] #[fun_time(message = "find_broken_files", level = "info")]
pub fn find_broken_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_broken_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;
@ -140,8 +139,8 @@ impl BrokenFiles {
} }
} }
self.common_data.allowed_extensions.set_and_validate_extensions(&extensions); self.common_data.extensions.set_and_validate_allowed_extensions(&extensions);
if !self.common_data.allowed_extensions.set_any_extensions() { if !self.common_data.extensions.set_any_extensions() {
return true; return true;
} }

View file

@ -1,4 +1,6 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use std::{fs, thread};
// I don't wanna fight with unused imports in this file, so simply ignore it to avoid too much complexity // I don't wanna fight with unused imports in this file, so simply ignore it to avoid too much complexity
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ffi::OsString; use std::ffi::OsString;
@ -8,7 +10,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize};
use std::sync::{atomic, Arc}; use std::sync::{atomic, Arc};
use std::thread::{sleep, JoinHandle}; use std::thread::{sleep, JoinHandle};
use std::time::{Duration, Instant, SystemTime}; use std::time::{Duration, Instant, SystemTime};
use std::{fs, thread};
#[cfg(feature = "heif")] #[cfg(feature = "heif")]
use anyhow::Result; use anyhow::Result;
@ -38,20 +39,16 @@ use crate::duplicate::make_hard_link;
use crate::CZKAWKA_VERSION; use crate::CZKAWKA_VERSION;
static NUMBER_OF_THREADS: state::InitCell<usize> = state::InitCell::new(); static NUMBER_OF_THREADS: state::InitCell<usize> = state::InitCell::new();
static ALL_AVAILABLE_THREADS: state::InitCell<usize> = state::InitCell::new();
pub const DEFAULT_THREAD_SIZE: usize = 8 * 1024 * 1024; // 8 MB pub const DEFAULT_THREAD_SIZE: usize = 8 * 1024 * 1024; // 8 MB
pub const DEFAULT_WORKER_THREAD_SIZE: usize = 4 * 1024 * 1024; // 4 MB pub const DEFAULT_WORKER_THREAD_SIZE: usize = 4 * 1024 * 1024; // 4 MB
#[cfg(not(target_family = "windows"))]
pub const CHARACTER: char = '/';
#[cfg(target_family = "windows")]
pub const CHARACTER: char = '\\';
pub fn get_number_of_threads() -> usize { pub fn get_number_of_threads() -> usize {
let data = NUMBER_OF_THREADS.get(); let data = NUMBER_OF_THREADS.get();
if *data >= 1 { if *data >= 1 {
*data *data
} else { } else {
get_default_number_of_threads() get_all_available_threads()
} }
} }
@ -70,15 +67,19 @@ pub fn setup_logger(disabled_printing: bool) {
handsome_logger::TermLogger::init(config, TerminalMode::Mixed, ColorChoice::Always).unwrap(); handsome_logger::TermLogger::init(config, TerminalMode::Mixed, ColorChoice::Always).unwrap();
} }
pub fn get_available_threads() -> usize { pub fn get_all_available_threads() -> usize {
thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(1) *ALL_AVAILABLE_THREADS.get_or_init(|| {
let available_threads = thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(1);
ALL_AVAILABLE_THREADS.set(available_threads);
available_threads
})
} }
pub fn print_version_mode() { pub fn print_version_mode() {
let rust_version = env!("RUST_VERSION_INTERNAL"); let rust_version = env!("RUST_VERSION_INTERNAL");
let debug_release = if cfg!(debug_assertions) { "debug" } else { "release" }; let debug_release = if cfg!(debug_assertions) { "debug" } else { "release" };
let processors = get_available_threads(); let processors = get_all_available_threads();
let info = os_info::get(); let info = os_info::get();
info!( info!(
@ -98,11 +99,7 @@ pub fn print_version_mode() {
} }
pub fn set_default_number_of_threads() { pub fn set_default_number_of_threads() {
set_number_of_threads(get_default_number_of_threads()); set_number_of_threads(get_all_available_threads());
}
pub fn get_default_number_of_threads() -> usize {
thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(1)
} }
pub fn set_number_of_threads(thread_number: usize) { pub fn set_number_of_threads(thread_number: usize) {
@ -149,22 +146,21 @@ pub const VIDEO_FILES_EXTENSIONS: &[&str] = &[
pub const LOOP_DURATION: u32 = 20; //ms pub const LOOP_DURATION: u32 = 20; //ms
pub const SEND_PROGRESS_DATA_TIME_BETWEEN: u32 = 200; //ms pub const SEND_PROGRESS_DATA_TIME_BETWEEN: u32 = 200; //ms
pub fn remove_folder_if_contains_only_empty_folders(path: impl AsRef<Path>) -> bool { pub fn remove_folder_if_contains_only_empty_folders(path: impl AsRef<Path>, remove_to_trash: bool) -> Result<(), String> {
let path = path.as_ref(); let path = path.as_ref();
if !path.is_dir() { if !path.is_dir() {
error!("Trying to remove folder which is not a directory"); return Err(format!("Trying to remove folder {path:?} which is not a directory",));
return false;
} }
let mut entries_to_check = Vec::new(); let mut entries_to_check = Vec::new();
let Ok(initial_entry) = path.read_dir() else { let Ok(initial_entry) = path.read_dir() else {
return false; return Err(format!("Cannot read directory {path:?}",));
}; };
for entry in initial_entry { for entry in initial_entry {
if let Ok(entry) = entry { if let Ok(entry) = entry {
entries_to_check.push(entry); entries_to_check.push(entry);
} else { } else {
return false; return Err(format!("Cannot read entry from directory {path:?}"));
} }
} }
loop { loop {
@ -172,25 +168,29 @@ pub fn remove_folder_if_contains_only_empty_folders(path: impl AsRef<Path>) -> b
break; break;
}; };
let Some(file_type) = entry.file_type().ok() else { let Some(file_type) = entry.file_type().ok() else {
return false; return Err(format!("Folder contains file with unknown type {:?} inside {path:?}", entry.path()));
}; };
if !file_type.is_dir() { if !file_type.is_dir() {
return false; return Err(format!("Folder contains file {:?} inside {path:?}", entry.path(),));
} }
let Ok(internal_read_dir) = entry.path().read_dir() else { let Ok(internal_read_dir) = entry.path().read_dir() else {
return false; return Err(format!("Cannot read directory {:?} inside {path:?}", entry.path()));
}; };
for internal_elements in internal_read_dir { for internal_elements in internal_read_dir {
if let Ok(internal_element) = internal_elements { if let Ok(internal_element) = internal_elements {
entries_to_check.push(internal_element); entries_to_check.push(internal_element);
} else { } else {
return false; return Err(format!("Cannot read entry from directory {:?} inside {path:?}", entry.path()));
} }
} }
} }
fs::remove_dir_all(path).is_ok() if remove_to_trash {
trash::delete(path).map_err(|e| format!("Cannot move folder {path:?} to trash, reason {e}"))
} else {
fs::remove_dir_all(path).map_err(|e| format!("Cannot remove directory {path:?}, reason {e}"))
}
} }
pub fn open_cache_folder(cache_file_name: &str, save_to_cache: bool, use_json: bool, warnings: &mut Vec<String>) -> Option<((Option<File>, PathBuf), (Option<File>, PathBuf))> { pub fn open_cache_folder(cache_file_name: &str, save_to_cache: bool, use_json: bool, warnings: &mut Vec<String>) -> Option<((Option<File>, PathBuf), (Option<File>, PathBuf))> {
@ -273,7 +273,6 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path>) -> Option<Dynami
let width = processed.width(); let width = processed.width();
let height = processed.height(); let height = processed.height();
dbg!(width, height);
let data = processed.to_vec(); let data = processed.to_vec();
@ -612,10 +611,11 @@ mod test {
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tempfile::tempdir; use tempfile::tempdir;
use crate::common::{normalize_windows_path, regex_check, remove_folder_if_contains_only_empty_folders}; use crate::common::{normalize_windows_path, regex_check, remove_folder_if_contains_only_empty_folders};
use crate::common_items::{new_excluded_item, ExcludedItems}; use crate::common_items::new_excluded_item;
#[test] #[test]
fn test_remove_folder_if_contains_only_empty_folders() { fn test_remove_folder_if_contains_only_empty_folders() {
@ -624,20 +624,20 @@ mod test {
fs::create_dir(&sub_dir).unwrap(); fs::create_dir(&sub_dir).unwrap();
// Test with empty directory // Test with empty directory
assert!(remove_folder_if_contains_only_empty_folders(&sub_dir)); assert!(remove_folder_if_contains_only_empty_folders(&sub_dir, false).is_ok());
assert!(!Path::new(&sub_dir).exists()); assert!(!Path::new(&sub_dir).exists());
// Test with directory containing an empty directory // Test with directory containing an empty directory
fs::create_dir(&sub_dir).unwrap(); fs::create_dir(&sub_dir).unwrap();
fs::create_dir(sub_dir.join("empty_sub_dir")).unwrap(); fs::create_dir(sub_dir.join("empty_sub_dir")).unwrap();
assert!(remove_folder_if_contains_only_empty_folders(&sub_dir)); assert!(remove_folder_if_contains_only_empty_folders(&sub_dir, false).is_ok());
assert!(!Path::new(&sub_dir).exists()); assert!(!Path::new(&sub_dir).exists());
// Test with directory containing a file // Test with directory containing a file
fs::create_dir(&sub_dir).unwrap(); fs::create_dir(&sub_dir).unwrap();
let mut file = fs::File::create(sub_dir.join("file.txt")).unwrap(); let mut file = fs::File::create(sub_dir.join("file.txt")).unwrap();
writeln!(file, "Hello, world!").unwrap(); writeln!(file, "Hello, world!").unwrap();
assert!(!remove_folder_if_contains_only_empty_folders(&sub_dir)); assert!(remove_folder_if_contains_only_empty_folders(&sub_dir, false).is_err());
assert!(Path::new(&sub_dir).exists()); assert!(Path::new(&sub_dir).exists());
} }

View file

@ -1,16 +1,18 @@
use crate::common; use std::collections::BTreeMap;
use crate::common_messages::Messages; use std::io::{BufReader, BufWriter};
use crate::common_traits::ResultEntry;
use crate::duplicate::HashType;
use crate::similar_images::{convert_algorithm_to_string, convert_filters_to_string};
use fun_time::fun_time; use fun_time::fun_time;
use image::imageops::FilterType; use image::imageops::FilterType;
use image_hasher::HashAlg; use image_hasher::HashAlg;
use log::debug; use log::debug;
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::io::{BufReader, BufWriter}; use crate::common;
use crate::common_messages::Messages;
use crate::common_traits::ResultEntry;
use crate::duplicate::HashType;
use crate::similar_images::{convert_algorithm_to_string, convert_filters_to_string};
const CACHE_VERSION: &str = "70"; const CACHE_VERSION: &str = "70";

View file

@ -20,7 +20,6 @@ use crate::common_items::ExcludedItems;
use crate::common_tool::CommonToolData; use crate::common_tool::CommonToolData;
use crate::common_traits::ResultEntry; use crate::common_traits::ResultEntry;
use crate::flc; use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
#[derive(Debug)] #[derive(Debug)]
pub struct ProgressData { pub struct ProgressData {
@ -115,7 +114,7 @@ pub struct DirTraversalBuilder<'a, 'b, F> {
recursive_search: bool, recursive_search: bool,
directories: Option<Directories>, directories: Option<Directories>,
excluded_items: Option<ExcludedItems>, excluded_items: Option<ExcludedItems>,
allowed_extensions: Option<Extensions>, extensions: Option<Extensions>,
tool_type: ToolType, tool_type: ToolType,
} }
@ -127,7 +126,7 @@ pub struct DirTraversal<'a, 'b, F> {
recursive_search: bool, recursive_search: bool,
directories: Directories, directories: Directories,
excluded_items: ExcludedItems, excluded_items: ExcludedItems,
allowed_extensions: Extensions, extensions: Extensions,
minimal_file_size: u64, minimal_file_size: u64,
maximal_file_size: u64, maximal_file_size: u64,
checking_method: CheckingMethod, checking_method: CheckingMethod,
@ -156,7 +155,7 @@ impl<'a, 'b> DirTraversalBuilder<'a, 'b, ()> {
collect: Collect::Files, collect: Collect::Files,
recursive_search: false, recursive_search: false,
directories: None, directories: None,
allowed_extensions: None, extensions: None,
excluded_items: None, excluded_items: None,
tool_type: ToolType::None, tool_type: ToolType::None,
} }
@ -171,7 +170,7 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> {
pub fn common_data(mut self, common_tool_data: &CommonToolData) -> Self { pub fn common_data(mut self, common_tool_data: &CommonToolData) -> Self {
self.root_dirs = common_tool_data.directories.included_directories.clone(); self.root_dirs = common_tool_data.directories.included_directories.clone();
self.allowed_extensions = Some(common_tool_data.allowed_extensions.clone()); self.extensions = Some(common_tool_data.extensions.clone());
self.excluded_items = Some(common_tool_data.excluded_items.clone()); self.excluded_items = Some(common_tool_data.excluded_items.clone());
self.recursive_search = common_tool_data.recursive_search; self.recursive_search = common_tool_data.recursive_search;
self.minimal_file_size = Some(common_tool_data.minimal_file_size); self.minimal_file_size = Some(common_tool_data.minimal_file_size);
@ -221,8 +220,8 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> {
self self
} }
pub fn allowed_extensions(mut self, allowed_extensions: Extensions) -> Self { pub fn extensions(mut self, extensions: Extensions) -> Self {
self.allowed_extensions = Some(allowed_extensions); self.extensions = Some(extensions);
self self
} }
@ -260,7 +259,7 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> {
stop_receiver: self.stop_receiver, stop_receiver: self.stop_receiver,
progress_sender: self.progress_sender, progress_sender: self.progress_sender,
directories: self.directories, directories: self.directories,
allowed_extensions: self.allowed_extensions, extensions: self.extensions,
excluded_items: self.excluded_items, excluded_items: self.excluded_items,
recursive_search: self.recursive_search, recursive_search: self.recursive_search,
maximal_file_size: self.maximal_file_size, maximal_file_size: self.maximal_file_size,
@ -285,7 +284,7 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> {
collect: self.collect, collect: self.collect,
directories: self.directories.expect("could not build"), directories: self.directories.expect("could not build"),
excluded_items: self.excluded_items.expect("could not build"), excluded_items: self.excluded_items.expect("could not build"),
allowed_extensions: self.allowed_extensions.unwrap_or_default(), extensions: self.extensions.unwrap_or_default(),
recursive_search: self.recursive_search, recursive_search: self.recursive_search,
tool_type: self.tool_type, tool_type: self.tool_type,
} }
@ -334,7 +333,7 @@ where
collect, collect,
directories, directories,
excluded_items, excluded_items,
allowed_extensions, extensions,
recursive_search, recursive_search,
minimal_file_size, minimal_file_size,
maximal_file_size, maximal_file_size,
@ -377,7 +376,7 @@ where
entry_data, entry_data,
&mut warnings, &mut warnings,
&mut fe_result, &mut fe_result,
&allowed_extensions, &extensions,
&directories, &directories,
&excluded_items, &excluded_items,
minimal_file_size, minimal_file_size,
@ -389,7 +388,7 @@ where
} }
(EntryType::Symlink, Collect::InvalidSymlinks) => { (EntryType::Symlink, Collect::InvalidSymlinks) => {
counter += 1; counter += 1;
process_symlink_in_symlink_mode(entry_data, &mut warnings, &mut fe_result, &allowed_extensions, &directories, &excluded_items); process_symlink_in_symlink_mode(entry_data, &mut warnings, &mut fe_result, &extensions, &directories, &excluded_items);
} }
(EntryType::Symlink, Collect::Files) | (EntryType::Other, _) => { (EntryType::Symlink, Collect::Files) | (EntryType::Other, _) => {
// nothing to do // nothing to do
@ -435,13 +434,13 @@ fn process_file_in_file_mode(
entry_data: &DirEntry, entry_data: &DirEntry,
warnings: &mut Vec<String>, warnings: &mut Vec<String>,
fe_result: &mut Vec<FileEntry>, fe_result: &mut Vec<FileEntry>,
allowed_extensions: &Extensions, extensions: &Extensions,
directories: &Directories, directories: &Directories,
excluded_items: &ExcludedItems, excluded_items: &ExcludedItems,
minimal_file_size: u64, minimal_file_size: u64,
maximal_file_size: u64, maximal_file_size: u64,
) { ) {
if !allowed_extensions.check_if_entry_ends_with_extension(entry_data) { if !extensions.check_if_entry_have_valid_extension(entry_data) {
return; return;
} }
@ -512,11 +511,11 @@ fn process_symlink_in_symlink_mode(
entry_data: &DirEntry, entry_data: &DirEntry,
warnings: &mut Vec<String>, warnings: &mut Vec<String>,
fe_result: &mut Vec<FileEntry>, fe_result: &mut Vec<FileEntry>,
allowed_extensions: &Extensions, extensions: &Extensions,
directories: &Directories, directories: &Directories,
excluded_items: &ExcludedItems, excluded_items: &ExcludedItems,
) { ) {
if !allowed_extensions.check_if_entry_ends_with_extension(entry_data) { if !extensions.check_if_entry_have_valid_extension(entry_data) {
return; return;
} }
@ -560,10 +559,7 @@ pub fn common_read_dir(current_folder: &Path, warnings: &mut Vec<String>) -> Opt
Some(r) Some(r)
} }
Err(e) => { Err(e) => {
warnings.push(flc!( warnings.push(flc!("core_cannot_open_dir", dir = current_folder.to_string_lossy().to_string(), reason = e.to_string()));
"core_cannot_open_dir",
generate_translation_hashmap(vec![("dir", current_folder.to_string_lossy().to_string()), ("reason", e.to_string())])
));
None None
} }
} }
@ -574,7 +570,8 @@ pub fn common_get_entry_data<'a>(entry: &'a Result<DirEntry, std::io::Error>, wa
Err(e) => { Err(e) => {
warnings.push(flc!( warnings.push(flc!(
"core_cannot_read_entry_dir", "core_cannot_read_entry_dir",
generate_translation_hashmap(vec![("dir", current_folder.to_string_lossy().to_string()), ("reason", e.to_string())]) dir = current_folder.to_string_lossy().to_string(),
reason = e.to_string()
)); ));
return None; return None;
} }
@ -587,7 +584,8 @@ pub fn common_get_metadata_dir(entry_data: &DirEntry, warnings: &mut Vec<String>
Err(e) => { Err(e) => {
warnings.push(flc!( warnings.push(flc!(
"core_cannot_read_metadata_dir", "core_cannot_read_metadata_dir",
generate_translation_hashmap(vec![("dir", current_folder.to_string_lossy().to_string()), ("reason", e.to_string())]) dir = current_folder.to_string_lossy().to_string(),
reason = e.to_string()
)); ));
return None; return None;
} }
@ -601,7 +599,8 @@ pub fn common_get_entry_data_metadata<'a>(entry: &'a Result<DirEntry, std::io::E
Err(e) => { Err(e) => {
warnings.push(flc!( warnings.push(flc!(
"core_cannot_read_entry_dir", "core_cannot_read_entry_dir",
generate_translation_hashmap(vec![("dir", current_folder.to_string_lossy().to_string()), ("reason", e.to_string())]) dir = current_folder.to_string_lossy().to_string(),
reason = e.to_string()
)); ));
return None; return None;
} }
@ -611,7 +610,8 @@ pub fn common_get_entry_data_metadata<'a>(entry: &'a Result<DirEntry, std::io::E
Err(e) => { Err(e) => {
warnings.push(flc!( warnings.push(flc!(
"core_cannot_read_metadata_dir", "core_cannot_read_metadata_dir",
generate_translation_hashmap(vec![("dir", current_folder.to_string_lossy().to_string()), ("reason", e.to_string())]) dir = current_folder.to_string_lossy().to_string(),
reason = e.to_string()
)); ));
return None; return None;
} }
@ -624,21 +624,27 @@ pub fn get_modified_time(metadata: &Metadata, warnings: &mut Vec<String>, curren
Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(t) => match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(), Ok(d) => d.as_secs(),
Err(_inspected) => { Err(_inspected) => {
let translation_hashmap = generate_translation_hashmap(vec![("name", current_file_name.to_string_lossy().to_string())]);
if is_folder { if is_folder {
warnings.push(flc!("core_folder_modified_before_epoch", translation_hashmap)); warnings.push(flc!("core_folder_modified_before_epoch", name = current_file_name.to_string_lossy().to_string()));
} else { } else {
warnings.push(flc!("core_file_modified_before_epoch", translation_hashmap)); warnings.push(flc!("core_file_modified_before_epoch", name = current_file_name.to_string_lossy().to_string()));
} }
0 0
} }
}, },
Err(e) => { Err(e) => {
let translation_hashmap = generate_translation_hashmap(vec![("name", current_file_name.to_string_lossy().to_string()), ("reason", e.to_string())]);
if is_folder { if is_folder {
warnings.push(flc!("core_folder_no_modification_date", translation_hashmap)); warnings.push(flc!(
"core_folder_no_modification_date",
name = current_file_name.to_string_lossy().to_string(),
reason = e.to_string()
));
} else { } else {
warnings.push(flc!("core_file_no_modification_date", translation_hashmap)); warnings.push(flc!(
"core_file_no_modification_date",
name = current_file_name.to_string_lossy().to_string(),
reason = e.to_string()
));
} }
0 0
} }

View file

@ -1,11 +1,10 @@
use crate::common::normalize_windows_path;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::{fs, os::unix::fs::MetadataExt}; use std::{fs, os::unix::fs::MetadataExt};
use crate::common::normalize_windows_path;
use crate::common_messages::Messages; use crate::common_messages::Messages;
use crate::flc; use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Directories { pub struct Directories {
@ -105,19 +104,15 @@ impl Directories {
let mut directory = directory.to_path_buf(); let mut directory = directory.to_path_buf();
if !directory.exists() { if !directory.exists() {
if !is_excluded { if !is_excluded {
messages.warnings.push(flc!( messages.warnings.push(flc!("core_directory_must_exists", path = directory.to_string_lossy().to_string()));
"core_directory_must_exists",
generate_translation_hashmap(vec![("path", directory.to_string_lossy().to_string())])
));
} }
return (None, messages); return (None, messages);
} }
if !directory.is_dir() { if !directory.is_dir() {
messages.warnings.push(flc!( messages
"core_directory_must_be_directory", .warnings
generate_translation_hashmap(vec![("path", directory.to_string_lossy().to_string())]) .push(flc!("core_directory_must_be_directory", path = directory.to_string_lossy().to_string()));
));
return (None, messages); return (None, messages);
} }
@ -290,10 +285,7 @@ impl Directories {
for d in &self.included_directories { for d in &self.included_directories {
match fs::metadata(d) { match fs::metadata(d) {
Ok(m) => self.included_dev_ids.push(m.dev()), Ok(m) => self.included_dev_ids.push(m.dev()),
Err(_) => messages.errors.push(flc!( Err(_) => messages.errors.push(flc!("core_directory_unable_to_get_device_id", path = d.to_string_lossy().to_string())),
"core_directory_unable_to_get_device_id",
generate_translation_hashmap(vec![("path", d.to_string_lossy().to_string())])
)),
} }
} }
} }
@ -322,10 +314,7 @@ impl Directories {
let path = path.as_ref(); let path = path.as_ref();
match fs::metadata(path) { match fs::metadata(path) {
Ok(m) => Ok(!self.included_dev_ids.iter().any(|&id| id == m.dev())), Ok(m) => Ok(!self.included_dev_ids.iter().any(|&id| id == m.dev())),
Err(_) => Err(flc!( Err(_) => Err(flc!("core_directory_unable_to_get_device_id", path = path.to_string_lossy().to_string())),
"core_directory_unable_to_get_device_id",
generate_translation_hashmap(vec![("path", path.to_string_lossy().to_string())])
)),
} }
} }
} }

View file

@ -1,37 +1,39 @@
use crate::common_messages::Messages;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::DirEntry; use std::fs::DirEntry;
use crate::common_messages::Messages;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Extensions { pub struct Extensions {
file_extensions_hashset: HashSet<String>, allowed_extensions_hashset: HashSet<String>,
excluded_extensions_hashset: HashSet<String>,
} }
impl Extensions { impl Extensions {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
/// List of allowed extensions, only files with this extensions will be checking if are duplicates
/// After, extensions cannot contains any dot, commas etc. pub fn filter_extensions(mut file_extensions: String) -> (HashSet<String>, Messages) {
pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) -> Messages {
let mut messages = Messages::new(); let mut messages = Messages::new();
let mut extensions_hashset = HashSet::new();
if allowed_extensions.trim().is_empty() { if file_extensions.trim().is_empty() {
return messages; return (Default::default(), messages);
} }
allowed_extensions = allowed_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,hdr,svg"); file_extensions = file_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,hdr,svg");
allowed_extensions = allowed_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp"); file_extensions = file_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp");
allowed_extensions = allowed_extensions.replace("MUSIC", "mp3,flac,ogg,tta,wma,webm"); file_extensions = file_extensions.replace("MUSIC", "mp3,flac,ogg,tta,wma,webm");
allowed_extensions = allowed_extensions.replace("TEXT", "txt,doc,docx,odt,rtf"); file_extensions = file_extensions.replace("TEXT", "txt,doc,docx,odt,rtf");
let extensions: Vec<String> = allowed_extensions.split(',').map(str::trim).map(String::from).collect(); let extensions: Vec<String> = file_extensions.split(',').map(str::trim).map(String::from).collect();
for mut extension in extensions { for mut extension in extensions {
if extension.is_empty() || extension.replace(['.', ' '], "").trim().is_empty() { if extension.is_empty() || extension.replace(['.', ' '], "").trim().is_empty() {
continue; continue;
} }
if extension.starts_with('.') { if extension.starts_with('.') {
extension = extension[1..].to_string(); extension = extension.chars().skip(1).collect::<String>();
} }
if extension.contains('.') { if extension.contains('.') {
@ -44,49 +46,61 @@ impl Extensions {
continue; continue;
} }
self.file_extensions_hashset.insert(extension); extensions_hashset.insert(extension);
} }
(extensions_hashset, messages)
}
if self.file_extensions_hashset.is_empty() { /// List of allowed extensions, only files with this extensions will be checking if are duplicates
messages /// After, extensions cannot contain any dot, commas etc.
.messages pub fn set_allowed_extensions(&mut self, allowed_extensions: String) -> Messages {
.push("No valid extensions were provided, so allowing all extensions by default.".to_string()); let (extensions, messages) = Self::filter_extensions(allowed_extensions);
}
self.allowed_extensions_hashset = extensions;
messages messages
} }
pub fn matches_filename(&self, file_name: &str) -> bool { pub fn set_excluded_extensions(&mut self, excluded_extensions: String) -> Messages {
// assert_eq!(file_name, file_name.to_lowercase()); let (extensions, messages) = Self::filter_extensions(excluded_extensions);
if !self.file_extensions_hashset.is_empty() && !self.file_extensions_hashset.iter().any(|e| file_name.ends_with(e)) {
return false; self.excluded_extensions_hashset = extensions;
} messages
true
} }
pub fn check_if_entry_ends_with_extension(&self, entry_data: &DirEntry) -> bool {
if self.file_extensions_hashset.is_empty() { pub fn check_if_entry_have_valid_extension(&self, entry_data: &DirEntry) -> bool {
if self.allowed_extensions_hashset.is_empty() && self.excluded_extensions_hashset.is_empty() {
return true; return true;
} }
// Using entry_data.path().extension() is a lot of slower, even 5 times
let file_name = entry_data.file_name(); let file_name = entry_data.file_name();
let Some(file_name_str) = file_name.to_str() else { return false }; let Some(file_name_str) = file_name.to_str() else { return false };
let Some(extension_idx) = file_name_str.rfind('.') else { return false }; let Some(extension_idx) = file_name_str.rfind('.') else { return false };
let extension = &file_name_str[extension_idx + 1..]; let extension = &file_name_str[extension_idx + 1..];
if extension.chars().all(|c| c.is_ascii_lowercase()) { if !self.allowed_extensions_hashset.is_empty() {
self.file_extensions_hashset.contains(extension) if extension.chars().all(|c| c.is_ascii_lowercase()) {
self.allowed_extensions_hashset.contains(extension)
} else {
self.allowed_extensions_hashset.contains(&extension.to_lowercase())
}
} else { } else {
self.file_extensions_hashset.contains(&extension.to_lowercase()) if extension.chars().all(|c| c.is_ascii_lowercase()) {
!self.excluded_extensions_hashset.contains(extension)
} else {
!self.excluded_extensions_hashset.contains(&extension.to_lowercase())
}
} }
} }
pub fn set_any_extensions(&self) -> bool { pub fn set_any_extensions(&self) -> bool {
!self.file_extensions_hashset.is_empty() !self.allowed_extensions_hashset.is_empty()
} }
fn extend_allowed_extensions(&mut self, file_extensions: &[&str]) { fn extend_allowed_extensions(&mut self, file_extensions: &[&str]) {
for extension in file_extensions { for extension in file_extensions {
let extension_without_dot = extension.trim_start_matches('.'); let extension_without_dot = extension.trim_start_matches('.');
self.file_extensions_hashset.insert(extension_without_dot.to_string()); self.allowed_extensions_hashset.insert(extension_without_dot.to_string());
} }
} }
@ -100,8 +114,8 @@ impl Extensions {
} }
} }
pub fn set_and_validate_extensions(&mut self, file_extensions: &[&str]) { pub fn set_and_validate_allowed_extensions(&mut self, file_extensions: &[&str]) {
if self.file_extensions_hashset.is_empty() { if self.allowed_extensions_hashset.is_empty() {
self.extend_allowed_extensions(file_extensions); self.extend_allowed_extensions(file_extensions);
} else { } else {
self.union_allowed_extensions(file_extensions); self.union_allowed_extensions(file_extensions);

View file

@ -1,8 +1,8 @@
use std::path::Path;
#[cfg(not(target_family = "unix"))] #[cfg(not(target_family = "unix"))]
use crate::common::normalize_windows_path; use crate::common::normalize_windows_path;
use crate::common::regex_check; use crate::common::regex_check;
use std::path::Path;
use crate::common_messages::Messages; use crate::common_messages::Messages;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]

View file

@ -9,6 +9,15 @@ impl Messages {
pub fn new() -> Self { pub fn new() -> Self {
Default::default() Default::default()
} }
pub fn new_from_errors(errors: Vec<String>) -> Self {
Messages { errors, ..Default::default() }
}
pub fn new_from_warnings(warnings: Vec<String>) -> Self {
Messages { warnings, ..Default::default() }
}
pub fn new_from_messages(messages: Vec<String>) -> Self {
Messages { messages, ..Default::default() }
}
pub fn print_messages(&self) { pub fn print_messages(&self) {
println!("{}", self.create_messages_text()); println!("{}", self.create_messages_text());
} }

View file

@ -11,7 +11,7 @@ pub struct CommonToolData {
pub(crate) tool_type: ToolType, pub(crate) tool_type: ToolType,
pub(crate) text_messages: Messages, pub(crate) text_messages: Messages,
pub(crate) directories: Directories, pub(crate) directories: Directories,
pub(crate) allowed_extensions: Extensions, pub(crate) extensions: Extensions,
pub(crate) excluded_items: ExcludedItems, pub(crate) excluded_items: ExcludedItems,
pub(crate) recursive_search: bool, pub(crate) recursive_search: bool,
pub(crate) delete_method: DeleteMethod, pub(crate) delete_method: DeleteMethod,
@ -43,7 +43,7 @@ impl CommonToolData {
tool_type, tool_type,
text_messages: Messages::new(), text_messages: Messages::new(),
directories: Directories::new(), directories: Directories::new(),
allowed_extensions: Extensions::new(), extensions: Extensions::new(),
excluded_items: ExcludedItems::new(), excluded_items: ExcludedItems::new(),
recursive_search: true, recursive_search: true,
delete_method: DeleteMethod::None, delete_method: DeleteMethod::None,
@ -168,7 +168,11 @@ pub trait CommonData {
self.get_cd_mut().text_messages.extend_with_another_messages(messages); self.get_cd_mut().text_messages.extend_with_another_messages(messages);
} }
fn set_allowed_extensions(&mut self, allowed_extensions: String) { fn set_allowed_extensions(&mut self, allowed_extensions: String) {
let messages = self.get_cd_mut().allowed_extensions.set_allowed_extensions(allowed_extensions); let messages = self.get_cd_mut().extensions.set_allowed_extensions(allowed_extensions);
self.get_cd_mut().text_messages.extend_with_another_messages(messages);
}
fn set_excluded_extensions(&mut self, excluded_extensions: String) {
let messages = self.get_cd_mut().extensions.set_excluded_extensions(excluded_extensions);
self.get_cd_mut().text_messages.extend_with_another_messages(messages); self.get_cd_mut().text_messages.extend_with_another_messages(messages);
} }
@ -177,8 +181,9 @@ pub trait CommonData {
self.get_cd_mut().text_messages.extend_with_another_messages(messages); self.get_cd_mut().text_messages.extend_with_another_messages(messages);
} }
fn optimize_dirs_before_start(&mut self) { fn prepare_items(&mut self) {
let recursive_search = self.get_cd().recursive_search; let recursive_search = self.get_cd().recursive_search;
// Optimizes directories and removes recursive calls
let messages = self.get_cd_mut().directories.optimize_directories(recursive_search); let messages = self.get_cd_mut().directories.optimize_directories(recursive_search);
self.get_cd_mut().text_messages.extend_with_another_messages(messages); self.get_cd_mut().text_messages.extend_with_another_messages(messages);
} }
@ -187,7 +192,7 @@ pub trait CommonData {
println!("---------------DEBUG PRINT COMMON---------------"); println!("---------------DEBUG PRINT COMMON---------------");
println!("Tool type: {:?}", self.get_cd().tool_type); println!("Tool type: {:?}", self.get_cd().tool_type);
println!("Directories: {:?}", self.get_cd().directories); println!("Directories: {:?}", self.get_cd().directories);
println!("Allowed extensions: {:?}", self.get_cd().allowed_extensions); println!("Extensions: {:?}", self.get_cd().extensions);
println!("Excluded items: {:?}", self.get_cd().excluded_items); println!("Excluded items: {:?}", self.get_cd().excluded_items);
println!("Recursive search: {:?}", self.get_cd().recursive_search); println!("Recursive search: {:?}", self.get_cd().recursive_search);
println!("Maximal file size: {:?}", self.get_cd().maximal_file_size); println!("Maximal file size: {:?}", self.get_cd().maximal_file_size);

View file

@ -1,9 +1,10 @@
use fun_time::fun_time;
use serde::Serialize;
use std::fs::File; use std::fs::File;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
use std::path::Path; use std::path::Path;
use fun_time::fun_time;
use serde::Serialize;
pub trait DebugPrint { pub trait DebugPrint {
fn debug_print(&self); fn debug_print(&self);
} }

View file

@ -134,7 +134,7 @@ impl DuplicateFinder {
ignore_hard_links: true, ignore_hard_links: true,
hash_type: HashType::Blake3, hash_type: HashType::Blake3,
use_prehash_cache: true, use_prehash_cache: true,
minimal_cache_file_size: 1024 * 3256, // By default cache only >= 256 KB files minimal_cache_file_size: 1024 * 256, // By default cache only >= 256 KB files
minimal_prehash_cache_file_size: 0, minimal_prehash_cache_file_size: 0,
case_sensitive_name_comparison: false, case_sensitive_name_comparison: false,
} }
@ -142,7 +142,7 @@ impl DuplicateFinder {
#[fun_time(message = "find_duplicates", level = "info")] #[fun_time(message = "find_duplicates", level = "info")]
pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty();
match self.check_method { match self.check_method {

View file

@ -1,5 +1,4 @@
use std::fs; use std::fs;
use std::io::prelude::*; use std::io::prelude::*;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
@ -41,7 +40,7 @@ impl EmptyFiles {
#[fun_time(message = "find_empty_files", level = "info")] #[fun_time(message = "find_empty_files", level = "info")]
pub fn find_empty_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_empty_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;

View file

@ -5,12 +5,12 @@ use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::common::{check_if_stop_received, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads};
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use fun_time::fun_time; use fun_time::fun_time;
use log::debug; use log::debug;
use rayon::prelude::*; use rayon::prelude::*;
use crate::common::{check_if_stop_received, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads};
use crate::common_dir_traversal::{common_get_entry_data, common_get_metadata_dir, common_read_dir, get_modified_time, CheckingMethod, ProgressData, ToolType}; use crate::common_dir_traversal::{common_get_entry_data, common_get_metadata_dir, common_read_dir, get_modified_time, CheckingMethod, ProgressData, ToolType};
use crate::common_directory::Directories; use crate::common_directory::Directories;
use crate::common_items::ExcludedItems; use crate::common_items::ExcludedItems;
@ -26,6 +26,12 @@ pub struct FolderEntry {
pub modified_date: u64, pub modified_date: u64,
} }
impl FolderEntry {
pub fn get_modified_date(&self) -> u64 {
self.modified_date
}
}
pub struct EmptyFolder { pub struct EmptyFolder {
common_data: CommonToolData, common_data: CommonToolData,
information: Info, information: Info,
@ -64,7 +70,7 @@ impl EmptyFolder {
#[fun_time(message = "find_empty_folders", level = "info")] #[fun_time(message = "find_empty_folders", level = "info")]
pub fn find_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_for_empty_folders(stop_receiver, progress_sender) { if !self.check_for_empty_folders(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;
@ -104,19 +110,17 @@ impl EmptyFolder {
let excluded_items = self.common_data.excluded_items.clone(); let excluded_items = self.common_data.excluded_items.clone();
let directories = self.common_data.directories.clone(); let directories = self.common_data.directories.clone();
let mut folder_entries: HashMap<String, FolderEntry> = HashMap::new();
let mut non_empty_folders: Vec<String> = vec![]; let mut non_empty_folders: Vec<String> = vec![];
let mut start_folder_entries = Vec::with_capacity(folders_to_check.len());
let mut new_folder_entries_list = Vec::new();
for dir in &folders_to_check { for dir in &folders_to_check {
folder_entries.insert( start_folder_entries.push(FolderEntry {
dir.to_string_lossy().to_string(), path: dir.clone(),
FolderEntry { parent_path: None,
path: dir.clone(), is_empty: FolderEmptiness::Maybe,
parent_path: None, modified_date: 0,
is_empty: FolderEmptiness::Maybe, });
modified_date: 0,
},
);
} }
while !folders_to_check.is_empty() { while !folders_to_check.is_empty() {
@ -130,12 +134,13 @@ impl EmptyFolder {
.map(|current_folder| { .map(|current_folder| {
let mut dir_result = vec![]; let mut dir_result = vec![];
let mut warnings = vec![]; let mut warnings = vec![];
let mut set_as_not_empty_folder_list = vec![]; let mut non_empty_folder = None;
let mut folder_entries_list = vec![]; let mut folder_entries_list = vec![];
let current_folder_as_string = current_folder.to_string_lossy().to_string();
let Some(read_dir) = common_read_dir(&current_folder, &mut warnings) else { let Some(read_dir) = common_read_dir(&current_folder, &mut warnings) else {
set_as_not_empty_folder_list.push(current_folder.to_string_lossy().to_string()); return (dir_result, warnings, Some(current_folder_as_string), folder_entries_list);
return (dir_result, warnings, set_as_not_empty_folder_list, folder_entries_list);
}; };
let mut counter = 0; let mut counter = 0;
@ -150,23 +155,27 @@ impl EmptyFolder {
counter += 1; counter += 1;
Self::process_dir_in_dir_mode( Self::process_dir_in_dir_mode(
&current_folder, &current_folder,
&current_folder_as_string,
entry_data, entry_data,
&directories, &directories,
&mut dir_result, &mut dir_result,
&mut warnings, &mut warnings,
&excluded_items, &excluded_items,
&mut set_as_not_empty_folder_list, &mut non_empty_folder,
&mut folder_entries_list, &mut folder_entries_list,
); );
} else { } else {
set_as_not_empty_folder_list.push(current_folder.to_string_lossy().to_string()); if non_empty_folder.is_none() {
non_empty_folder = Some(current_folder_as_string.clone());
}
} }
} }
if counter > 0 { if counter > 0 {
// Increase counter in batch, because usually it may be slow to add multiple times atomic value // Increase counter in batch, because usually it may be slow to add multiple times atomic value
atomic_counter.fetch_add(counter, Ordering::Relaxed); atomic_counter.fetch_add(counter, Ordering::Relaxed);
} }
(dir_result, warnings, set_as_not_empty_folder_list, folder_entries_list)
(dir_result, warnings, non_empty_folder, folder_entries_list)
}) })
.collect(); .collect();
@ -174,13 +183,25 @@ impl EmptyFolder {
folders_to_check = Vec::with_capacity(required_size); folders_to_check = Vec::with_capacity(required_size);
// Process collected data // Process collected data
for (segment, warnings, set_as_not_empty_folder_list, fe_list) in segments { for (segment, warnings, non_empty_folder, fe_list) in segments {
folders_to_check.extend(segment); folders_to_check.extend(segment);
self.common_data.text_messages.warnings.extend(warnings); if !warnings.is_empty() {
non_empty_folders.extend(set_as_not_empty_folder_list); self.common_data.text_messages.warnings.extend(warnings);
for (path, entry) in fe_list {
folder_entries.insert(path, entry);
} }
if let Some(non_empty_folder) = non_empty_folder {
non_empty_folders.push(non_empty_folder);
}
new_folder_entries_list.push(fe_list);
}
}
let mut folder_entries: HashMap<String, FolderEntry> = HashMap::with_capacity(start_folder_entries.len() + new_folder_entries_list.iter().map(Vec::len).sum::<usize>());
for fe in start_folder_entries {
folder_entries.insert(fe.path.to_string_lossy().to_string(), fe);
}
for fe_list in new_folder_entries_list {
for fe in fe_list {
folder_entries.insert(fe.path.to_string_lossy().to_string(), fe);
} }
} }
@ -223,18 +244,20 @@ impl EmptyFolder {
fn process_dir_in_dir_mode( fn process_dir_in_dir_mode(
current_folder: &Path, current_folder: &Path,
current_folder_as_str: &str,
entry_data: &DirEntry, entry_data: &DirEntry,
directories: &Directories, directories: &Directories,
dir_result: &mut Vec<PathBuf>, dir_result: &mut Vec<PathBuf>,
warnings: &mut Vec<String>, warnings: &mut Vec<String>,
excluded_items: &ExcludedItems, excluded_items: &ExcludedItems,
set_as_not_empty_folder_list: &mut Vec<String>, non_empty_folder: &mut Option<String>,
folder_entries_list: &mut Vec<(String, FolderEntry)>, folder_entries_list: &mut Vec<FolderEntry>,
) { ) {
let parent_folder_str = current_folder.to_string_lossy().to_string();
let next_folder = entry_data.path(); let next_folder = entry_data.path();
if excluded_items.is_excluded(&next_folder) || directories.is_excluded(&next_folder) { if excluded_items.is_excluded(&next_folder) || directories.is_excluded(&next_folder) {
set_as_not_empty_folder_list.push(parent_folder_str); if non_empty_folder.is_none() {
*non_empty_folder = Some(current_folder_as_str.to_string());
}
return; return;
} }
@ -248,20 +271,19 @@ impl EmptyFolder {
} }
let Some(metadata) = common_get_metadata_dir(entry_data, warnings, &next_folder) else { let Some(metadata) = common_get_metadata_dir(entry_data, warnings, &next_folder) else {
set_as_not_empty_folder_list.push(parent_folder_str); if non_empty_folder.is_none() {
*non_empty_folder = Some(current_folder_as_str.to_string());
}
return; return;
}; };
dir_result.push(next_folder.clone()); dir_result.push(next_folder.clone());
folder_entries_list.push(( folder_entries_list.push(FolderEntry {
next_folder.to_string_lossy().to_string(), path: next_folder,
FolderEntry { parent_path: Some(current_folder_as_str.to_string()),
path: next_folder, is_empty: FolderEmptiness::Maybe,
parent_path: Some(parent_folder_str), modified_date: get_modified_time(&metadata, warnings, current_folder, true),
is_empty: FolderEmptiness::Maybe, });
modified_date: get_modified_time(&metadata, warnings, current_folder, true),
},
));
} }
#[fun_time(message = "delete_files", level = "debug")] #[fun_time(message = "delete_files", level = "debug")]

View file

@ -1,5 +1,4 @@
use std::fs; use std::fs;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -73,7 +72,7 @@ impl InvalidSymlinks {
#[fun_time(message = "find_invalid_links", level = "info")] #[fun_time(message = "find_invalid_links", level = "info")]
pub fn find_invalid_links(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_invalid_links(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;

View file

@ -2,14 +2,12 @@ use std::cmp::max;
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::{mem, panic}; use std::{mem, panic};
use anyhow::Context; use anyhow::Context;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use fun_time::fun_time; use fun_time::fun_time;
use humansize::{format_size, BINARY}; use humansize::{format_size, BINARY};
@ -138,7 +136,7 @@ impl SameMusic {
#[fun_time(message = "find_same_music", level = "info")] #[fun_time(message = "find_same_music", level = "info")]
pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
@ -177,8 +175,8 @@ impl SameMusic {
#[fun_time(message = "check_files", level = "debug")] #[fun_time(message = "check_files", level = "debug")]
fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool { fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool {
self.common_data.allowed_extensions.set_and_validate_extensions(AUDIO_FILES_EXTENSIONS); self.common_data.extensions.set_and_validate_allowed_extensions(AUDIO_FILES_EXTENSIONS);
if !self.common_data.allowed_extensions.set_any_extensions() { if !self.common_data.extensions.set_any_extensions() {
return true; return true;
} }

View file

@ -6,7 +6,6 @@ use std::time::SystemTime;
use std::{mem, panic}; use std::{mem, panic};
use bk_tree::BKTree; use bk_tree::BKTree;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use fun_time::fun_time; use fun_time::fun_time;
use humansize::{format_size, BINARY}; use humansize::{format_size, BINARY};
@ -41,7 +40,8 @@ pub const SIMILAR_VALUES: [[u32; 6]; 4] = [
pub struct ImagesEntry { pub struct ImagesEntry {
pub path: PathBuf, pub path: PathBuf,
pub size: u64, pub size: u64,
pub dimensions: String, pub width: u32,
pub height: u32,
pub modified_date: u64, pub modified_date: u64,
pub hash: ImHash, pub hash: ImHash,
pub similarity: u32, pub similarity: u32,
@ -66,7 +66,8 @@ impl FileEntry {
path: self.path, path: self.path,
modified_date: self.modified_date, modified_date: self.modified_date,
dimensions: String::new(), width: 0,
height: 0,
hash: Vec::new(), hash: Vec::new(),
similarity: 0, similarity: 0,
image_type: ImageType::Unknown, image_type: ImageType::Unknown,
@ -152,7 +153,7 @@ impl SimilarImages {
#[fun_time(message = "find_similar_images", level = "info")] #[fun_time(message = "find_similar_images", level = "info")]
pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty();
if !self.check_for_similar_images(stop_receiver, progress_sender) { if !self.check_for_similar_images(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
@ -174,14 +175,14 @@ impl SimilarImages {
fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool { fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool {
if cfg!(feature = "heif") { if cfg!(feature = "heif") {
self.common_data self.common_data
.allowed_extensions .extensions
.set_and_validate_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS, HEIC_EXTENSIONS].concat()); .set_and_validate_allowed_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS, HEIC_EXTENSIONS].concat());
} else { } else {
self.common_data self.common_data
.allowed_extensions .extensions
.set_and_validate_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS].concat()); .set_and_validate_allowed_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS].concat());
} }
if !self.common_data.allowed_extensions.set_any_extensions() { if !self.common_data.extensions.set_any_extensions() {
return true; return true;
} }
@ -294,7 +295,7 @@ impl SimilarImages {
.while_some() .while_some()
.filter_map(|e| e) .filter_map(|e| e)
.collect::<Vec<ImagesEntry>>(); .collect::<Vec<ImagesEntry>>();
debug!("hash_images - end hashing images"); debug!("hash_images - end hashing {} images", vec_file_entry.len());
send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle);
@ -396,7 +397,8 @@ impl SimilarImages {
let dimensions = img.dimensions(); let dimensions = img.dimensions();
file_entry.dimensions = format!("{}x{}", dimensions.0, dimensions.1); file_entry.width = dimensions.0;
file_entry.height = dimensions.1;
let hasher_config = HasherConfig::new() let hasher_config = HasherConfig::new()
.hash_size(self.hash_size as u32, self.hash_size as u32) .hash_size(self.hash_size as u32, self.hash_size as u32)
@ -460,54 +462,21 @@ impl SimilarImages {
collected_similar_images: &mut HashMap<ImHash, Vec<ImagesEntry>>, collected_similar_images: &mut HashMap<ImHash, Vec<ImagesEntry>>,
hashes_similarity: HashMap<ImHash, (ImHash, u32)>, hashes_similarity: HashMap<ImHash, (ImHash, u32)>,
) { ) {
if self.common_data.use_reference_folders { // Collecting results to vector
// This is same step as without reference folders, but also checks if children are inside/outside reference directories, because may happen, that one file is inside reference folder and other outside for (parent_hash, child_number) in hashes_parents {
// If hash contains other hasher OR multiple images are available for checked hash
if child_number > 0 || hashes_with_multiple_images.contains(&parent_hash) {
let vec_fe = all_hashed_images[&parent_hash].clone();
collected_similar_images.insert(parent_hash.clone(), vec_fe);
}
}
// Collecting results to vector for (child_hash, (parent_hash, similarity)) in hashes_similarity {
for (parent_hash, child_number) in hashes_parents { let mut vec_fe = all_hashed_images[&child_hash].clone();
// If hash contains other hasher OR multiple images are available for checked hash for fe in &mut vec_fe {
if child_number > 0 || hashes_with_multiple_images.contains(&parent_hash) { fe.similarity = similarity;
let vec_fe = all_hashed_images
.get(&parent_hash)
.unwrap()
.iter()
.filter(|e| is_in_reference_folder(&self.common_data.directories.reference_directories, &e.path))
.cloned()
.collect();
collected_similar_images.insert(parent_hash.clone(), vec_fe);
}
}
for (child_hash, (parent_hash, similarity)) in hashes_similarity {
let mut vec_fe: Vec<_> = all_hashed_images
.get(&child_hash)
.unwrap()
.iter()
.filter(|e| !is_in_reference_folder(&self.common_data.directories.reference_directories, &e.path))
.cloned()
.collect();
for fe in &mut vec_fe {
fe.similarity = similarity;
}
collected_similar_images.get_mut(&parent_hash).unwrap().append(&mut vec_fe);
}
} else {
// Collecting results to vector
for (parent_hash, child_number) in hashes_parents {
// If hash contains other hasher OR multiple images are available for checked hash
if child_number > 0 || hashes_with_multiple_images.contains(&parent_hash) {
let vec_fe = all_hashed_images.get(&parent_hash).unwrap().clone();
collected_similar_images.insert(parent_hash.clone(), vec_fe);
}
}
for (child_hash, (parent_hash, similarity)) in hashes_similarity {
let mut vec_fe = all_hashed_images.get(&child_hash).unwrap().clone();
for fe in &mut vec_fe {
fe.similarity = similarity;
}
collected_similar_images.get_mut(&parent_hash).unwrap().append(&mut vec_fe);
} }
collected_similar_images.get_mut(&parent_hash).unwrap().append(&mut vec_fe);
} }
} }
@ -773,7 +742,7 @@ impl SimilarImages {
} }
} }
} }
assert!(!found, "Found Invalid entries, verify errors before"); // TODO crashes with empty result with reference folder, verify why assert!(!found, "Found Invalid entries, verify errors before");
} }
fn delete_files(&mut self) { fn delete_files(&mut self) {
@ -818,9 +787,10 @@ impl PrintResults for SimilarImages {
for file_entry in struct_similar { for file_entry in struct_similar {
writeln!( writeln!(
writer, writer,
"{:?} - {} - {} - {}", "{:?} - {}x{} - {} - {}",
file_entry.path, file_entry.path,
file_entry.dimensions, file_entry.width,
file_entry.height,
format_size(file_entry.size, BINARY), format_size(file_entry.size, BINARY),
get_string_from_similarity(&file_entry.similarity, self.hash_size) get_string_from_similarity(&file_entry.similarity, self.hash_size)
)?; )?;
@ -835,18 +805,20 @@ impl PrintResults for SimilarImages {
writeln!(writer)?; writeln!(writer)?;
writeln!( writeln!(
writer, writer,
"{:?} - {} - {} - {}", "{:?} - {}x{} - {} - {}",
file_entry.path, file_entry.path,
file_entry.dimensions, file_entry.width,
file_entry.height,
format_size(file_entry.size, BINARY), format_size(file_entry.size, BINARY),
get_string_from_similarity(&file_entry.similarity, self.hash_size) get_string_from_similarity(&file_entry.similarity, self.hash_size)
)?; )?;
for file_entry in vec_file_entry { for file_entry in vec_file_entry {
writeln!( writeln!(
writer, writer,
"{:?} - {} - {} - {}", "{:?} - {}x{} - {} - {}",
file_entry.path, file_entry.path,
file_entry.dimensions, file_entry.width,
file_entry.height,
format_size(file_entry.size, BINARY), format_size(file_entry.size, BINARY),
get_string_from_similarity(&file_entry.similarity, self.hash_size) get_string_from_similarity(&file_entry.similarity, self.hash_size)
)?; )?;
@ -1007,12 +979,12 @@ fn debug_check_for_duplicated_things(
for (hash, number_of_children) in hashes_parents { for (hash, number_of_children) in hashes_parents {
if *number_of_children > 0 { if *number_of_children > 0 {
if hashmap_hashes.contains(hash) { if hashmap_hashes.contains(hash) {
println!("------1--HASH--{} {:?}", numm, all_hashed_images.get(hash).unwrap()); println!("------1--HASH--{} {:?}", numm, all_hashed_images[hash]);
found_broken_thing = true; found_broken_thing = true;
} }
hashmap_hashes.insert((*hash).clone()); hashmap_hashes.insert((*hash).clone());
for i in all_hashed_images.get(hash).unwrap() { for i in &all_hashed_images[hash] {
let name = i.path.to_string_lossy().to_string(); let name = i.path.to_string_lossy().to_string();
if hashmap_names.contains(&name) { if hashmap_names.contains(&name) {
println!("------1--NAME--{numm} {name:?}"); println!("------1--NAME--{numm} {name:?}");
@ -1024,12 +996,12 @@ fn debug_check_for_duplicated_things(
} }
for hash in hashes_similarity.keys() { for hash in hashes_similarity.keys() {
if hashmap_hashes.contains(hash) { if hashmap_hashes.contains(hash) {
println!("------2--HASH--{} {:?}", numm, all_hashed_images.get(hash).unwrap()); println!("------2--HASH--{} {:?}", numm, all_hashed_images[hash]);
found_broken_thing = true; found_broken_thing = true;
} }
hashmap_hashes.insert((*hash).clone()); hashmap_hashes.insert((*hash).clone());
for i in all_hashed_images.get(hash).unwrap() { for i in &all_hashed_images[hash] {
let name = i.path.to_string_lossy().to_string(); let name = i.path.to_string_lossy().to_string();
if hashmap_names.contains(&name) { if hashmap_names.contains(&name) {
println!("------2--NAME--{numm} {name:?}"); println!("------2--NAME--{numm} {name:?}");
@ -1103,9 +1075,9 @@ mod tests {
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use crate::common_dir_traversal::ToolType;
use bk_tree::BKTree; use bk_tree::BKTree;
use crate::common_dir_traversal::ToolType;
use crate::common_directory::Directories; use crate::common_directory::Directories;
use crate::common_tool::CommonToolData; use crate::common_tool::CommonToolData;
use crate::similar_images::{Hamming, ImHash, ImageType, ImagesEntry, SimilarImages}; use crate::similar_images::{Hamming, ImHash, ImageType, ImagesEntry, SimilarImages};
@ -1201,34 +1173,6 @@ mod tests {
} }
} }
// TODO this not works yet,
// Need to find a way to
// #[test]
// fn test_similar_similarity() {
// for _ in 0..100 {
// let mut similar_images = SimilarImages {
// similarity: 10,
// common_data: CommonToolData {
// tool_type: ToolType::SimilarImages,
// ..Default::default()
// },
// use_reference_folders: false,
// ..Default::default()
// };
//
// let fe1 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 0b0000_0001], "abc.txt");
// let fe2 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 0b0000_0010], "bcd.txt");
// let fe3 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 0b0000_0100], "rrd.txt");
// let fe4 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 0b0111_1111], "rdd.txt");
//
// add_hashes(&mut similar_images.image_hashes, vec![fe1, fe2, fe3, fe4]);
//
// similar_images.find_similar_hashes(None, None);
// assert_eq!(similar_images.get_similar_images().len(), 1);
// assert_eq!(similar_images.get_similar_images()[0].len(), 4);
// }
// }
#[test] #[test]
fn test_simple_referenced_same_group() { fn test_simple_referenced_same_group() {
for _ in 0..100 { for _ in 0..100 {
@ -1473,6 +1417,35 @@ mod tests {
} }
} }
#[test]
fn test_reference_same() {
for _ in 0..100 {
let mut similar_images = SimilarImages {
similarity: 1,
common_data: CommonToolData {
tool_type: ToolType::SimilarImages,
directories: Directories {
reference_directories: vec![PathBuf::from("/home/rr/")],
..Default::default()
},
use_reference_folders: true,
..Default::default()
},
..Default::default()
};
let fe1 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 1], "/home/rr/abc.txt");
let fe2 = create_random_file_entry(vec![1, 1, 1, 1, 1, 1, 1, 1], "/home/kk/bcd.txt");
add_hashes(&mut similar_images.image_hashes, vec![fe1, fe2]);
similar_images.find_similar_hashes(None, None);
let res = similar_images.get_similar_images_referenced();
assert_eq!(res.len(), 1);
assert_eq!(res[0].1.len(), 1);
}
}
#[test] #[test]
fn test_reference_union() { fn test_reference_union() {
for _ in 0..100 { for _ in 0..100 {
@ -1544,7 +1517,8 @@ mod tests {
ImagesEntry { ImagesEntry {
path: PathBuf::from(name.to_string()), path: PathBuf::from(name.to_string()),
size: 0, size: 0,
dimensions: String::new(), width: 100,
height: 100,
modified_date: 0, modified_date: 0,
hash, hash,
similarity: 0, similarity: 0,

View file

@ -20,7 +20,6 @@ use crate::common_dir_traversal::{inode, take_1_per_inode, CheckingMethod, DirTr
use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_tool::{CommonData, CommonToolData, DeleteMethod};
use crate::common_traits::{DebugPrint, PrintResults, ResultEntry}; use crate::common_traits::{DebugPrint, PrintResults, ResultEntry};
use crate::flc; use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
pub const MAX_TOLERANCE: i32 = 20; pub const MAX_TOLERANCE: i32 = 20;
@ -123,12 +122,12 @@ impl SimilarVideos {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
self.common_data.text_messages.errors.push(flc!("core_ffmpeg_not_found_windows")); self.common_data.text_messages.errors.push(flc!("core_ffmpeg_not_found_windows"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
self.common_data.text_messages.errors.push(flc!( self.common_data
"core_ffmpeg_missing_in_snap", .text_messages
generate_translation_hashmap(vec![("url", "https://github.com/snapcrafters/ffmpeg/issues/73".to_string())]) .errors
)); .push(flc!("core_ffmpeg_missing_in_snap", url = "https://github.com/snapcrafters/ffmpeg/issues/73"));
} else { } else {
self.optimize_dirs_before_start(); self.prepare_items();
self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty();
if !self.check_for_similar_videos(stop_receiver, progress_sender) { if !self.check_for_similar_videos(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
@ -145,8 +144,8 @@ impl SimilarVideos {
// #[fun_time(message = "check_for_similar_videos", level = "debug")] // #[fun_time(message = "check_for_similar_videos", level = "debug")]
fn check_for_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool { fn check_for_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) -> bool {
self.common_data.allowed_extensions.set_and_validate_extensions(VIDEO_FILES_EXTENSIONS); self.common_data.extensions.set_and_validate_allowed_extensions(VIDEO_FILES_EXTENSIONS);
if !self.common_data.allowed_extensions.set_any_extensions() { if !self.common_data.extensions.set_any_extensions() {
return true; return true;
} }

View file

@ -1,7 +1,6 @@
use std::fs; use std::fs;
use std::fs::DirEntry; use std::fs::DirEntry;
use std::io::prelude::*; use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -60,7 +59,7 @@ impl Temporary {
#[fun_time(message = "find_temporary_files", level = "info")] #[fun_time(message = "find_temporary_files", level = "info")]
pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) { pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&Sender<ProgressData>>) {
self.optimize_dirs_before_start(); self.prepare_items();
if !self.check_files(stop_receiver, progress_sender) { if !self.check_files(stop_receiver, progress_sender) {
self.common_data.stopped_search = true; self.common_data.stopped_search = true;
return; return;

View file

@ -3,19 +3,19 @@ name = "czkawka_gui"
version = "6.1.0" version = "6.1.0"
authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"] authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.72.1" rust-version = "1.74.0"
description = "GTK frontend of Czkawka" description = "GTK frontend of Czkawka"
license = "MIT" license = "MIT"
homepage = "https://github.com/qarmin/czkawka" homepage = "https://github.com/qarmin/czkawka"
repository = "https://github.com/qarmin/czkawka" repository = "https://github.com/qarmin/czkawka"
[dependencies] [dependencies]
gdk4 = "0.7" gdk4 = "0.8"
glib = "0.18" glib = "0.19"
gtk4 = { version = "0.7", default-features = false, features = ["v4_6"] } gtk4 = { version = "0.8", default-features = false, features = ["v4_6"] }
humansize = "2.1" humansize = "2.1"
chrono = "0.4.31" chrono = "0.4.34"
# Used for sending stop signal across threads # Used for sending stop signal across threads
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
@ -36,14 +36,14 @@ regex = "1.10"
image_hasher = "1.2" image_hasher = "1.2"
# Move files to trash # Move files to trash
trash = "3.2" trash = "3.3"
# For moving files(why std::fs doesn't have such features?) # For moving files(why std::fs doesn't have such features?)
fs_extra = "1.3" fs_extra = "1.3"
# Language # Language
i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] } i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.7" i18n-embed-fl = "0.8"
rust-embed = { version = "8.2", features = ["debug-embed"] } rust-embed = { version = "8.2", features = ["debug-embed"] }
once_cell = "1.19" once_cell = "1.19"

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 Rafał Mikrut Copyright (c) 2020-2024 Rafał Mikrut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -36,7 +36,7 @@ Compiling the gui is harder than compiling cli or core, because it uses gtk4 whi
### Requirements ### Requirements
| Program | Minimal version | | Program | Minimal version |
|:---------:|:-----------------:| |:---------:|:-----------------:|
| Rust | 1.72.1 | | Rust | 1.74.0 |
| GTK | 4.6 | | GTK | 4.6 |
### Linux (Ubuntu, but on other OS should work similar) ### Linux (Ubuntu, but on other OS should work similar)

View file

@ -192,12 +192,18 @@ upper_allowed_extensions_tooltip =
Usage example ".exe, IMAGE, VIDEO, .rar, 7z" - this means that images (e.g. jpg, png), videos (e.g. avi, mp4), exe, rar, and 7z files will be scanned. Usage example ".exe, IMAGE, VIDEO, .rar, 7z" - this means that images (e.g. jpg, png), videos (e.g. avi, mp4), exe, rar, and 7z files will be scanned.
upper_excluded_extensions_tooltip =
List of disabled files which will be ignored in scan.
When using both allowed and disabled extensions, this one has higher priority, so file will not be checked.
upper_excluded_items_tooltip = upper_excluded_items_tooltip =
Excluded items must contain * wildcard and should be separated by commas. Excluded items must contain * wildcard and should be separated by commas.
This is slower than Excluded Directories, so use it carefully. This is slower than Excluded Directories, so use it carefully.
upper_excluded_items = Excluded Items: upper_excluded_items = Excluded Items:
upper_allowed_extensions = Allowed Extensions: upper_allowed_extensions = Allowed Extensions:
upper_excluded_extensions = Disabled Extensions:
# Popovers # Popovers

View file

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
enable-background="new 0 0 512 512" enable-background="new 0 0 512 512"
viewBox="0 0 512 512" viewBox="0 0 512 512"
version="1.1" version="1.1"
id="svg16" id="svg16"
sodipodi:docname="czk_hide_down.svg" sodipodi:docname="czk_hide_down.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"> >
<defs <defs
id="defs20" /> id="defs20" />
<sodipodi:namedview <sodipodi:namedview

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
enable-background="new 0 0 512 512" enable-background="new 0 0 512 512"
viewBox="0 0 512 512" viewBox="0 0 512 512"
version="1.1" version="1.1"
id="svg16" id="svg16"
sodipodi:docname="czk_hide_up.svg" sodipodi:docname="czk_hide_up.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"> >
<defs <defs
id="defs20" /> id="defs20" />
<sodipodi:namedview <sodipodi:namedview

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="iso-8859-1"?> <?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"> viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<polygon style="fill:#298983;" points="358.206,345.663 357.688,227.522 292.942,227.806 293.462,345.948 325.975,378.178 "/> <polygon style="fill:#298983;" points="358.206,345.663 357.688,227.522 292.942,227.806 293.462,345.948 325.975,378.178 "/>
<rect x="359.234" y="336.67" transform="matrix(0.7102 0.704 -0.704 0.7102 381.6591 -165.2999)" style="fill:#A5F2E5;" width="64.748" height="88.511"/> <rect x="359.234" y="336.67" transform="matrix(0.7102 0.704 -0.704 0.7102 381.6591 -165.2999)" style="fill:#A5F2E5;" width="64.748" height="88.511"/>
<path d="M462.868,366.489l-57.464-56.96c-1.514-1.501-3.559-2.342-5.69-2.342c-0.012,0-0.024,0-0.036,0 <path d="M462.868,366.489l-57.464-56.96c-1.514-1.501-3.559-2.342-5.69-2.342c-0.012,0-0.024,0-0.036,0

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -1,10 +1,11 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use crossbeam_channel::Receiver;
use fun_time::fun_time; use fun_time::fun_time;
use glib::Receiver;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{Entry, ListStore, TextView, TreeView, Widget}; use gtk4::{Entry, ListStore, TextView, TreeView, Widget};
use humansize::{format_size, BINARY}; use humansize::{format_size, BINARY};
@ -21,7 +22,6 @@ use czkawka_core::duplicate::DuplicateFinder;
use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::EmptyFolder; use czkawka_core::empty_folder::EmptyFolder;
use czkawka_core::invalid_symlinks::InvalidSymlinks; use czkawka_core::invalid_symlinks::InvalidSymlinks;
use czkawka_core::localizer_core::generate_translation_hashmap;
use czkawka_core::same_music::{MusicSimilarity, SameMusic}; use czkawka_core::same_music::{MusicSimilarity, SameMusic};
use czkawka_core::similar_images; use czkawka_core::similar_images;
use czkawka_core::similar_images::{ImagesEntry, SimilarImages}; use czkawka_core::similar_images::{ImagesEntry, SimilarImages};
@ -36,7 +36,7 @@ use crate::notebook_enums::*;
use crate::notebook_info::NOTEBOOKS_INFO; use crate::notebook_info::NOTEBOOKS_INFO;
use crate::opening_selecting_records::*; use crate::opening_selecting_records::*;
pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<Message>) { pub fn connect_compute_results(gui_data: &GuiData, result_receiver: Receiver<Message>) {
let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone(); let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone();
let buttons_search = gui_data.bottom_buttons.buttons_search.clone(); let buttons_search = gui_data.bottom_buttons.buttons_search.clone();
let notebook_main = gui_data.main_notebook.notebook_main.clone(); let notebook_main = gui_data.main_notebook.notebook_main.clone();
@ -76,163 +76,171 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
let main_context = glib::MainContext::default(); let main_context = glib::MainContext::default();
let _guard = main_context.acquire().unwrap(); let _guard = main_context.acquire().unwrap();
glib_stop_receiver.attach(None, move |msg| { glib::spawn_future_local(async move {
buttons_search.show(); loop {
loop {
let msg = result_receiver.try_recv();
if let Ok(msg) = msg {
buttons_search.show();
notebook_main.set_sensitive(true); notebook_main.set_sensitive(true);
notebook_upper.set_sensitive(true); notebook_upper.set_sensitive(true);
button_settings.set_sensitive(true); button_settings.set_sensitive(true);
button_app_info.set_sensitive(true); button_app_info.set_sensitive(true);
window_progress.hide(); window_progress.hide();
taskbar_state.borrow().hide(); taskbar_state.borrow().hide();
let hash_size_index = combo_box_image_hash_size.active().unwrap() as usize; let hash_size_index = combo_box_image_hash_size.active().unwrap() as usize;
let hash_size = IMAGES_HASH_SIZE_COMBO_BOX[hash_size_index] as u8; let hash_size = IMAGES_HASH_SIZE_COMBO_BOX[hash_size_index] as u8;
match msg { match msg {
Message::Duplicates(df) => { Message::Duplicates(df) => {
computer_duplicate_finder( compute_duplicate_finder(
df, df,
&entry_info, &entry_info,
&tree_view_duplicate_finder, &tree_view_duplicate_finder,
&text_view_errors, &text_view_errors,
&shared_duplication_state, &shared_duplication_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::EmptyFolders(ef) => { Message::EmptyFolders(ef) => {
computer_empty_folders( compute_empty_folders(
ef, ef,
&entry_info, &entry_info,
&tree_view_empty_folder_finder, &tree_view_empty_folder_finder,
&text_view_errors, &text_view_errors,
&shared_empty_folders_state, &shared_empty_folders_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::EmptyFiles(vf) => { Message::EmptyFiles(vf) => {
computer_empty_files( compute_empty_files(
vf, vf,
&entry_info, &entry_info,
&tree_view_empty_files_finder, &tree_view_empty_files_finder,
&text_view_errors, &text_view_errors,
&shared_empty_files_state, &shared_empty_files_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::BigFiles(bf) => { Message::BigFiles(bf) => {
computer_big_files( compute_big_files(
bf, bf,
&entry_info, &entry_info,
&tree_view_big_files_finder, &tree_view_big_files_finder,
&text_view_errors, &text_view_errors,
&shared_big_files_state, &shared_big_files_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::Temporary(tf) => { Message::Temporary(tf) => {
computer_temporary_files( compute_temporary_files(
tf, tf,
&entry_info, &entry_info,
&tree_view_temporary_files_finder, &tree_view_temporary_files_finder,
&text_view_errors, &text_view_errors,
&shared_temporary_files_state, &shared_temporary_files_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::SimilarImages(sf) => { Message::SimilarImages(sf) => {
computer_similar_images( compute_similar_images(
sf, sf,
&entry_info, &entry_info,
&tree_view_similar_images_finder, &tree_view_similar_images_finder,
&text_view_errors, &text_view_errors,
&shared_similar_images_state, &shared_similar_images_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
hash_size, hash_size,
); );
} }
Message::SimilarVideos(ff) => { Message::SimilarVideos(ff) => {
computer_similar_videos( compute_similar_videos(
ff, ff,
&entry_info, &entry_info,
&tree_view_similar_videos_finder, &tree_view_similar_videos_finder,
&text_view_errors, &text_view_errors,
&shared_similar_videos_state, &shared_similar_videos_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::SameMusic(mf) => { Message::SameMusic(mf) => {
computer_same_music( compute_same_music(
mf, mf,
&entry_info, &entry_info,
&tree_view_same_music_finder, &tree_view_same_music_finder,
&text_view_errors, &text_view_errors,
&shared_same_music_state, &shared_same_music_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::InvalidSymlinks(ifs) => { Message::InvalidSymlinks(ifs) => {
computer_invalid_symlinks( compute_invalid_symlinks(
ifs, ifs,
&entry_info, &entry_info,
&tree_view_invalid_symlinks, &tree_view_invalid_symlinks,
&text_view_errors, &text_view_errors,
&shared_same_invalid_symlinks, &shared_same_invalid_symlinks,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::BrokenFiles(br) => { Message::BrokenFiles(br) => {
computer_broken_files( compute_broken_files(
br, br,
&entry_info, &entry_info,
&tree_view_broken_files, &tree_view_broken_files,
&text_view_errors, &text_view_errors,
&shared_broken_files_state, &shared_broken_files_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
} }
Message::BadExtensions(be) => { Message::BadExtensions(be) => {
computer_bad_extensions( compute_bad_extensions(
be, be,
&entry_info, &entry_info,
&tree_view_bad_extensions, &tree_view_bad_extensions,
&text_view_errors, &text_view_errors,
&shared_bad_extensions_state, &shared_bad_extensions_state,
&shared_buttons, &shared_buttons,
&buttons_array, &buttons_array,
&buttons_names, &buttons_names,
); );
}
}
} else {
break;
}
} }
glib::timeout_future(Duration::from_millis(300)).await;
} }
// Returning false here would close the receiver and have senders fail
glib::ControlFlow::Continue
}); });
} }
#[fun_time(message = "computer_bad_extensions", level = "debug")] #[fun_time(message = "compute_bad_extensions", level = "debug")]
fn computer_bad_extensions( fn compute_bad_extensions(
be: BadExtensions, be: BadExtensions,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -250,13 +258,7 @@ fn computer_bad_extensions(
let text_messages = be.get_text_messages(); let text_messages = be.get_text_messages();
let bad_extensions_number: usize = information.number_of_files_with_bad_extension; let bad_extensions_number: usize = information.number_of_files_with_bad_extension;
entry_info.set_text( entry_info.set_text(flg!("compute_found_bad_extensions", number_files = bad_extensions_number).as_str());
flg!(
"compute_found_bad_extensions",
generate_translation_hashmap(vec![("number_files", bad_extensions_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -302,8 +304,8 @@ fn computer_bad_extensions(
} }
} }
#[fun_time(message = "computer_broken_files", level = "debug")] #[fun_time(message = "compute_broken_files", level = "debug")]
fn computer_broken_files( fn compute_broken_files(
br: BrokenFiles, br: BrokenFiles,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -322,13 +324,7 @@ fn computer_broken_files(
let broken_files_number: usize = information.number_of_broken_files; let broken_files_number: usize = information.number_of_broken_files;
entry_info.set_text( entry_info.set_text(flg!("compute_found_broken_files", number_files = broken_files_number).as_str());
flg!(
"compute_found_broken_files",
generate_translation_hashmap(vec![("number_files", broken_files_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -373,8 +369,8 @@ fn computer_broken_files(
} }
} }
#[fun_time(message = "computer_invalid_symlinks", level = "debug")] #[fun_time(message = "compute_invalid_symlinks", level = "debug")]
fn computer_invalid_symlinks( fn compute_invalid_symlinks(
ifs: InvalidSymlinks, ifs: InvalidSymlinks,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -393,13 +389,7 @@ fn computer_invalid_symlinks(
let invalid_symlinks: usize = information.number_of_invalid_symlinks; let invalid_symlinks: usize = information.number_of_invalid_symlinks;
entry_info.set_text( entry_info.set_text(flg!("compute_found_invalid_symlinks", number_files = invalid_symlinks).as_str());
flg!(
"compute_found_invalid_symlinks",
generate_translation_hashmap(vec![("number_files", invalid_symlinks.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -445,8 +435,8 @@ fn computer_invalid_symlinks(
} }
} }
#[fun_time(message = "computer_same_music", level = "debug")] #[fun_time(message = "compute_same_music", level = "debug")]
fn computer_same_music( fn compute_same_music(
mf: SameMusic, mf: SameMusic,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -473,10 +463,8 @@ fn computer_same_music(
entry_info.set_text( entry_info.set_text(
flg!( flg!(
"compute_found_music", "compute_found_music",
generate_translation_hashmap(vec![ number_files = information.number_of_duplicates,
("number_files", information.number_of_duplicates.to_string()), number_groups = information.number_of_groups
("number_groups", information.number_of_groups.to_string()),
])
) )
.as_str(), .as_str(),
); );
@ -614,8 +602,8 @@ fn computer_same_music(
} }
} }
#[fun_time(message = "computer_similar_videos", level = "debug")] #[fun_time(message = "compute_similar_videos", level = "debug")]
fn computer_similar_videos( fn compute_similar_videos(
ff: SimilarVideos, ff: SimilarVideos,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -640,10 +628,8 @@ fn computer_similar_videos(
entry_info.set_text( entry_info.set_text(
flg!( flg!(
"compute_found_videos", "compute_found_videos",
generate_translation_hashmap(vec![ number_files = information.number_of_duplicates,
("number_files", information.number_of_duplicates.to_string()), number_groups = information.number_of_groups
("number_groups", information.number_of_groups.to_string()),
])
) )
.as_str(), .as_str(),
); );
@ -710,8 +696,8 @@ fn computer_similar_videos(
} }
} }
#[fun_time(message = "computer_similar_images", level = "debug")] #[fun_time(message = "compute_similar_images", level = "debug")]
fn computer_similar_images( fn compute_similar_images(
sf: SimilarImages, sf: SimilarImages,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -738,10 +724,8 @@ fn computer_similar_images(
entry_info.set_text( entry_info.set_text(
flg!( flg!(
"compute_found_images", "compute_found_images",
generate_translation_hashmap(vec![ number_files = information.number_of_duplicates,
("number_files", information.number_of_duplicates.to_string()), number_groups = information.number_of_groups
("number_groups", information.number_of_groups.to_string()),
])
) )
.as_str(), .as_str(),
); );
@ -771,7 +755,7 @@ fn computer_similar_images(
&directory, &directory,
base_file_entry.size, base_file_entry.size,
base_file_entry.modified_date, base_file_entry.modified_date,
&base_file_entry.dimensions, &format!("{}x{}", base_file_entry.width, base_file_entry.height),
0, 0,
hash_size, hash_size,
true, true,
@ -785,7 +769,7 @@ fn computer_similar_images(
&directory, &directory,
file_entry.size, file_entry.size,
file_entry.modified_date, file_entry.modified_date,
&file_entry.dimensions, &format!("{}x{}", file_entry.width, file_entry.height),
file_entry.similarity, file_entry.similarity,
hash_size, hash_size,
false, false,
@ -815,7 +799,7 @@ fn computer_similar_images(
&directory, &directory,
file_entry.size, file_entry.size,
file_entry.modified_date, file_entry.modified_date,
&file_entry.dimensions, &format!("{}x{}", file_entry.width, file_entry.height),
file_entry.similarity, file_entry.similarity,
hash_size, hash_size,
false, false,
@ -843,8 +827,8 @@ fn computer_similar_images(
} }
} }
#[fun_time(message = "computer_temporary_files", level = "debug")] #[fun_time(message = "compute_temporary_files", level = "debug")]
fn computer_temporary_files( fn compute_temporary_files(
tf: Temporary, tf: Temporary,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -862,13 +846,7 @@ fn computer_temporary_files(
let text_messages = tf.get_text_messages(); let text_messages = tf.get_text_messages();
let temporary_files_number: usize = information.number_of_temporary_files; let temporary_files_number: usize = information.number_of_temporary_files;
entry_info.set_text( entry_info.set_text(flg!("compute_found_temporary_files", number_files = temporary_files_number).as_str());
flg!(
"compute_found_temporary_files",
generate_translation_hashmap(vec![("number_files", temporary_files_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -912,8 +890,8 @@ fn computer_temporary_files(
} }
} }
#[fun_time(message = "computer_big_files", level = "debug")] #[fun_time(message = "compute_big_files", level = "debug")]
fn computer_big_files( fn compute_big_files(
bf: BigFile, bf: BigFile,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -932,13 +910,7 @@ fn computer_big_files(
let biggest_files_number: usize = information.number_of_real_files; let biggest_files_number: usize = information.number_of_real_files;
entry_info.set_text( entry_info.set_text(flg!("compute_found_big_files", number_files = biggest_files_number).as_str());
flg!(
"compute_found_big_files",
generate_translation_hashmap(vec![("number_files", biggest_files_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -980,8 +952,8 @@ fn computer_big_files(
} }
} }
#[fun_time(message = "computer_empty_files", level = "debug")] #[fun_time(message = "compute_empty_files", level = "debug")]
fn computer_empty_files( fn compute_empty_files(
vf: EmptyFiles, vf: EmptyFiles,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -1000,13 +972,7 @@ fn computer_empty_files(
let empty_files_number: usize = information.number_of_empty_files; let empty_files_number: usize = information.number_of_empty_files;
entry_info.set_text( entry_info.set_text(flg!("compute_found_empty_files", number_files = empty_files_number).as_str());
flg!(
"compute_found_empty_files",
generate_translation_hashmap(vec![("number_files", empty_files_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -1047,8 +1013,8 @@ fn computer_empty_files(
} }
} }
#[fun_time(message = "computer_empty_folders", level = "debug")] #[fun_time(message = "compute_empty_folders", level = "debug")]
fn computer_empty_folders( fn compute_empty_folders(
ef: EmptyFolder, ef: EmptyFolder,
entry_info: &Entry, entry_info: &Entry,
tree_view: &TreeView, tree_view: &TreeView,
@ -1067,13 +1033,7 @@ fn computer_empty_folders(
let empty_folder_number: usize = information.number_of_empty_folders; let empty_folder_number: usize = information.number_of_empty_folders;
entry_info.set_text( entry_info.set_text(flg!("compute_found_empty_folders", number_files = empty_folder_number).as_str());
flg!(
"compute_found_empty_folders",
generate_translation_hashmap(vec![("number_files", empty_folder_number.to_string()),])
)
.as_str(),
);
// Create GUI // Create GUI
{ {
@ -1116,8 +1076,8 @@ fn computer_empty_folders(
} }
} }
#[fun_time(message = "computer_duplicate_finder", level = "debug")] #[fun_time(message = "compute_duplicate_finder", level = "debug")]
fn computer_duplicate_finder( fn compute_duplicate_finder(
df: DuplicateFinder, df: DuplicateFinder,
entry_info: &Entry, entry_info: &Entry,
tree_view_duplicate_finder: &TreeView, tree_view_duplicate_finder: &TreeView,
@ -1167,22 +1127,14 @@ fn computer_duplicate_finder(
_ => panic!(), _ => panic!(),
} }
if duplicates_size == 0 { if duplicates_size == 0 {
entry_info.set_text( entry_info.set_text(flg!("compute_found_duplicates_name", number_files = duplicates_number, number_groups = duplicates_group).as_str());
flg!(
"compute_found_duplicates_name",
generate_translation_hashmap(vec![("number_files", duplicates_number.to_string()), ("number_groups", duplicates_group.to_string())])
)
.as_str(),
);
} else { } else {
entry_info.set_text( entry_info.set_text(
flg!( flg!(
"compute_found_duplicates_hash_size", "compute_found_duplicates_hash_size",
generate_translation_hashmap(vec![ number_files = duplicates_number,
("number_files", duplicates_number.to_string()), number_groups = duplicates_group,
("number_groups", duplicates_group.to_string()), size = format_size(duplicates_size, BINARY)
("size", format_size(duplicates_size, BINARY))
])
) )
.as_str(), .as_str(),
); );

View file

@ -15,7 +15,6 @@ use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::{ use crate::help_functions::{
count_number_of_groups, get_all_direct_children, get_full_name_from_path_name, get_max_file_name, get_pixbuf_from_dynamic_image, resize_pixbuf_dimension, count_number_of_groups, get_all_direct_children, get_full_name_from_path_name, get_max_file_name, get_pixbuf_from_dynamic_image, resize_pixbuf_dimension,
}; };
use crate::localizer_core::generate_translation_hashmap;
use crate::notebook_info::{NotebookObject, NOTEBOOKS_INFO}; use crate::notebook_info::{NotebookObject, NOTEBOOKS_INFO};
const BIG_PREVIEW_SIZE: i32 = 600; const BIG_PREVIEW_SIZE: i32 = 600;
@ -303,11 +302,9 @@ fn populate_groups_at_start(
label_group_info.set_text( label_group_info.set_text(
flg!( flg!(
"compare_groups_number", "compare_groups_number",
generate_translation_hashmap(vec![ current_group = current_group,
("current_group", current_group.to_string()), all_groups = group_number,
("all_groups", group_number.to_string()), images_in_group = cache_all_images.len()
("images_in_group", cache_all_images.len().to_string())
])
) )
.as_str(), .as_str(),
); );

View file

@ -8,7 +8,6 @@ use gtk4::{Align, CheckButton, Dialog, Orientation, ResponseType, TextView};
use crate::flg; use crate::flg;
use crate::gui_structs::gui_data::GuiData; use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::*; use crate::help_functions::*;
use crate::localizer_core::generate_translation_hashmap;
use crate::notebook_enums::*; use crate::notebook_enums::*;
use crate::notebook_info::NOTEBOOKS_INFO; use crate::notebook_info::NOTEBOOKS_INFO;
@ -148,13 +147,11 @@ fn create_dialog_ask_for_deletion(window_main: &gtk4::Window, number_of_selected
let label: gtk4::Label = gtk4::Label::new(Some(&flg!("delete_question_label"))); let label: gtk4::Label = gtk4::Label::new(Some(&flg!("delete_question_label")));
let label2: gtk4::Label = match number_of_selected_groups { let label2: gtk4::Label = match number_of_selected_groups {
0 => gtk4::Label::new(Some(&flg!( 0 => gtk4::Label::new(Some(&flg!("delete_items_label", items = number_of_selected_items))),
"delete_items_label",
generate_translation_hashmap(vec![("items", number_of_selected_items.to_string())])
))),
_ => gtk4::Label::new(Some(&flg!( _ => gtk4::Label::new(Some(&flg!(
"delete_items_groups_label", "delete_items_groups_label",
generate_translation_hashmap(vec![("items", number_of_selected_items.to_string()), ("groups", number_of_selected_groups.to_string())]) items = number_of_selected_items,
groups = number_of_selected_groups
))), ))),
}; };
@ -368,10 +365,7 @@ pub fn empty_folder_remover(
} }
} }
if error_happened { if error_happened {
messages += &flg!( messages += &flg!("delete_folder_failed", dir = get_full_name_from_path_name(&path, &name));
"delete_folder_failed",
generate_translation_hashmap(vec![("dir", get_full_name_from_path_name(&path, &name))])
);
messages += "\n"; messages += "\n";
} }
} }
@ -425,11 +419,7 @@ pub fn basic_remove(
} }
Err(e) => { Err(e) => {
messages += flg!( messages += flg!("delete_file_failed", name = get_full_name_from_path_name(&path, &name), reason = e.to_string()).as_str();
"delete_file_failed",
generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &name)), ("reason", e.to_string())])
)
.as_str();
messages += "\n"; messages += "\n";
} }
} }
@ -439,11 +429,7 @@ pub fn basic_remove(
model.remove(&iter); model.remove(&iter);
} }
Err(e) => { Err(e) => {
messages += flg!( messages += flg!("delete_file_failed", name = get_full_name_from_path_name(&path, &name), reason = e.to_string()).as_str();
"delete_file_failed",
generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &name)), ("reason", e.to_string())])
)
.as_str();
messages += "\n"; messages += "\n";
} }
} }
@ -513,19 +499,11 @@ pub fn tree_remove(
for file_name in vec_file_name { for file_name in vec_file_name {
if !use_trash { if !use_trash {
if let Err(e) = fs::remove_file(get_full_name_from_path_name(&path, &file_name)) { if let Err(e) = fs::remove_file(get_full_name_from_path_name(&path, &file_name)) {
messages += flg!( messages += flg!("delete_file_failed", name = get_full_name_from_path_name(&path, &file_name), reason = e.to_string()).as_str();
"delete_file_failed",
generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &file_name)), ("reason", e.to_string())])
)
.as_str();
messages += "\n"; messages += "\n";
} }
} else if let Err(e) = trash::delete(get_full_name_from_path_name(&path, &file_name)) { } else if let Err(e) = trash::delete(get_full_name_from_path_name(&path, &file_name)) {
messages += flg!( messages += flg!("delete_file_failed", name = get_full_name_from_path_name(&path, &file_name), reason = e.to_string()).as_str();
"delete_file_failed",
generate_translation_hashmap(vec![("name", get_full_name_from_path_name(&path, &file_name)), ("reason", e.to_string())])
)
.as_str();
messages += "\n"; messages += "\n";
} }

View file

@ -9,7 +9,6 @@ use czkawka_core::duplicate::make_hard_link;
use crate::flg; use crate::flg;
use crate::gui_structs::gui_data::GuiData; use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::*; use crate::help_functions::*;
use crate::localizer_core::generate_translation_hashmap;
use crate::notebook_enums::*; use crate::notebook_enums::*;
use crate::notebook_info::NOTEBOOKS_INFO; use crate::notebook_info::NOTEBOOKS_INFO;
@ -211,42 +210,21 @@ fn hardlink_symlink(
for symhardlink_data in vec_symhardlink_data { for symhardlink_data in vec_symhardlink_data {
for file_to_symlink in symhardlink_data.files_to_symhardlink { for file_to_symlink in symhardlink_data.files_to_symhardlink {
if let Err(e) = fs::remove_file(&file_to_symlink) { if let Err(e) = fs::remove_file(&file_to_symlink) {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("delete_file_failed", name = file_to_symlink, reason = e.to_string()).as_str());
text_view_errors,
flg!(
"delete_file_failed",
generate_translation_hashmap(vec![("name", file_to_symlink.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
continue; continue;
}; };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
{ {
if let Err(e) = std::os::unix::fs::symlink(&symhardlink_data.original_data, &file_to_symlink) { if let Err(e) = std::os::unix::fs::symlink(&symhardlink_data.original_data, &file_to_symlink) {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("delete_file_failed", name = file_to_symlink, reason = e.to_string()).as_str());
text_view_errors,
flg!(
"delete_file_failed",
generate_translation_hashmap(vec![("name", file_to_symlink.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
continue; continue;
}; };
} }
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
{ {
if let Err(e) = std::os::windows::fs::symlink_file(&symhardlink_data.original_data, &file_to_symlink) { if let Err(e) = std::os::windows::fs::symlink_file(&symhardlink_data.original_data, &file_to_symlink) {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("delete_file_failed", name = file_to_symlink, reason = e.to_string()).as_str());
text_view_errors,
flg!(
"delete_file_failed",
generate_translation_hashmap(vec![("name", file_to_symlink.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
continue; continue;
}; };
} }

View file

@ -7,7 +7,6 @@ use gtk4::{ResponseType, TreePath};
use crate::flg; use crate::flg;
use crate::gui_structs::gui_data::GuiData; use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::*; use crate::help_functions::*;
use crate::localizer_core::generate_translation_hashmap;
use crate::notebook_enums::*; use crate::notebook_enums::*;
use crate::notebook_info::NOTEBOOKS_INFO; use crate::notebook_info::NOTEBOOKS_INFO;
@ -54,14 +53,7 @@ pub fn connect_button_move(gui_data: &GuiData) {
} }
if folders.len() != 1 { if folders.len() != 1 {
add_text_to_text_view( add_text_to_text_view(&text_view_errors, flg!("move_files_choose_more_than_1_path", path_number = folders.len()).as_str());
&text_view_errors,
flg!(
"move_files_choose_more_than_1_path",
generate_translation_hashmap(vec![("path_number", folders.len().to_string())])
)
.as_str(),
);
} else { } else {
let folder = folders[0].clone(); let folder = folders[0].clone();
if let Some(column_header) = nb_object.column_header { if let Some(column_header) = nb_object.column_header {
@ -201,13 +193,13 @@ fn move_files_common(
let destination_file = destination_folder.join(file_name); let destination_file = destination_folder.join(file_name);
if Path::new(&thing).is_dir() { if Path::new(&thing).is_dir() {
if let Err(e) = fs_extra::dir::move_dir(&thing, &destination_file, &CopyOptions::new()) { if let Err(e) = fs_extra::dir::move_dir(&thing, &destination_file, &CopyOptions::new()) {
messages += flg!("move_folder_failed", generate_translation_hashmap(vec![("name", thing), ("reason", e.to_string())])).as_str(); messages += flg!("move_folder_failed", name = thing, reason = e.to_string()).as_str();
messages += "\n"; messages += "\n";
continue 'next_result; continue 'next_result;
} }
} else { } else {
if let Err(e) = fs_extra::file::move_file(&thing, &destination_file, &fs_extra::file::CopyOptions::new()) { if let Err(e) = fs_extra::file::move_file(&thing, &destination_file, &fs_extra::file::CopyOptions::new()) {
messages += flg!("move_file_failed", generate_translation_hashmap(vec![("name", thing), ("reason", e.to_string())])).as_str(); messages += flg!("move_file_failed", name = thing, reason = e.to_string()).as_str();
messages += "\n"; messages += "\n";
continue 'next_result; continue 'next_result;
@ -217,13 +209,7 @@ fn move_files_common(
moved_files += 1; moved_files += 1;
} }
entry_info.set_text( entry_info.set_text(flg!("move_stats", num_files = moved_files, all_files = selected_rows.len()).as_str());
flg!(
"move_stats",
generate_translation_hashmap(vec![("num_files", moved_files.to_string()), ("all_files", selected_rows.len().to_string())])
)
.as_str(),
);
text_view_errors.buffer().set_text(messages.as_str()); text_view_errors.buffer().set_text(messages.as_str());
} }

View file

@ -11,7 +11,6 @@ use czkawka_core::common_traits::PrintResults;
use crate::flg; use crate::flg;
use crate::gui_structs::gui_data::GuiData; use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::BottomButtonsEnum; use crate::help_functions::BottomButtonsEnum;
use crate::localizer_core::generate_translation_hashmap;
use crate::notebook_enums::*; use crate::notebook_enums::*;
pub fn connect_button_save(gui_data: &GuiData) { pub fn connect_button_save(gui_data: &GuiData) {
@ -76,7 +75,7 @@ fn post_save_things(
buttons_save: &Button, buttons_save: &Button,
current_path: String, current_path: String,
) { ) {
entry_info.set_text(&flg!("save_results_to_file", generate_translation_hashmap(vec![("name", current_path),]))); entry_info.set_text(&flg!("save_results_to_file", name = current_path));
// Set state // Set state
{ {
buttons_save.hide(); buttons_save.hide();

View file

@ -5,7 +5,6 @@ use std::thread;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use fun_time::fun_time; use fun_time::fun_time;
use glib::Sender as glibSender;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::Grid; use gtk4::Grid;
@ -35,10 +34,10 @@ use crate::taskbar_progress::tbp_flags::TBPF_NOPROGRESS;
use crate::{flg, DEFAULT_MAXIMAL_FILE_SIZE, DEFAULT_MINIMAL_CACHE_SIZE, DEFAULT_MINIMAL_FILE_SIZE}; use crate::{flg, DEFAULT_MAXIMAL_FILE_SIZE, DEFAULT_MINIMAL_CACHE_SIZE, DEFAULT_MINIMAL_FILE_SIZE};
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn connect_button_search(gui_data: &GuiData, glib_stop_sender: glibSender<Message>, progress_sender: Sender<ProgressData>) { pub fn connect_button_search(gui_data: &GuiData, result_sender: Sender<Message>, progress_sender: Sender<ProgressData>) {
let buttons_array = gui_data.bottom_buttons.buttons_array.clone(); let buttons_array = gui_data.bottom_buttons.buttons_array.clone();
let buttons_search_clone = gui_data.bottom_buttons.buttons_search.clone(); let buttons_search_clone = gui_data.bottom_buttons.buttons_search.clone();
let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone(); let grid_progress = gui_data.progress_window.grid_progress.clone();
let label_stage = gui_data.progress_window.label_stage.clone(); let label_stage = gui_data.progress_window.label_stage.clone();
let notebook_main = gui_data.main_notebook.notebook_main.clone(); let notebook_main = gui_data.main_notebook.notebook_main.clone();
let notebook_upper = gui_data.upper_notebook.notebook_upper.clone(); let notebook_upper = gui_data.upper_notebook.notebook_upper.clone();
@ -55,7 +54,7 @@ pub fn connect_button_search(gui_data: &GuiData, glib_stop_sender: glibSender<Me
let gui_data = gui_data.clone(); let gui_data = gui_data.clone();
buttons_search_clone.connect_clicked(move |_| { buttons_search_clone.connect_clicked(move |_| {
let loaded_common_items = LoadedCommonItems::load_items(&gui_data); let loaded_commons = LoadedCommonItems::load_items(&gui_data);
// Check if user selected all referenced folders // Check if user selected all referenced folders
let list_store_included_directories = get_list_store(&tree_view_included_directories); let list_store_included_directories = get_list_store(&tree_view_included_directories);
@ -83,104 +82,27 @@ pub fn connect_button_search(gui_data: &GuiData, glib_stop_sender: glibSender<Me
reset_text_view(&text_view_errors); reset_text_view(&text_view_errors);
let glib_stop_sender = glib_stop_sender.clone(); let result_sender = result_sender.clone();
let stop_receiver = stop_receiver.clone(); let stop_receiver = stop_receiver.clone();
// Consume any stale stop messages. // Consume any stale stop messages.
stop_receiver.try_iter().for_each(|()| ()); stop_receiver.try_iter().for_each(|()| ());
label_stage.show(); label_stage.show();
let progress_sender = progress_sender.clone();
match to_notebook_main_enum(notebook_main.current_page().unwrap()) { match to_notebook_main_enum(notebook_main.current_page().unwrap()) {
NotebookMainEnum::Duplicate => duplicate_search( NotebookMainEnum::Duplicate => duplicate_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
&gui_data, NotebookMainEnum::EmptyFiles => empty_files_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
loaded_common_items, NotebookMainEnum::EmptyDirectories => empty_dirs_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
stop_receiver, NotebookMainEnum::BigFiles => big_files_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
glib_stop_sender, NotebookMainEnum::Temporary => temporary_files_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
&grid_progress_stages, NotebookMainEnum::SimilarImages => similar_image_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
progress_sender.clone(), NotebookMainEnum::SimilarVideos => similar_video_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
), NotebookMainEnum::SameMusic => same_music_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender, &show_dialog),
NotebookMainEnum::EmptyFiles => empty_files_search( NotebookMainEnum::Symlinks => bad_symlinks_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
&gui_data, NotebookMainEnum::BrokenFiles => broken_files_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender, &show_dialog),
loaded_common_items, NotebookMainEnum::BadExtensions => bad_extensions_search(&gui_data, loaded_commons, stop_receiver, result_sender, &grid_progress, progress_sender),
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::EmptyDirectories => empty_directories_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::BigFiles => big_files_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::Temporary => temporary_files_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::SimilarImages => similar_image_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::SimilarVideos => similar_video_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::SameMusic => same_music_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
&show_dialog,
),
NotebookMainEnum::Symlinks => bad_symlinks_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
NotebookMainEnum::BrokenFiles => broken_files_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
&show_dialog,
),
NotebookMainEnum::BadExtensions => bad_extensions_search(
&gui_data,
loaded_common_items,
stop_receiver,
glib_stop_sender,
&grid_progress_stages,
progress_sender.clone(),
),
} }
window_progress.set_default_size(1, 1); window_progress.set_default_size(1, 1);
@ -201,6 +123,7 @@ struct LoadedCommonItems {
recursive_search: bool, recursive_search: bool,
excluded_items: Vec<String>, excluded_items: Vec<String>,
allowed_extensions: String, allowed_extensions: String,
excluded_extensions: String,
hide_hard_links: bool, hide_hard_links: bool,
use_cache: bool, use_cache: bool,
save_also_as_json: bool, save_also_as_json: bool,
@ -217,6 +140,7 @@ impl LoadedCommonItems {
let check_button_settings_hide_hard_links = gui_data.settings.check_button_settings_hide_hard_links.clone(); let check_button_settings_hide_hard_links = gui_data.settings.check_button_settings_hide_hard_links.clone();
let check_button_settings_use_cache = gui_data.settings.check_button_settings_use_cache.clone(); let check_button_settings_use_cache = gui_data.settings.check_button_settings_use_cache.clone();
let entry_allowed_extensions = gui_data.upper_notebook.entry_allowed_extensions.clone(); let entry_allowed_extensions = gui_data.upper_notebook.entry_allowed_extensions.clone();
let entry_excluded_extensions = gui_data.upper_notebook.entry_excluded_extensions.clone();
let entry_excluded_items = gui_data.upper_notebook.entry_excluded_items.clone(); let entry_excluded_items = gui_data.upper_notebook.entry_excluded_items.clone();
let entry_general_maximal_size = gui_data.upper_notebook.entry_general_maximal_size.clone(); let entry_general_maximal_size = gui_data.upper_notebook.entry_general_maximal_size.clone();
let entry_general_minimal_size = gui_data.upper_notebook.entry_general_minimal_size.clone(); let entry_general_minimal_size = gui_data.upper_notebook.entry_general_minimal_size.clone();
@ -241,6 +165,7 @@ impl LoadedCommonItems {
.map(std::string::ToString::to_string) .map(std::string::ToString::to_string)
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let allowed_extensions = entry_allowed_extensions.text().as_str().to_string(); let allowed_extensions = entry_allowed_extensions.text().as_str().to_string();
let excluded_extensions = entry_excluded_extensions.text().as_str().to_string();
let hide_hard_links = check_button_settings_hide_hard_links.is_active(); let hide_hard_links = check_button_settings_hide_hard_links.is_active();
let use_cache = check_button_settings_use_cache.is_active(); let use_cache = check_button_settings_use_cache.is_active();
let save_also_as_json = check_button_settings_save_also_json.is_active(); let save_also_as_json = check_button_settings_save_also_json.is_active();
@ -269,6 +194,7 @@ impl LoadedCommonItems {
recursive_search, recursive_search,
excluded_items, excluded_items,
allowed_extensions, allowed_extensions,
excluded_extensions,
hide_hard_links, hide_hard_links,
use_cache, use_cache,
save_also_as_json, save_also_as_json,
@ -282,13 +208,13 @@ impl LoadedCommonItems {
fn duplicate_search( fn duplicate_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let combo_box_duplicate_check_method = gui_data.main_notebook.combo_box_duplicate_check_method.clone(); let combo_box_duplicate_check_method = gui_data.main_notebook.combo_box_duplicate_check_method.clone();
let combo_box_duplicate_hash_type = gui_data.main_notebook.combo_box_duplicate_hash_type.clone(); let combo_box_duplicate_hash_type = gui_data.main_notebook.combo_box_duplicate_hash_type.clone();
@ -319,41 +245,32 @@ fn duplicate_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut df = DuplicateFinder::new(); let mut item = DuplicateFinder::new();
df.set_included_directory(loaded_common_items.included_directories);
df.set_excluded_directory(loaded_common_items.excluded_directories); set_common_settings(&mut item, &loaded_commons);
df.set_reference_directory(loaded_common_items.reference_directories); item.set_minimal_cache_file_size(loaded_commons.minimal_cache_file_size);
df.set_recursive_search(loaded_common_items.recursive_search); item.set_minimal_prehash_cache_file_size(minimal_prehash_cache_file_size);
df.set_excluded_items(loaded_common_items.excluded_items); item.set_check_method(check_method);
df.set_allowed_extensions(loaded_common_items.allowed_extensions); item.set_hash_type(hash_type);
df.set_minimal_file_size(loaded_common_items.minimal_file_size); item.set_ignore_hard_links(loaded_commons.hide_hard_links);
df.set_maximal_file_size(loaded_common_items.maximal_file_size); item.set_use_prehash_cache(use_prehash_cache);
df.set_minimal_cache_file_size(loaded_common_items.minimal_cache_file_size); item.set_delete_outdated_cache(delete_outdated_cache);
df.set_minimal_prehash_cache_file_size(minimal_prehash_cache_file_size); item.set_case_sensitive_name_comparison(case_sensitive_name_comparison);
df.set_check_method(check_method); item.find_duplicates(Some(&stop_receiver), Some(&progress_data_sender));
df.set_hash_type(hash_type); result_sender.send(Message::Duplicates(item)).unwrap();
df.set_save_also_as_json(loaded_common_items.save_also_as_json);
df.set_ignore_hard_links(loaded_common_items.hide_hard_links);
df.set_use_cache(loaded_common_items.use_cache);
df.set_use_prehash_cache(use_prehash_cache);
df.set_delete_outdated_cache(delete_outdated_cache);
df.set_case_sensitive_name_comparison(case_sensitive_name_comparison);
df.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
df.find_duplicates(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::Duplicates(df)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn empty_files_search( fn empty_files_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.hide(); grid_progress.hide();
let tree_view_empty_files_finder = gui_data.main_notebook.tree_view_empty_files_finder.clone(); let tree_view_empty_files_finder = gui_data.main_notebook.tree_view_empty_files_finder.clone();
clean_tree_view(&tree_view_empty_files_finder); clean_tree_view(&tree_view_empty_files_finder);
@ -361,29 +278,24 @@ fn empty_files_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut vf = EmptyFiles::new(); let mut item = EmptyFiles::new();
vf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
vf.set_excluded_directory(loaded_common_items.excluded_directories); item.find_empty_files(Some(&stop_receiver), Some(&progress_data_sender));
vf.set_recursive_search(loaded_common_items.recursive_search); result_sender.send(Message::EmptyFiles(item)).unwrap();
vf.set_excluded_items(loaded_common_items.excluded_items);
vf.set_allowed_extensions(loaded_common_items.allowed_extensions);
vf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
vf.find_empty_files(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::EmptyFiles(vf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn empty_directories_search( fn empty_dirs_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.hide(); grid_progress.hide();
let tree_view_empty_folder_finder = gui_data.main_notebook.tree_view_empty_folder_finder.clone(); let tree_view_empty_folder_finder = gui_data.main_notebook.tree_view_empty_folder_finder.clone();
clean_tree_view(&tree_view_empty_folder_finder); clean_tree_view(&tree_view_empty_folder_finder);
@ -391,26 +303,24 @@ fn empty_directories_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut ef = EmptyFolder::new(); let mut item = EmptyFolder::new();
ef.set_included_directory(loaded_common_items.included_directories);
ef.set_excluded_directory(loaded_common_items.excluded_directories); set_common_settings(&mut item, &loaded_commons);
ef.set_excluded_items(loaded_common_items.excluded_items); item.find_empty_folders(Some(&stop_receiver), Some(&progress_data_sender));
ef.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems); result_sender.send(Message::EmptyFolders(item)).unwrap();
ef.find_empty_folders(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::EmptyFolders(ef)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn big_files_search( fn big_files_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.hide(); grid_progress.hide();
let combo_box_big_files_mode = gui_data.main_notebook.combo_box_big_files_mode.clone(); let combo_box_big_files_mode = gui_data.main_notebook.combo_box_big_files_mode.clone();
let entry_big_files_number = gui_data.main_notebook.entry_big_files_number.clone(); let entry_big_files_number = gui_data.main_notebook.entry_big_files_number.clone();
@ -425,31 +335,26 @@ fn big_files_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut bf = BigFile::new(); let mut item = BigFile::new();
bf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
bf.set_excluded_directory(loaded_common_items.excluded_directories); item.set_number_of_files_to_check(numbers_of_files_to_check);
bf.set_recursive_search(loaded_common_items.recursive_search); item.set_search_mode(big_files_mode);
bf.set_excluded_items(loaded_common_items.excluded_items); item.find_big_files(Some(&stop_receiver), Some(&progress_data_sender));
bf.set_allowed_extensions(loaded_common_items.allowed_extensions); result_sender.send(Message::BigFiles(item)).unwrap();
bf.set_number_of_files_to_check(numbers_of_files_to_check);
bf.set_search_mode(big_files_mode);
bf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
bf.find_big_files(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::BigFiles(bf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn temporary_files_search( fn temporary_files_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.hide(); grid_progress.hide();
let tree_view_temporary_files_finder = gui_data.main_notebook.tree_view_temporary_files_finder.clone(); let tree_view_temporary_files_finder = gui_data.main_notebook.tree_view_temporary_files_finder.clone();
clean_tree_view(&tree_view_temporary_files_finder); clean_tree_view(&tree_view_temporary_files_finder);
@ -457,29 +362,25 @@ fn temporary_files_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut tf = Temporary::new(); let mut item = Temporary::new();
tf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
tf.set_excluded_directory(loaded_common_items.excluded_directories); item.find_temporary_files(Some(&stop_receiver), Some(&progress_data_sender));
tf.set_recursive_search(loaded_common_items.recursive_search); result_sender.send(Message::Temporary(item)).unwrap();
tf.set_excluded_items(loaded_common_items.excluded_items);
tf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
tf.find_temporary_files(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::Temporary(tf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn same_music_search( fn same_music_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
show_dialog: &Arc<AtomicBool>, show_dialog: &Arc<AtomicBool>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let check_button_music_artist: gtk4::CheckButton = gui_data.main_notebook.check_button_music_artist.clone(); let check_button_music_artist: gtk4::CheckButton = gui_data.main_notebook.check_button_music_artist.clone();
let check_button_music_title: gtk4::CheckButton = gui_data.main_notebook.check_button_music_title.clone(); let check_button_music_title: gtk4::CheckButton = gui_data.main_notebook.check_button_music_title.clone();
@ -528,26 +429,16 @@ fn same_music_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut mf = SameMusic::new(); let mut item = SameMusic::new();
mf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
mf.set_excluded_directory(loaded_common_items.excluded_directories); item.set_music_similarity(music_similarity);
mf.set_reference_directory(loaded_common_items.reference_directories); item.set_maximum_difference(maximum_difference);
mf.set_excluded_items(loaded_common_items.excluded_items); item.set_minimum_segment_duration(minimum_segment_duration);
mf.set_use_cache(loaded_common_items.use_cache); item.set_check_type(check_method);
mf.set_minimal_file_size(loaded_common_items.minimal_file_size); item.set_approximate_comparison(approximate_comparison);
mf.set_maximal_file_size(loaded_common_items.maximal_file_size); item.find_same_music(Some(&stop_receiver), Some(&progress_data_sender));
mf.set_allowed_extensions(loaded_common_items.allowed_extensions); result_sender.send(Message::SameMusic(item)).unwrap();
mf.set_recursive_search(loaded_common_items.recursive_search);
mf.set_music_similarity(music_similarity);
mf.set_maximum_difference(maximum_difference);
mf.set_minimum_segment_duration(minimum_segment_duration);
mf.set_check_type(check_method);
mf.set_approximate_comparison(approximate_comparison);
mf.set_save_also_as_json(loaded_common_items.save_also_as_json);
mf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
mf.find_same_music(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::SameMusic(mf)).unwrap();
}) })
.unwrap(); .unwrap();
} else { } else {
@ -577,14 +468,14 @@ fn same_music_search(
fn broken_files_search( fn broken_files_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
show_dialog: &Arc<AtomicBool>, show_dialog: &Arc<AtomicBool>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let check_button_broken_files_archive: gtk4::CheckButton = gui_data.main_notebook.check_button_broken_files_archive.clone(); let check_button_broken_files_archive: gtk4::CheckButton = gui_data.main_notebook.check_button_broken_files_archive.clone();
let check_button_broken_files_pdf: gtk4::CheckButton = gui_data.main_notebook.check_button_broken_files_pdf.clone(); let check_button_broken_files_pdf: gtk4::CheckButton = gui_data.main_notebook.check_button_broken_files_pdf.clone();
@ -613,19 +504,12 @@ fn broken_files_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut br = BrokenFiles::new(); let mut item = BrokenFiles::new();
br.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
br.set_excluded_directory(loaded_common_items.excluded_directories); item.set_checked_types(checked_types);
br.set_recursive_search(loaded_common_items.recursive_search); item.find_broken_files(Some(&stop_receiver), Some(&progress_data_sender));
br.set_excluded_items(loaded_common_items.excluded_items); result_sender.send(Message::BrokenFiles(item)).unwrap();
br.set_use_cache(loaded_common_items.use_cache);
br.set_allowed_extensions(loaded_common_items.allowed_extensions);
br.set_save_also_as_json(loaded_common_items.save_also_as_json);
br.set_checked_types(checked_types);
br.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
br.find_broken_files(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::BrokenFiles(br)).unwrap();
}) })
.unwrap(); .unwrap();
} else { } else {
@ -655,13 +539,13 @@ fn broken_files_search(
fn similar_image_search( fn similar_image_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone(); let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone();
let combo_box_image_hash_algorithm = gui_data.main_notebook.combo_box_image_hash_algorithm.clone(); let combo_box_image_hash_algorithm = gui_data.main_notebook.combo_box_image_hash_algorithm.clone();
@ -693,40 +577,30 @@ fn similar_image_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut sf = SimilarImages::new(); let mut item = SimilarImages::new();
sf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
sf.set_excluded_directory(loaded_common_items.excluded_directories); item.set_similarity(similarity);
sf.set_reference_directory(loaded_common_items.reference_directories); item.set_hash_alg(hash_alg);
sf.set_recursive_search(loaded_common_items.recursive_search); item.set_hash_size(hash_size);
sf.set_excluded_items(loaded_common_items.excluded_items); item.set_image_filter(image_filter);
sf.set_minimal_file_size(loaded_common_items.minimal_file_size); item.set_delete_outdated_cache(delete_outdated_cache);
sf.set_maximal_file_size(loaded_common_items.maximal_file_size); item.set_exclude_images_with_same_size(ignore_same_size);
sf.set_similarity(similarity); item.find_similar_images(Some(&stop_receiver), Some(&progress_data_sender));
sf.set_use_cache(loaded_common_items.use_cache); result_sender.send(Message::SimilarImages(item)).unwrap();
sf.set_hash_alg(hash_alg);
sf.set_hash_size(hash_size);
sf.set_image_filter(image_filter);
sf.set_allowed_extensions(loaded_common_items.allowed_extensions);
sf.set_delete_outdated_cache(delete_outdated_cache);
sf.set_exclude_images_with_same_size(ignore_same_size);
sf.set_save_also_as_json(loaded_common_items.save_also_as_json);
sf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
sf.find_similar_images(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::SimilarImages(sf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn similar_video_search( fn similar_video_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let check_button_video_ignore_same_size = gui_data.main_notebook.check_button_video_ignore_same_size.clone(); let check_button_video_ignore_same_size = gui_data.main_notebook.check_button_video_ignore_same_size.clone();
let check_button_settings_similar_videos_delete_outdated_cache = gui_data.settings.check_button_settings_similar_videos_delete_outdated_cache.clone(); let check_button_settings_similar_videos_delete_outdated_cache = gui_data.settings.check_button_settings_similar_videos_delete_outdated_cache.clone();
@ -743,37 +617,27 @@ fn similar_video_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut sf = SimilarVideos::new(); let mut item = SimilarVideos::new();
sf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
sf.set_excluded_directory(loaded_common_items.excluded_directories); item.set_tolerance(tolerance);
sf.set_reference_directory(loaded_common_items.reference_directories); item.set_delete_outdated_cache(delete_outdated_cache);
sf.set_recursive_search(loaded_common_items.recursive_search); item.set_exclude_videos_with_same_size(ignore_same_size);
sf.set_excluded_items(loaded_common_items.excluded_items); item.find_similar_videos(Some(&stop_receiver), Some(&progress_data_sender));
sf.set_minimal_file_size(loaded_common_items.minimal_file_size); result_sender.send(Message::SimilarVideos(item)).unwrap();
sf.set_maximal_file_size(loaded_common_items.maximal_file_size);
sf.set_allowed_extensions(loaded_common_items.allowed_extensions);
sf.set_use_cache(loaded_common_items.use_cache);
sf.set_tolerance(tolerance);
sf.set_delete_outdated_cache(delete_outdated_cache);
sf.set_exclude_videos_with_same_size(ignore_same_size);
sf.set_save_also_as_json(loaded_common_items.save_also_as_json);
sf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
sf.find_similar_videos(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::SimilarVideos(sf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn bad_symlinks_search( fn bad_symlinks_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.hide(); grid_progress.hide();
let tree_view_invalid_symlinks = gui_data.main_notebook.tree_view_invalid_symlinks.clone(); let tree_view_invalid_symlinks = gui_data.main_notebook.tree_view_invalid_symlinks.clone();
clean_tree_view(&tree_view_invalid_symlinks); clean_tree_view(&tree_view_invalid_symlinks);
@ -781,29 +645,24 @@ fn bad_symlinks_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut isf = InvalidSymlinks::new(); let mut item = InvalidSymlinks::new();
isf.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
isf.set_excluded_directory(loaded_common_items.excluded_directories); item.find_invalid_links(Some(&stop_receiver), Some(&progress_data_sender));
isf.set_recursive_search(loaded_common_items.recursive_search); result_sender.send(Message::InvalidSymlinks(item)).unwrap();
isf.set_excluded_items(loaded_common_items.excluded_items);
isf.set_allowed_extensions(loaded_common_items.allowed_extensions);
isf.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
isf.find_invalid_links(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::InvalidSymlinks(isf)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn bad_extensions_search( fn bad_extensions_search(
gui_data: &GuiData, gui_data: &GuiData,
loaded_common_items: LoadedCommonItems, loaded_commons: LoadedCommonItems,
stop_receiver: Receiver<()>, stop_receiver: Receiver<()>,
glib_stop_sender: glibSender<Message>, result_sender: Sender<Message>,
grid_progress_stages: &Grid, grid_progress: &Grid,
progress_data_sender: Sender<ProgressData>, progress_data_sender: Sender<ProgressData>,
) { ) {
grid_progress_stages.show(); grid_progress.show();
let tree_view_bad_extensions = gui_data.main_notebook.tree_view_bad_extensions.clone(); let tree_view_bad_extensions = gui_data.main_notebook.tree_view_bad_extensions.clone();
clean_tree_view(&tree_view_bad_extensions); clean_tree_view(&tree_view_bad_extensions);
@ -811,22 +670,33 @@ fn bad_extensions_search(
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
.spawn(move || { .spawn(move || {
let mut be = BadExtensions::new(); let mut item = BadExtensions::new();
be.set_included_directory(loaded_common_items.included_directories); set_common_settings(&mut item, &loaded_commons);
be.set_excluded_directory(loaded_common_items.excluded_directories); item.find_bad_extensions_files(Some(&stop_receiver), Some(&progress_data_sender));
be.set_excluded_items(loaded_common_items.excluded_items); result_sender.send(Message::BadExtensions(item)).unwrap();
be.set_minimal_file_size(loaded_common_items.minimal_file_size);
be.set_maximal_file_size(loaded_common_items.maximal_file_size);
be.set_allowed_extensions(loaded_common_items.allowed_extensions);
be.set_recursive_search(loaded_common_items.recursive_search);
be.set_exclude_other_filesystems(loaded_common_items.ignore_other_filesystems);
be.find_bad_extensions_files(Some(&stop_receiver), Some(&progress_data_sender));
glib_stop_sender.send(Message::BadExtensions(be)).unwrap();
}) })
.unwrap(); .unwrap();
} }
fn set_common_settings<T>(component: &mut T, loaded_commons: &LoadedCommonItems)
where
T: CommonData,
{
component.set_included_directory(loaded_commons.included_directories.clone());
component.set_excluded_directory(loaded_commons.excluded_directories.clone());
component.set_reference_directory(loaded_commons.reference_directories.clone());
component.set_recursive_search(loaded_commons.recursive_search);
component.set_allowed_extensions(loaded_commons.allowed_extensions.clone());
component.set_excluded_extensions(loaded_commons.excluded_extensions.clone());
component.set_excluded_items(loaded_commons.excluded_items.clone());
component.set_exclude_other_filesystems(loaded_commons.ignore_other_filesystems);
component.set_use_cache(loaded_commons.use_cache);
component.set_save_also_as_json(loaded_commons.save_also_as_json);
component.set_minimal_file_size(loaded_commons.minimal_file_size);
component.set_maximal_file_size(loaded_commons.maximal_file_size);
}
#[fun_time(message = "clean_tree_view", level = "debug")] #[fun_time(message = "clean_tree_view", level = "debug")]
fn clean_tree_view(tree_view: &gtk4::TreeView) { fn clean_tree_view(tree_view: &gtk4::TreeView) {
let list_store = get_list_store(tree_view); let list_store = get_list_store(tree_view);

View file

@ -1,9 +1,10 @@
use czkawka_core::common::regex_check;
use czkawka_core::common_items::new_excluded_item;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{ResponseType, TreeIter, Window}; use gtk4::{ResponseType, TreeIter, Window};
use regex::Regex; use regex::Regex;
use czkawka_core::common::regex_check;
use czkawka_core::common_items::new_excluded_item;
use crate::flg; use crate::flg;
use crate::gui_structs::gui_data::GuiData; use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::*; use crate::help_functions::*;

View file

@ -1,10 +1,9 @@
use crossbeam_channel::Receiver;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use crossbeam_channel::Receiver;
use glib::MainContext; use glib::MainContext;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::ProgressBar; use gtk4::ProgressBar;
@ -164,10 +163,7 @@ fn progress_collect_items(gui_data: &GuiData, item: &ProgressData, files: bool)
if files { if files {
label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(item))); label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(item)));
} else { } else {
label_stage.set_text(&flg!( label_stage.set_text(&flg!("progress_scanning_empty_folders", folder_number = item.entries_checked));
"progress_scanning_empty_folders",
generate_translation_hashmap(vec![("folder_number", item.entries_checked.to_string())])
));
} }
} }

View file

@ -1,7 +1,7 @@
use gdk4::{DragAction, FileList};
use std::collections::HashSet; use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use gdk4::{DragAction, FileList};
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{DropTarget, FileChooserNative, Notebook, Orientation, ResponseType, TreeView, Window}; use gtk4::{DropTarget, FileChooserNative, Notebook, Orientation, ResponseType, TreeView, Window};

View file

@ -1,16 +1,16 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::default::Default; use std::default::Default;
use czkawka_core::common_cache::{
get_duplicate_cache_file, get_similar_images_cache_file, get_similar_videos_cache_file, load_cache_from_file_generalized_by_path, load_cache_from_file_generalized_by_size,
save_cache_to_file_generalized,
};
use directories_next::ProjectDirs; use directories_next::ProjectDirs;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{Label, ResponseType, Window}; use gtk4::{Label, ResponseType, Window};
use image::imageops::FilterType; use image::imageops::FilterType;
use image_hasher::HashAlg; use image_hasher::HashAlg;
use czkawka_core::common_cache::{
get_duplicate_cache_file, get_similar_images_cache_file, get_similar_videos_cache_file, load_cache_from_file_generalized_by_path, load_cache_from_file_generalized_by_size,
save_cache_to_file_generalized,
};
use czkawka_core::common_messages::Messages; use czkawka_core::common_messages::Messages;
use czkawka_core::duplicate::HashType; use czkawka_core::duplicate::HashType;

View file

@ -108,7 +108,7 @@ impl GuiAbout {
} }
pub fn update_language(&self) { pub fn update_language(&self) {
let mut comment_text: String = "2020 - 2023 Rafał Mikrut(qarmin)\n\n".to_string(); let mut comment_text: String = "2020 - 2024 Rafał Mikrut(qarmin)\n\n".to_string();
comment_text += &flg!("about_window_motto"); comment_text += &flg!("about_window_motto");
self.about_dialog.set_comments(Some(&comment_text)); self.about_dialog.set_comments(Some(&comment_text));

View file

@ -15,7 +15,7 @@ pub struct GuiProgressDialog {
pub label_progress_current_stage: gtk4::Label, pub label_progress_current_stage: gtk4::Label,
pub label_progress_all_stages: gtk4::Label, pub label_progress_all_stages: gtk4::Label,
pub grid_progress_stages: gtk4::Grid, pub grid_progress: gtk4::Grid,
pub button_stop_in_dialog: gtk4::Button, pub button_stop_in_dialog: gtk4::Button,
pub evk_button_stop_in_dialog: EventControllerKey, pub evk_button_stop_in_dialog: EventControllerKey,
@ -38,7 +38,7 @@ impl GuiProgressDialog {
let label_progress_current_stage: gtk4::Label = builder.object("label_progress_current_stage").unwrap(); let label_progress_current_stage: gtk4::Label = builder.object("label_progress_current_stage").unwrap();
let label_progress_all_stages: gtk4::Label = builder.object("label_progress_all_stages").unwrap(); let label_progress_all_stages: gtk4::Label = builder.object("label_progress_all_stages").unwrap();
let grid_progress_stages: gtk4::Grid = builder.object("grid_progress_stages").unwrap(); let grid_progress: gtk4::Grid = builder.object("grid_progress").unwrap();
let button_stop_in_dialog: gtk4::Button = builder.object("button_stop_in_dialog").unwrap(); let button_stop_in_dialog: gtk4::Button = builder.object("button_stop_in_dialog").unwrap();
let evk_button_stop_in_dialog = EventControllerKey::new(); let evk_button_stop_in_dialog = EventControllerKey::new();
@ -53,7 +53,7 @@ impl GuiProgressDialog {
label_stage, label_stage,
label_progress_current_stage, label_progress_current_stage,
label_progress_all_stages, label_progress_all_stages,
grid_progress_stages, grid_progress,
button_stop_in_dialog, button_stop_in_dialog,
evk_button_stop_in_dialog, evk_button_stop_in_dialog,
} }

View file

@ -23,6 +23,7 @@ pub struct GuiUpperNotebook {
pub entry_excluded_items: gtk4::Entry, pub entry_excluded_items: gtk4::Entry,
pub entry_allowed_extensions: gtk4::Entry, pub entry_allowed_extensions: gtk4::Entry,
pub entry_excluded_extensions: gtk4::Entry,
pub check_button_recursive: gtk4::CheckButton, pub check_button_recursive: gtk4::CheckButton,
@ -35,6 +36,7 @@ pub struct GuiUpperNotebook {
pub label_excluded_items: gtk4::Label, pub label_excluded_items: gtk4::Label,
pub label_allowed_extensions: gtk4::Label, pub label_allowed_extensions: gtk4::Label,
pub label_excluded_extensions: gtk4::Label,
pub entry_general_minimal_size: gtk4::Entry, pub entry_general_minimal_size: gtk4::Entry,
pub entry_general_maximal_size: gtk4::Entry, pub entry_general_maximal_size: gtk4::Entry,
@ -64,6 +66,7 @@ impl GuiUpperNotebook {
tree_view_excluded_directories.add_controller(gc_tree_view_excluded_directories.clone()); tree_view_excluded_directories.add_controller(gc_tree_view_excluded_directories.clone());
let entry_allowed_extensions: gtk4::Entry = builder.object("entry_allowed_extensions").unwrap(); let entry_allowed_extensions: gtk4::Entry = builder.object("entry_allowed_extensions").unwrap();
let entry_excluded_extensions: gtk4::Entry = builder.object("entry_excluded_extensions").unwrap();
let entry_excluded_items: gtk4::Entry = builder.object("entry_excluded_items").unwrap(); let entry_excluded_items: gtk4::Entry = builder.object("entry_excluded_items").unwrap();
let check_button_recursive: gtk4::CheckButton = builder.object("check_button_recursive").unwrap(); let check_button_recursive: gtk4::CheckButton = builder.object("check_button_recursive").unwrap();
@ -77,6 +80,7 @@ impl GuiUpperNotebook {
let label_excluded_items: gtk4::Label = builder.object("label_excluded_items").unwrap(); let label_excluded_items: gtk4::Label = builder.object("label_excluded_items").unwrap();
let label_allowed_extensions: gtk4::Label = builder.object("label_allowed_extensions").unwrap(); let label_allowed_extensions: gtk4::Label = builder.object("label_allowed_extensions").unwrap();
let label_excluded_extensions: gtk4::Label = builder.object("label_excluded_extensions").unwrap();
let entry_general_minimal_size: gtk4::Entry = builder.object("entry_general_minimal_size").unwrap(); let entry_general_minimal_size: gtk4::Entry = builder.object("entry_general_minimal_size").unwrap();
let entry_general_maximal_size: gtk4::Entry = builder.object("entry_general_maximal_size").unwrap(); let entry_general_maximal_size: gtk4::Entry = builder.object("entry_general_maximal_size").unwrap();
@ -103,6 +107,7 @@ impl GuiUpperNotebook {
gc_tree_view_excluded_directories, gc_tree_view_excluded_directories,
entry_excluded_items, entry_excluded_items,
entry_allowed_extensions, entry_allowed_extensions,
entry_excluded_extensions,
check_button_recursive, check_button_recursive,
buttons_manual_add_included_directory, buttons_manual_add_included_directory,
buttons_add_included_directory, buttons_add_included_directory,
@ -112,6 +117,7 @@ impl GuiUpperNotebook {
buttons_remove_excluded_directory, buttons_remove_excluded_directory,
label_excluded_items, label_excluded_items,
label_allowed_extensions, label_allowed_extensions,
label_excluded_extensions,
entry_general_minimal_size, entry_general_minimal_size,
entry_general_maximal_size, entry_general_maximal_size,
label_general_size_bytes, label_general_size_bytes,
@ -141,11 +147,14 @@ impl GuiUpperNotebook {
self.label_allowed_extensions.set_tooltip_text(Some(&flg!("upper_allowed_extensions_tooltip"))); self.label_allowed_extensions.set_tooltip_text(Some(&flg!("upper_allowed_extensions_tooltip")));
self.entry_allowed_extensions.set_tooltip_text(Some(&flg!("upper_allowed_extensions_tooltip"))); self.entry_allowed_extensions.set_tooltip_text(Some(&flg!("upper_allowed_extensions_tooltip")));
self.label_excluded_extensions.set_tooltip_text(Some(&flg!("upper_excluded_extensions_tooltip")));
self.entry_excluded_extensions.set_tooltip_text(Some(&flg!("upper_excluded_extensions_tooltip")));
self.label_excluded_items.set_tooltip_text(Some(&flg!("upper_excluded_items_tooltip"))); self.label_excluded_items.set_tooltip_text(Some(&flg!("upper_excluded_items_tooltip")));
self.entry_excluded_items.set_tooltip_text(Some(&flg!("upper_excluded_items_tooltip"))); self.entry_excluded_items.set_tooltip_text(Some(&flg!("upper_excluded_items_tooltip")));
self.label_excluded_items.set_label(&flg!("upper_excluded_items")); self.label_excluded_items.set_label(&flg!("upper_excluded_items"));
self.label_allowed_extensions.set_label(&flg!("upper_allowed_extensions")); self.label_allowed_extensions.set_label(&flg!("upper_allowed_extensions"));
self.label_excluded_extensions.set_label(&flg!("upper_excluded_extensions"));
self.label_general_size_bytes.set_label(&flg!("main_label_size_bytes")); self.label_general_size_bytes.set_label(&flg!("main_label_size_bytes"));
self.label_general_min_size.set_label(&flg!("main_label_min_size")); self.label_general_min_size.set_label(&flg!("main_label_min_size"));

View file

@ -1,7 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::{PathBuf, MAIN_SEPARATOR};
use gdk4::gdk_pixbuf::{InterpType, Pixbuf}; use gdk4::gdk_pixbuf::{InterpType, Pixbuf};
use glib::Error; use glib::Error;
@ -14,7 +14,6 @@ use once_cell::sync::OnceCell;
use czkawka_core::bad_extensions::BadExtensions; use czkawka_core::bad_extensions::BadExtensions;
use czkawka_core::big_file::BigFile; use czkawka_core::big_file::BigFile;
use czkawka_core::broken_files::BrokenFiles; use czkawka_core::broken_files::BrokenFiles;
use czkawka_core::common::CHARACTER;
use czkawka_core::common_dir_traversal; use czkawka_core::common_dir_traversal;
use czkawka_core::common_messages::Messages; use czkawka_core::common_messages::Messages;
use czkawka_core::duplicate::DuplicateFinder; use czkawka_core::duplicate::DuplicateFinder;
@ -414,7 +413,7 @@ pub fn get_notebook_object_from_tree_view(tree_view: &TreeView) -> &NotebookObje
pub fn get_full_name_from_path_name(path: &str, name: &str) -> String { pub fn get_full_name_from_path_name(path: &str, name: &str) -> String {
let mut string = String::with_capacity(path.len() + name.len() + 1); let mut string = String::with_capacity(path.len() + name.len() + 1);
string.push_str(path); string.push_str(path);
string.push(CHARACTER); string.push(MAIN_SEPARATOR);
string.push_str(name); string.push_str(name);
string string
} }

View file

@ -510,14 +510,7 @@ fn show_preview(
let image = match get_dynamic_image_from_heic(file_name) { let image = match get_dynamic_image_from_heic(file_name) {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("preview_image_opening_failure", name = file_name, reason = e.to_string()).as_str());
text_view_errors,
flg!(
"preview_image_opening_failure",
generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
break 'dir; break 'dir;
} }
}; };
@ -526,14 +519,7 @@ fn show_preview(
match get_pixbuf_from_dynamic_image(&image) { match get_pixbuf_from_dynamic_image(&image) {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("preview_image_opening_failure", name = file_name, reason = e.to_string()).as_str());
text_view_errors,
flg!(
"preview_image_opening_failure",
generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
break 'dir; break 'dir;
} }
} }
@ -559,10 +545,7 @@ fn show_preview(
pixbuf = match resize_pixbuf_dimension(&pixbuf, (800, 800), InterpType::Bilinear) { pixbuf = match resize_pixbuf_dimension(&pixbuf, (800, 800), InterpType::Bilinear) {
None => { None => {
add_text_to_text_view( add_text_to_text_view(text_view_errors, flg!("preview_image_resize_failure", name = file_name).as_str());
text_view_errors,
flg!("preview_image_resize_failure", generate_translation_hashmap(vec![("name", file_name.to_string())])).as_str(),
);
break 'dir; break 'dir;
} }
Some(pixbuf) => pixbuf, Some(pixbuf) => pixbuf,

View file

@ -5,11 +5,10 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![allow(clippy::needless_late_init)] #![allow(clippy::needless_late_init)]
use crossbeam_channel::{unbounded, Receiver, Sender};
use std::env; use std::env;
use std::ffi::OsString; use std::ffi::OsString;
use glib::Priority; use crossbeam_channel::{unbounded, Receiver, Sender};
use gtk4::gio::ApplicationFlags; use gtk4::gio::ApplicationFlags;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::Application; use gtk4::Application;
@ -82,10 +81,7 @@ fn main() {
fn build_ui(application: &Application, arguments: &[OsString]) { fn build_ui(application: &Application, arguments: &[OsString]) {
let gui_data: GuiData = GuiData::new_with_application(application); let gui_data: GuiData = GuiData::new_with_application(application);
// Used for getting data from thread let (result_sender, result_receiver) = unbounded();
// TODO - deprecation happened without any example, so not sure how new code should look like
#[allow(deprecated)]
let (glib_stop_sender, glib_stop_receiver) = glib::MainContext::channel(Priority::default());
// Futures progress report // Futures progress report
let (progress_sender, progress_receiver): (Sender<ProgressData>, Receiver<ProgressData>) = unbounded(); let (progress_sender, progress_receiver): (Sender<ProgressData>, Receiver<ProgressData>) = unbounded();
@ -111,7 +107,7 @@ fn build_ui(application: &Application, arguments: &[OsString]) {
connect_button_delete(&gui_data); connect_button_delete(&gui_data);
connect_button_save(&gui_data); connect_button_save(&gui_data);
connect_button_search(&gui_data, glib_stop_sender, progress_sender); connect_button_search(&gui_data, result_sender, progress_sender);
connect_button_select(&gui_data); connect_button_select(&gui_data);
connect_button_sort(&gui_data); connect_button_sort(&gui_data);
connect_button_stop(&gui_data); connect_button_stop(&gui_data);
@ -124,7 +120,7 @@ fn build_ui(application: &Application, arguments: &[OsString]) {
connect_selection_of_directories(&gui_data); connect_selection_of_directories(&gui_data);
connect_popover_select(&gui_data); connect_popover_select(&gui_data);
connect_popover_sort(&gui_data); connect_popover_sort(&gui_data);
connect_compute_results(&gui_data, glib_stop_receiver); connect_compute_results(&gui_data, result_receiver);
connect_progress_window(&gui_data, progress_receiver); connect_progress_window(&gui_data, progress_receiver);
connect_show_hide_ui(&gui_data); connect_show_hide_ui(&gui_data);
connect_settings(&gui_data); connect_settings(&gui_data);

View file

@ -9,7 +9,7 @@ use directories_next::ProjectDirs;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{ComboBoxText, ScrolledWindow, TextView, TreeView}; use gtk4::{ComboBoxText, ScrolledWindow, TextView, TreeView};
use czkawka_core::common::get_default_number_of_threads; use czkawka_core::common::get_all_available_threads;
use czkawka_core::common_dir_traversal::CheckingMethod; use czkawka_core::common_dir_traversal::CheckingMethod;
use czkawka_core::common_items::DEFAULT_EXCLUDED_ITEMS; use czkawka_core::common_items::DEFAULT_EXCLUDED_ITEMS;
use czkawka_core::similar_images::SIMILAR_VALUES; use czkawka_core::similar_images::SIMILAR_VALUES;
@ -21,7 +21,6 @@ use crate::gui_structs::gui_upper_notebook::GuiUpperNotebook;
use crate::help_combo_box::DUPLICATES_CHECK_METHOD_COMBO_BOX; use crate::help_combo_box::DUPLICATES_CHECK_METHOD_COMBO_BOX;
use crate::help_functions::*; use crate::help_functions::*;
use crate::language_functions::{get_language_from_combo_box_text, LANGUAGES_ALL}; use crate::language_functions::{get_language_from_combo_box_text, LANGUAGES_ALL};
use crate::localizer_core::generate_translation_hashmap;
const SAVE_FILE_NAME: &str = "czkawka_gui_config_4.txt"; const SAVE_FILE_NAME: &str = "czkawka_gui_config_4.txt";
@ -82,7 +81,7 @@ impl LoadSaveStruct {
pub fn get_vector_string(&self, key: &str, default_value: Vec<String>) -> Vec<String> { pub fn get_vector_string(&self, key: &str, default_value: Vec<String>) -> Vec<String> {
if self.loaded_items.contains_key(key) { if self.loaded_items.contains_key(key) {
let mut new_vector = Vec::new(); let mut new_vector = Vec::new();
for i in self.loaded_items.get(key).unwrap() { for i in &self.loaded_items[key] {
if !i.trim().is_empty() { if !i.trim().is_empty() {
new_vector.push(i.trim().to_string()); new_vector.push(i.trim().to_string());
} }
@ -105,19 +104,13 @@ impl LoadSaveStruct {
} }
pub fn get_string(&self, key: String, default_value: String) -> String { pub fn get_string(&self, key: String, default_value: String) -> String {
if self.loaded_items.contains_key(&key) { if self.loaded_items.contains_key(&key) {
let item = self.loaded_items.get(&key).unwrap().clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>(); let item = &self.loaded_items[&key].clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>();
return if item.len() == 1 { return if item.len() == 1 {
item[0].clone() item[0].clone()
} else if item.is_empty() { } else if item.is_empty() {
String::new() String::new()
} else { } else {
add_text_to_text_view( add_text_to_text_view(&self.text_view, &flg!("saving_loading_invalid_string", key = key, result = format!("{item:?}")));
&self.text_view,
&flg!(
"saving_loading_invalid_string",
generate_translation_hashmap(vec![("key", key), ("result", format!("{item:?}"))])
),
);
default_value default_value
}; };
} }
@ -126,7 +119,7 @@ impl LoadSaveStruct {
} }
pub fn get_object<T: std::str::FromStr>(&self, key: String, default_value: T) -> T { pub fn get_object<T: std::str::FromStr>(&self, key: String, default_value: T) -> T {
if self.loaded_items.contains_key(&key) { if self.loaded_items.contains_key(&key) {
let item = self.loaded_items.get(&key).unwrap().clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>(); let item = &self.loaded_items[&key].clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>();
return if item.len() == 1 { return if item.len() == 1 {
if let Ok(t) = item[0].parse::<T>() { if let Ok(t) = item[0].parse::<T>() {
@ -136,13 +129,7 @@ impl LoadSaveStruct {
default_value default_value
} }
} else { } else {
add_text_to_text_view( add_text_to_text_view(&self.text_view, &flg!("saving_loading_invalid_int", key = key, result = format!("{item:?}")));
&self.text_view,
&flg!(
"saving_loading_invalid_int",
generate_translation_hashmap(vec![("key", key), ("result", format!("{item:?}"))])
),
);
default_value default_value
}; };
} }
@ -151,7 +138,7 @@ impl LoadSaveStruct {
} }
pub fn get_bool(&self, key: String, default_value: bool) -> bool { pub fn get_bool(&self, key: String, default_value: bool) -> bool {
if self.loaded_items.contains_key(&key) { if self.loaded_items.contains_key(&key) {
let item = self.loaded_items.get(&key).unwrap().clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>(); let item = &self.loaded_items[&key].clone().into_iter().filter(|e| !e.is_empty()).collect::<Vec<String>>();
return if item.len() == 1 { return if item.len() == 1 {
let text = item[0].trim().to_lowercase(); let text = item[0].trim().to_lowercase();
if text == "false" || text == "0" { if text == "false" || text == "0" {
@ -159,23 +146,11 @@ impl LoadSaveStruct {
} else if text == "true" || text == "1" { } else if text == "true" || text == "1" {
true true
} else { } else {
add_text_to_text_view( add_text_to_text_view(&self.text_view, &flg!("saving_loading_decode_problem_bool", key = key, result = item[0].to_string()));
&self.text_view,
&flg!(
"saving_loading_decode_problem_bool",
generate_translation_hashmap(vec![("key", key), ("result", item[0].to_string())])
),
);
default_value default_value
} }
} else { } else {
add_text_to_text_view( add_text_to_text_view(&self.text_view, &flg!("saving_loading_invalid_bool", key = key, result = format!("{item:?}")));
&self.text_view,
&flg!(
"saving_loading_invalid_bool",
generate_translation_hashmap(vec![("key", key), ("result", format!("{item:?}"))])
),
);
default_value default_value
}; };
} }
@ -186,10 +161,7 @@ impl LoadSaveStruct {
// Bool, int, string // Bool, int, string
pub fn save_var<T: ToString>(&mut self, key: String, value: &T) { pub fn save_var<T: ToString>(&mut self, key: String, value: &T) {
if self.loaded_items.contains_key(&key) { if self.loaded_items.contains_key(&key) {
add_text_to_text_view( add_text_to_text_view(&self.text_view, &flg!("saving_loading_saving_same_keys", key = key.clone()));
&self.text_view,
&flg!("saving_loading_saving_same_keys", generate_translation_hashmap(vec![("key", key.clone())])),
);
} }
self.loaded_items.insert(key, vec![value.to_string()]); self.loaded_items.insert(key, vec![value.to_string()]);
@ -224,10 +196,7 @@ impl LoadSaveStruct {
if !config_dir.is_dir() { if !config_dir.is_dir() {
add_text_to_text_view( add_text_to_text_view(
text_view_errors, text_view_errors,
&flg!( &flg!("saving_loading_folder_config_instead_file", path = config_dir.to_string_lossy().to_string()),
"saving_loading_folder_config_instead_file",
generate_translation_hashmap(vec![("path", config_dir.to_string_lossy().to_string())])
),
); );
return None; return None;
} }
@ -236,7 +205,8 @@ impl LoadSaveStruct {
text_view_errors, text_view_errors,
&flg!( &flg!(
"saving_loading_failed_to_create_configuration_folder", "saving_loading_failed_to_create_configuration_folder",
generate_translation_hashmap(vec![("path", config_dir.to_string_lossy().to_string()), ("reason", e.to_string())]) path = config_dir.to_string_lossy().to_string(),
reason = e.to_string()
), ),
); );
return None; return None;
@ -249,7 +219,8 @@ impl LoadSaveStruct {
text_view_errors, text_view_errors,
&flg!( &flg!(
"saving_loading_failed_to_create_config_file", "saving_loading_failed_to_create_config_file",
generate_translation_hashmap(vec![("path", config_file.to_string_lossy().to_string()), ("reason", e.to_string())]) path = config_file.to_string_lossy().to_string(),
reason = e.to_string()
), ),
); );
return None; return None;
@ -262,10 +233,7 @@ impl LoadSaveStruct {
// Don't show errors when there is no configuration file when starting app // Don't show errors when there is no configuration file when starting app
add_text_to_text_view( add_text_to_text_view(
text_view_errors, text_view_errors,
&flg!( &flg!("saving_loading_failed_to_read_config_file", path = config_file.to_string_lossy().to_string()),
"saving_loading_failed_to_read_config_file",
generate_translation_hashmap(vec![("path", config_file.to_string_lossy().to_string())])
),
); );
} }
return None; return None;
@ -278,7 +246,8 @@ impl LoadSaveStruct {
text_view_errors, text_view_errors,
&flg!( &flg!(
"saving_loading_failed_to_create_config_file", "saving_loading_failed_to_create_config_file",
generate_translation_hashmap(vec![("path", config_file.to_string_lossy().to_string()), ("reason", e.to_string())]) path = config_file.to_string_lossy().to_string(),
reason = e.to_string()
), ),
); );
return None; return None;
@ -299,7 +268,8 @@ impl LoadSaveStruct {
text_view_errors, text_view_errors,
&flg!( &flg!(
"saving_loading_failed_to_read_data_from_file", "saving_loading_failed_to_read_data_from_file",
generate_translation_hashmap(vec![("path", config_file.to_string_lossy().to_string()), ("reason", e.to_string())]) path = config_file.to_string_lossy().to_string(),
reason = e.to_string()
), ),
); );
return; return;
@ -314,23 +284,14 @@ impl LoadSaveStruct {
} else if !header.is_empty() { } else if !header.is_empty() {
self.loaded_items.entry(header.clone()).or_default().push(line.to_string()); self.loaded_items.entry(header.clone()).or_default().push(line.to_string());
} else { } else {
add_text_to_text_view( add_text_to_text_view(text_view_errors, &flg!("saving_loading_orphan_data", data = line, line = index.to_string()));
text_view_errors,
&flg!(
"saving_loading_orphan_data",
generate_translation_hashmap(vec![("data", line.to_string()), ("index", index.to_string())])
),
);
} }
} }
let (_, hashmap_sl) = create_hash_map(); let (_, hashmap_sl) = create_hash_map();
for setting in self.loaded_items.keys() { for setting in self.loaded_items.keys() {
if !hashmap_sl.contains_key(setting) { if !hashmap_sl.contains_key(setting) {
add_text_to_text_view( add_text_to_text_view(text_view_errors, &flg!("saving_loading_not_valid", data = setting.clone()));
text_view_errors,
&flg!("saving_loading_not_valid", generate_translation_hashmap(vec![("data", setting.to_string())])),
);
} }
} }
@ -368,20 +329,12 @@ impl LoadSaveStruct {
if data_saved { if data_saved {
add_text_to_text_view( add_text_to_text_view(
text_view_errors, text_view_errors,
flg!( flg!("saving_loading_saving_success", name = config_file.to_string_lossy().to_string()).as_str(),
"saving_loading_saving_success",
generate_translation_hashmap(vec![("name", config_file.to_string_lossy().to_string())])
)
.as_str(),
); );
} else { } else {
add_text_to_text_view( add_text_to_text_view(
text_view_errors, text_view_errors,
flg!( flg!("saving_loading_saving_failure", name = config_file.to_string_lossy().to_string()).as_str(),
"saving_loading_saving_failure",
generate_translation_hashmap(vec![("name", config_file.to_string_lossy().to_string())])
)
.as_str(),
); );
} }
} }
@ -509,183 +462,138 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb
// Upper notebook // Upper notebook
saving_struct.save_list_store( saving_struct.save_list_store(
hashmap_ls.get(&LoadText::IncludedDirectories).unwrap().to_string(), hashmap_ls[&LoadText::IncludedDirectories].clone(),
&upper_notebook.tree_view_included_directories.clone(), &upper_notebook.tree_view_included_directories.clone(),
ColumnsIncludedDirectory::Path as i32, ColumnsIncludedDirectory::Path as i32,
); );
saving_struct.save_list_store( saving_struct.save_list_store(
hashmap_ls.get(&LoadText::ExcludedDirectories).unwrap().to_string(), hashmap_ls[&LoadText::ExcludedDirectories].clone(),
&upper_notebook.tree_view_excluded_directories.clone(), &upper_notebook.tree_view_excluded_directories.clone(),
ColumnsExcludedDirectory::Path as i32, ColumnsExcludedDirectory::Path as i32,
); );
saving_struct.save_var(hashmap_ls.get(&LoadText::ExcludedItems).unwrap().to_string(), &upper_notebook.entry_excluded_items.text()); saving_struct.save_var(hashmap_ls[&LoadText::ExcludedItems].clone(), &upper_notebook.entry_excluded_items.text());
saving_struct.save_var( saving_struct.save_var(hashmap_ls[&LoadText::AllowedExtensions].clone(), &upper_notebook.entry_allowed_extensions.text());
hashmap_ls.get(&LoadText::AllowedExtensions).unwrap().to_string(), saving_struct.save_var(hashmap_ls[&LoadText::MinimalFileSize].clone(), &upper_notebook.entry_general_minimal_size.text());
&upper_notebook.entry_allowed_extensions.text(), saving_struct.save_var(hashmap_ls[&LoadText::MaximalFileSize].clone(), &upper_notebook.entry_general_maximal_size.text());
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::MinimalFileSize).unwrap().to_string(),
&upper_notebook.entry_general_minimal_size.text(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::MaximalFileSize).unwrap().to_string(),
&upper_notebook.entry_general_maximal_size.text(),
);
// Check buttons // Check buttons
saving_struct.save_var(hashmap_ls[&LoadText::SaveAtExit].clone(), &settings.check_button_settings_save_at_exit.is_active());
saving_struct.save_var(hashmap_ls[&LoadText::LoadAtStart].clone(), &settings.check_button_settings_load_at_start.is_active());
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::SaveAtExit).unwrap().to_string(), hashmap_ls[&LoadText::ConfirmDeletionFiles].clone(),
&settings.check_button_settings_save_at_exit.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::LoadAtStart).unwrap().to_string(),
&settings.check_button_settings_load_at_start.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::ConfirmDeletionFiles).unwrap().to_string(),
&settings.check_button_settings_confirm_deletion.is_active(), &settings.check_button_settings_confirm_deletion.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ConfirmDeletionAllFilesInGroup).unwrap().to_string(), hashmap_ls[&LoadText::ConfirmDeletionAllFilesInGroup].clone(),
&settings.check_button_settings_confirm_group_deletion.is_active(), &settings.check_button_settings_confirm_group_deletion.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ImagePreviewImage).unwrap().to_string(), hashmap_ls[&LoadText::ImagePreviewImage].clone(),
&settings.check_button_settings_show_preview_similar_images.is_active(), &settings.check_button_settings_show_preview_similar_images.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::DuplicatePreviewImage).unwrap().to_string(), hashmap_ls[&LoadText::DuplicatePreviewImage].clone(),
&settings.check_button_settings_show_preview_duplicates.is_active(), &settings.check_button_settings_show_preview_duplicates.is_active(),
); );
saving_struct.save_var(hashmap_ls[&LoadText::HideHardLinks].clone(), &settings.check_button_settings_hide_hard_links.is_active());
saving_struct.save_var(hashmap_ls[&LoadText::UseCache].clone(), &settings.check_button_settings_use_cache.is_active());
saving_struct.save_var(hashmap_ls[&LoadText::UseJsonCacheFile].clone(), &settings.check_button_settings_save_also_json.is_active());
saving_struct.save_var(hashmap_ls[&LoadText::DeleteToTrash].clone(), &settings.check_button_settings_use_trash.is_active());
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::HideHardLinks).unwrap().to_string(), hashmap_ls[&LoadText::ImageDeleteOutdatedCacheEntries].clone(),
&settings.check_button_settings_hide_hard_links.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::UseCache).unwrap().to_string(),
&settings.check_button_settings_use_cache.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::UseJsonCacheFile).unwrap().to_string(),
&settings.check_button_settings_save_also_json.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::DeleteToTrash).unwrap().to_string(),
&settings.check_button_settings_use_trash.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::ImageDeleteOutdatedCacheEntries).unwrap().to_string(),
&settings.check_button_settings_similar_images_delete_outdated_cache.is_active(), &settings.check_button_settings_similar_images_delete_outdated_cache.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::DuplicateDeleteOutdatedCacheEntries).unwrap().to_string(), hashmap_ls[&LoadText::DuplicateDeleteOutdatedCacheEntries].clone(),
&settings.check_button_settings_duplicates_delete_outdated_cache.is_active(), &settings.check_button_settings_duplicates_delete_outdated_cache.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::VideoDeleteOutdatedCacheEntries).unwrap().to_string(), hashmap_ls[&LoadText::VideoDeleteOutdatedCacheEntries].clone(),
&settings.check_button_settings_similar_videos_delete_outdated_cache.is_active(), &settings.check_button_settings_similar_videos_delete_outdated_cache.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::UsePrehashCache).unwrap().to_string(), hashmap_ls[&LoadText::UsePrehashCache].clone(),
&settings.check_button_duplicates_use_prehash_cache.is_active(), &settings.check_button_duplicates_use_prehash_cache.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ShowBottomTextPanel).unwrap().to_string(), hashmap_ls[&LoadText::ShowBottomTextPanel].clone(),
&settings.check_button_settings_show_text_view.is_active(), &settings.check_button_settings_show_text_view.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::GeneralIgnoreOtherFilesystems).unwrap().to_string(), hashmap_ls[&LoadText::GeneralIgnoreOtherFilesystems].clone(),
&settings.check_button_settings_one_filesystem.is_active(), &settings.check_button_settings_one_filesystem.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::BrokenFilesArchive).unwrap().to_string(), hashmap_ls[&LoadText::BrokenFilesArchive].clone(),
&main_notebook.check_button_broken_files_archive.is_active(), &main_notebook.check_button_broken_files_archive.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(hashmap_ls[&LoadText::BrokenFilesImage].clone(), &main_notebook.check_button_broken_files_image.is_active());
hashmap_ls.get(&LoadText::BrokenFilesImage).unwrap().to_string(), saving_struct.save_var(hashmap_ls[&LoadText::BrokenFilesAudio].clone(), &main_notebook.check_button_broken_files_audio.is_active());
&main_notebook.check_button_broken_files_image.is_active(), saving_struct.save_var(hashmap_ls[&LoadText::BrokenFilesPdf].clone(), &main_notebook.check_button_broken_files_pdf.is_active());
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::BrokenFilesAudio).unwrap().to_string(),
&main_notebook.check_button_broken_files_audio.is_active(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::BrokenFilesPdf).unwrap().to_string(),
&main_notebook.check_button_broken_files_pdf.is_active(),
);
// Others // Others
saving_struct.save_var(hashmap_ls[&LoadText::ThreadNumber].clone(), &settings.scale_settings_number_of_threads.value().round());
saving_struct.save_var(hashmap_ls[&LoadText::MinimalCacheSize].clone(), &settings.entry_settings_cache_file_minimal_size.text());
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ThreadNumber).unwrap().to_string(), hashmap_ls[&LoadText::MinimalPrehashCacheSize].clone(),
&settings.scale_settings_number_of_threads.value().round(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::MinimalCacheSize).unwrap().to_string(),
&settings.entry_settings_cache_file_minimal_size.text(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::MinimalPrehashCacheSize).unwrap().to_string(),
&settings.entry_settings_prehash_cache_file_minimal_size.text(), &settings.entry_settings_prehash_cache_file_minimal_size.text(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::Language).unwrap().to_string(), hashmap_ls[&LoadText::Language].clone(),
&get_language_from_combo_box_text(&settings.combo_box_settings_language.active_text().unwrap()).short_text, &get_language_from_combo_box_text(&settings.combo_box_settings_language.active_text().unwrap()).short_text,
); );
// Comboboxes main notebook // Comboboxes main notebook
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxDuplicateHashType).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxDuplicateHashType].clone(),
&main_notebook.combo_box_duplicate_hash_type.active().unwrap_or(0), &main_notebook.combo_box_duplicate_hash_type.active().unwrap_or(0),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxDuplicateCheckMethod).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxDuplicateCheckMethod].clone(),
&main_notebook.combo_box_duplicate_check_method.active().unwrap_or(0), &main_notebook.combo_box_duplicate_check_method.active().unwrap_or(0),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxImageResizeAlgorithm).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxImageResizeAlgorithm].clone(),
&main_notebook.combo_box_image_resize_algorithm.active().unwrap_or(0), &main_notebook.combo_box_image_resize_algorithm.active().unwrap_or(0),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxImageHashType).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxImageHashType].clone(),
&main_notebook.combo_box_image_hash_algorithm.active().unwrap_or(0), &main_notebook.combo_box_image_hash_algorithm.active().unwrap_or(0),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxImageHashSize).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxImageHashSize].clone(),
&main_notebook.combo_box_image_hash_size.active().unwrap_or(0), &main_notebook.combo_box_image_hash_size.active().unwrap_or(0),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::ComboBoxBigFiles).unwrap().to_string(), hashmap_ls[&LoadText::ComboBoxBigFiles].clone(),
&main_notebook.combo_box_big_files_mode.active().unwrap_or(0), &main_notebook.combo_box_big_files_mode.active().unwrap_or(0),
); );
// Other2 // Other2
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::DuplicateNameCaseSensitive).unwrap().to_string(), hashmap_ls[&LoadText::DuplicateNameCaseSensitive].clone(),
&main_notebook.check_button_duplicate_case_sensitive_name.is_active(), &main_notebook.check_button_duplicate_case_sensitive_name.is_active(),
); );
saving_struct.save_var(hashmap_ls[&LoadText::NumberOfBiggestFiles].clone(), &main_notebook.entry_big_files_number.text());
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::NumberOfBiggestFiles).unwrap().to_string(), hashmap_ls[&LoadText::SimilarImagesSimilarity].clone(),
&main_notebook.entry_big_files_number.text(),
);
saving_struct.save_var(
hashmap_ls.get(&LoadText::SimilarImagesSimilarity).unwrap().to_string(),
&main_notebook.scale_similarity_similar_images.value(), &main_notebook.scale_similarity_similar_images.value(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::SimilarImagesIgnoreSameSize).unwrap().to_string(), hashmap_ls[&LoadText::SimilarImagesIgnoreSameSize].clone(),
&main_notebook.check_button_image_ignore_same_size.is_active(), &main_notebook.check_button_image_ignore_same_size.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::SimilarVideosSimilarity).unwrap().to_string(), hashmap_ls[&LoadText::SimilarVideosSimilarity].clone(),
&main_notebook.scale_similarity_similar_videos.value(), &main_notebook.scale_similarity_similar_videos.value(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::SimilarVideosIgnoreSameSize).unwrap().to_string(), hashmap_ls[&LoadText::SimilarVideosIgnoreSameSize].clone(),
&main_notebook.check_button_video_ignore_same_size.is_active(), &main_notebook.check_button_video_ignore_same_size.is_active(),
); );
saving_struct.save_var( saving_struct.save_var(
hashmap_ls.get(&LoadText::MusicApproximateComparison).unwrap().to_string(), hashmap_ls[&LoadText::MusicApproximateComparison].clone(),
&main_notebook.check_button_music_approximate_comparison.is_active(), &main_notebook.check_button_music_approximate_comparison.is_active(),
); );
@ -719,83 +627,59 @@ pub fn load_configuration(
// Loading data from hashmaps // Loading data from hashmaps
let (hashmap_ls, _hashmap_sl) = create_hash_map(); let (hashmap_ls, _hashmap_sl) = create_hash_map();
let mut included_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::IncludedDirectories).unwrap(), included_directories); let mut included_directories: Vec<String> = loaded_entries.get_vector_string(&hashmap_ls[&LoadText::IncludedDirectories], included_directories);
let mut excluded_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::ExcludedDirectories).unwrap(), excluded_directories); let mut excluded_directories: Vec<String> = loaded_entries.get_vector_string(&hashmap_ls[&LoadText::ExcludedDirectories], excluded_directories);
let excluded_items: String = loaded_entries.get_string( let excluded_items: String = loaded_entries.get_string(hashmap_ls[&LoadText::ExcludedItems].clone(), upper_notebook.entry_excluded_items.text().to_string());
hashmap_ls.get(&LoadText::ExcludedItems).unwrap().clone(), let allowed_extensions: String = loaded_entries.get_string(hashmap_ls[&LoadText::AllowedExtensions].clone(), String::new());
upper_notebook.entry_excluded_items.text().to_string(), let minimal_file_size: String = loaded_entries.get_integer_string(hashmap_ls[&LoadText::MinimalFileSize].clone(), DEFAULT_MINIMAL_FILE_SIZE.to_string());
); let maximal_file_size: String = loaded_entries.get_integer_string(hashmap_ls[&LoadText::MaximalFileSize].clone(), DEFAULT_MAXIMAL_FILE_SIZE.to_string());
let allowed_extensions: String = loaded_entries.get_string(hashmap_ls.get(&LoadText::AllowedExtensions).unwrap().clone(), String::new());
let minimal_file_size: String = loaded_entries.get_integer_string(hashmap_ls.get(&LoadText::MinimalFileSize).unwrap().clone(), DEFAULT_MINIMAL_FILE_SIZE.to_string());
let maximal_file_size: String = loaded_entries.get_integer_string(hashmap_ls.get(&LoadText::MaximalFileSize).unwrap().clone(), DEFAULT_MAXIMAL_FILE_SIZE.to_string());
let loading_at_start: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::LoadAtStart).unwrap().clone(), DEFAULT_LOAD_AT_START); let loading_at_start: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::LoadAtStart].clone(), DEFAULT_LOAD_AT_START);
let mut saving_at_exit: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::SaveAtExit).unwrap().clone(), DEFAULT_SAVE_ON_EXIT); let mut saving_at_exit: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::SaveAtExit].clone(), DEFAULT_SAVE_ON_EXIT);
let confirm_deletion: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::ConfirmDeletionFiles).unwrap().clone(), DEFAULT_CONFIRM_DELETION); let confirm_deletion: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::ConfirmDeletionFiles].clone(), DEFAULT_CONFIRM_DELETION);
let confirm_group_deletion: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::ConfirmDeletionAllFilesInGroup).unwrap().clone(), DEFAULT_CONFIRM_GROUP_DELETION); let confirm_group_deletion: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::ConfirmDeletionAllFilesInGroup].clone(), DEFAULT_CONFIRM_GROUP_DELETION);
let show_previews_similar_images: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::ImagePreviewImage).unwrap().clone(), DEFAULT_SHOW_IMAGE_PREVIEW); let show_previews_similar_images: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::ImagePreviewImage].clone(), DEFAULT_SHOW_IMAGE_PREVIEW);
let show_previews_duplicates: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::DuplicatePreviewImage).unwrap().clone(), DEFAULT_SHOW_DUPLICATE_IMAGE_PREVIEW); let show_previews_duplicates: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::DuplicatePreviewImage].clone(), DEFAULT_SHOW_DUPLICATE_IMAGE_PREVIEW);
let bottom_text_panel: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::ShowBottomTextPanel).unwrap().clone(), DEFAULT_BOTTOM_TEXT_VIEW); let bottom_text_panel: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::ShowBottomTextPanel].clone(), DEFAULT_BOTTOM_TEXT_VIEW);
let hide_hard_links: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::HideHardLinks).unwrap().clone(), DEFAULT_HIDE_HARD_LINKS); let hide_hard_links: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::HideHardLinks].clone(), DEFAULT_HIDE_HARD_LINKS);
let use_cache: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::UseCache).unwrap().clone(), DEFAULT_USE_CACHE); let use_cache: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::UseCache].clone(), DEFAULT_USE_CACHE);
let use_json_cache: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::UseJsonCacheFile).unwrap().clone(), DEFAULT_SAVE_ALSO_AS_JSON); let use_json_cache: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::UseJsonCacheFile].clone(), DEFAULT_SAVE_ALSO_AS_JSON);
let use_trash: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::DeleteToTrash).unwrap().clone(), DEFAULT_USE_TRASH); let use_trash: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::DeleteToTrash].clone(), DEFAULT_USE_TRASH);
let ignore_other_fs: bool = loaded_entries.get_bool( let ignore_other_fs: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::GeneralIgnoreOtherFilesystems].clone(), DEFAULT_GENERAL_IGNORE_OTHER_FILESYSTEMS);
hashmap_ls.get(&LoadText::GeneralIgnoreOtherFilesystems).unwrap().clone(),
DEFAULT_GENERAL_IGNORE_OTHER_FILESYSTEMS,
);
let delete_outdated_cache_duplicates: bool = loaded_entries.get_bool( let delete_outdated_cache_duplicates: bool = loaded_entries.get_bool(
hashmap_ls.get(&LoadText::DuplicateDeleteOutdatedCacheEntries).unwrap().clone(), hashmap_ls[&LoadText::DuplicateDeleteOutdatedCacheEntries].clone(),
DEFAULT_DUPLICATE_REMOVE_AUTO_OUTDATED_CACHE, DEFAULT_DUPLICATE_REMOVE_AUTO_OUTDATED_CACHE,
); );
let delete_outdated_cache_similar_images: bool = loaded_entries.get_bool( let delete_outdated_cache_similar_images: bool =
hashmap_ls.get(&LoadText::ImageDeleteOutdatedCacheEntries).unwrap().clone(), loaded_entries.get_bool(hashmap_ls[&LoadText::ImageDeleteOutdatedCacheEntries].clone(), DEFAULT_IMAGE_REMOVE_AUTO_OUTDATED_CACHE);
DEFAULT_IMAGE_REMOVE_AUTO_OUTDATED_CACHE, let delete_outdated_cache_similar_videos: bool =
); loaded_entries.get_bool(hashmap_ls[&LoadText::VideoDeleteOutdatedCacheEntries].clone(), DEFAULT_VIDEO_REMOVE_AUTO_OUTDATED_CACHE);
let delete_outdated_cache_similar_videos: bool = loaded_entries.get_bool( let use_prehash_cache: bool = loaded_entries.get_bool(hashmap_ls[&LoadText::UsePrehashCache].clone(), DEFAULT_USE_PRECACHE);
hashmap_ls.get(&LoadText::VideoDeleteOutdatedCacheEntries).unwrap().clone(),
DEFAULT_VIDEO_REMOVE_AUTO_OUTDATED_CACHE,
);
let use_prehash_cache: bool = loaded_entries.get_bool(hashmap_ls.get(&LoadText::UsePrehashCache).unwrap().clone(), DEFAULT_USE_PRECACHE);
let cache_prehash_minimal_size: String = loaded_entries.get_integer_string( let cache_prehash_minimal_size: String =
hashmap_ls.get(&LoadText::MinimalPrehashCacheSize).unwrap().clone(), loaded_entries.get_integer_string(hashmap_ls[&LoadText::MinimalPrehashCacheSize].clone(), DEFAULT_PREHASH_MINIMAL_CACHE_SIZE.to_string());
DEFAULT_PREHASH_MINIMAL_CACHE_SIZE.to_string(), let cache_minimal_size: String = loaded_entries.get_integer_string(hashmap_ls[&LoadText::MinimalCacheSize].clone(), DEFAULT_MINIMAL_CACHE_SIZE.to_string());
); let short_language = loaded_entries.get_string(hashmap_ls[&LoadText::Language].clone(), short_language);
let cache_minimal_size: String = loaded_entries.get_integer_string(hashmap_ls.get(&LoadText::MinimalCacheSize).unwrap().clone(), DEFAULT_MINIMAL_CACHE_SIZE.to_string());
let short_language = loaded_entries.get_string(hashmap_ls.get(&LoadText::Language).unwrap().clone(), short_language);
let combo_box_duplicate_hash_type = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxDuplicateHashType).unwrap().clone(), 0); let combo_box_duplicate_hash_type = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxDuplicateHashType].clone(), 0);
let combo_box_duplicate_checking_method = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxDuplicateCheckMethod).unwrap().clone(), 0); let combo_box_duplicate_checking_method = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxDuplicateCheckMethod].clone(), 0);
let combo_box_image_hash_size = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxImageHashSize).unwrap().clone(), 1); // 16 instead default 8 let combo_box_image_hash_size = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxImageHashSize].clone(), 1); // 16 instead default 8
let combo_box_image_hash_algorithm = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxImageHashType).unwrap().clone(), 0); let combo_box_image_hash_algorithm = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxImageHashType].clone(), 0);
let combo_box_image_resize_algorithm = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxImageResizeAlgorithm).unwrap().clone(), 0); let combo_box_image_resize_algorithm = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxImageResizeAlgorithm].clone(), 0);
let combo_box_big_files_mode = loaded_entries.get_object(hashmap_ls.get(&LoadText::ComboBoxBigFiles).unwrap().clone(), 0); let combo_box_big_files_mode = loaded_entries.get_object(hashmap_ls[&LoadText::ComboBoxBigFiles].clone(), 0);
let number_of_biggest_files = loaded_entries.get_integer_string( let number_of_biggest_files = loaded_entries.get_integer_string(hashmap_ls[&LoadText::NumberOfBiggestFiles].clone(), DEFAULT_NUMBER_OF_BIGGEST_FILES.to_string());
hashmap_ls.get(&LoadText::NumberOfBiggestFiles).unwrap().clone(), let similar_images_similarity = loaded_entries.get_object(hashmap_ls[&LoadText::SimilarImagesSimilarity].clone(), DEFAULT_SIMILAR_IMAGES_SIMILARITY);
DEFAULT_NUMBER_OF_BIGGEST_FILES.to_string(), let similar_images_ignore_same_size = loaded_entries.get_bool(hashmap_ls[&LoadText::SimilarImagesIgnoreSameSize].clone(), DEFAULT_SIMILAR_IMAGES_IGNORE_SAME_SIZE);
); let similar_videos_similarity = loaded_entries.get_object(hashmap_ls[&LoadText::SimilarVideosSimilarity].clone(), DEFAULT_SIMILAR_VIDEOS_SIMILARITY);
let similar_images_similarity = loaded_entries.get_object(hashmap_ls.get(&LoadText::SimilarImagesSimilarity).unwrap().clone(), DEFAULT_SIMILAR_IMAGES_SIMILARITY); let similar_videos_ignore_same_size = loaded_entries.get_bool(hashmap_ls[&LoadText::SimilarVideosIgnoreSameSize].clone(), DEFAULT_SIMILAR_VIDEOS_IGNORE_SAME_SIZE);
let similar_images_ignore_same_size = loaded_entries.get_bool( let check_button_case_sensitive_name = loaded_entries.get_object(hashmap_ls[&LoadText::DuplicateNameCaseSensitive].clone(), DEFAULT_DUPLICATE_CASE_SENSITIVE_NAME_CHECKING);
hashmap_ls.get(&LoadText::SimilarImagesIgnoreSameSize).unwrap().clone(),
DEFAULT_SIMILAR_IMAGES_IGNORE_SAME_SIZE,
);
let similar_videos_similarity = loaded_entries.get_object(hashmap_ls.get(&LoadText::SimilarVideosSimilarity).unwrap().clone(), DEFAULT_SIMILAR_VIDEOS_SIMILARITY);
let similar_videos_ignore_same_size = loaded_entries.get_bool(
hashmap_ls.get(&LoadText::SimilarVideosIgnoreSameSize).unwrap().clone(),
DEFAULT_SIMILAR_VIDEOS_IGNORE_SAME_SIZE,
);
let check_button_case_sensitive_name = loaded_entries.get_object(
hashmap_ls.get(&LoadText::DuplicateNameCaseSensitive).unwrap().clone(),
DEFAULT_DUPLICATE_CASE_SENSITIVE_NAME_CHECKING,
);
let check_button_broken_files_archive = loaded_entries.get_object(hashmap_ls.get(&LoadText::BrokenFilesArchive).unwrap().clone(), DEFAULT_BROKEN_FILES_ARCHIVE); let check_button_broken_files_archive = loaded_entries.get_object(hashmap_ls[&LoadText::BrokenFilesArchive].clone(), DEFAULT_BROKEN_FILES_ARCHIVE);
let check_button_broken_files_pdf = loaded_entries.get_object(hashmap_ls.get(&LoadText::BrokenFilesPdf).unwrap().clone(), DEFAULT_BROKEN_FILES_PDF); let check_button_broken_files_pdf = loaded_entries.get_object(hashmap_ls[&LoadText::BrokenFilesPdf].clone(), DEFAULT_BROKEN_FILES_PDF);
let check_button_broken_files_image = loaded_entries.get_object(hashmap_ls.get(&LoadText::BrokenFilesImage).unwrap().clone(), DEFAULT_BROKEN_FILES_IMAGE); let check_button_broken_files_image = loaded_entries.get_object(hashmap_ls[&LoadText::BrokenFilesImage].clone(), DEFAULT_BROKEN_FILES_IMAGE);
let check_button_broken_files_audio = loaded_entries.get_object(hashmap_ls.get(&LoadText::BrokenFilesAudio).unwrap().clone(), DEFAULT_BROKEN_FILES_AUDIO); let check_button_broken_files_audio = loaded_entries.get_object(hashmap_ls[&LoadText::BrokenFilesAudio].clone(), DEFAULT_BROKEN_FILES_AUDIO);
let thread_number = loaded_entries.get_object(hashmap_ls.get(&LoadText::ThreadNumber).unwrap().clone(), DEFAULT_THREAD_NUMBER); let thread_number = loaded_entries.get_object(hashmap_ls[&LoadText::ThreadNumber].clone(), DEFAULT_THREAD_NUMBER);
let mut set_start_folders = false; let mut set_start_folders = false;
if !manual_execution { if !manual_execution {
@ -949,8 +833,8 @@ pub fn load_configuration(
main_notebook.scale_similarity_similar_images.connect_change_value(scale_step_function); main_notebook.scale_similarity_similar_images.connect_change_value(scale_step_function);
main_notebook.scale_similarity_similar_images.set_value(similar_images_similarity as f64); main_notebook.scale_similarity_similar_images.set_value(similar_images_similarity as f64);
settings.scale_settings_number_of_threads.set_range(0_f64, get_default_number_of_threads() as f64); settings.scale_settings_number_of_threads.set_range(0_f64, get_all_available_threads() as f64);
settings.scale_settings_number_of_threads.set_fill_level(get_default_number_of_threads() as f64); settings.scale_settings_number_of_threads.set_fill_level(get_all_available_threads() as f64);
settings.scale_settings_number_of_threads.connect_change_value(scale_step_function); settings.scale_settings_number_of_threads.connect_change_value(scale_step_function);
settings.scale_settings_number_of_threads.set_value(thread_number as f64); settings.scale_settings_number_of_threads.set_value(thread_number as f64);
} else { } else {

View file

@ -4,7 +4,7 @@
<!-- interface-name about_dialog.ui --> <!-- interface-name about_dialog.ui -->
<requires lib="gtk" version="4.6"/> <requires lib="gtk" version="4.6"/>
<object class="GtkAboutDialog" id="about_dialog"> <object class="GtkAboutDialog" id="about_dialog">
<property name="comments" translatable="yes">2020 - 2023 Rafał Mikrut(qarmin) <property name="comments" translatable="yes">2020 - 2024 Rafał Mikrut(qarmin)
This program is free to use and will always be. This program is free to use and will always be.
</property> </property>

View file

@ -242,6 +242,8 @@
(5,237,"GtkScale","scale_similarity_same_music",117,None,None,None,9,None), (5,237,"GtkScale","scale_similarity_same_music",117,None,None,None,9,None),
(5,238,"GtkLabel","label_same_music_seconds",117,None,None,None,6,None), (5,238,"GtkLabel","label_same_music_seconds",117,None,None,None,6,None),
(5,239,"GtkLabel","label_same_music_similarity",117,None,None,None,8,None), (5,239,"GtkLabel","label_same_music_similarity",117,None,None,None,8,None),
(5,240,"GtkLabel","label_excluded_extensions",46,None,None,None,2,None),
(5,241,"GtkEntry","entry_excluded_extensions",46,None,None,None,3,None),
(6,1,"GtkPopover","popover_right_click",None,None,None,None,None,None), (6,1,"GtkPopover","popover_right_click",None,None,None,None,None,None),
(6,2,"GtkBox",None,1,None,None,None,None,None), (6,2,"GtkBox",None,1,None,None,None,None,None),
(6,3,"GtkButton","buttons_popover_right_click_open_file",2,None,None,None,None,None), (6,3,"GtkButton","buttons_popover_right_click_open_file",2,None,None,None,None,None),
@ -265,7 +267,7 @@
(7,17,"GtkButton","buttons_popover_unselect_all",2,None,None,None,14,None), (7,17,"GtkButton","buttons_popover_unselect_all",2,None,None,None,14,None),
(8,15,"GtkDialog","window_progress",None,None,None,None,None,None), (8,15,"GtkDialog","window_progress",None,None,None,None,None,None),
(8,16,"GtkBox",None,15,None,None,None,None,None), (8,16,"GtkBox",None,15,None,None,None,None,None),
(8,17,"GtkGrid","grid_progress_stages",16,None,None,None,None,None), (8,17,"GtkGrid","grid_progress",16,None,None,None,None,None),
(8,18,"GtkLabel","label_progress_all_stages",17,None,None,None,None,None), (8,18,"GtkLabel","label_progress_all_stages",17,None,None,None,None,None),
(8,19,"GtkProgressBar","progress_bar_all_stages",17,None,None,None,1,None), (8,19,"GtkProgressBar","progress_bar_all_stages",17,None,None,None,1,None),
(8,20,"GtkLabel","label_progress_current_stage",17,None,None,None,2,None), (8,20,"GtkLabel","label_progress_current_stage",17,None,None,None,2,None),
@ -340,7 +342,7 @@
(10,7,"GtkButton","buttons_popover_sort_selection",2,None,None,None,4,None) (10,7,"GtkButton","buttons_popover_sort_selection",2,None,None,None,4,None)
</object> </object>
<object_property> <object_property>
(3,1,"GtkAboutDialog","comments","2020 - 2023 Rafał Mikrut(qarmin) (3,1,"GtkAboutDialog","comments","2020 - 2024 Rafał Mikrut(qarmin)
This program is free to use and will always be. This program is free to use and will always be.
",1,None,None,None,None,None,None,None,None), ",1,None,None,None,None,None,None,None,None),
@ -797,6 +799,9 @@
(5,239,"GtkLabel","label","Max difference",None,None,None,None,None,None,None,None,None), (5,239,"GtkLabel","label","Max difference",None,None,None,None,None,None,None,None,None),
(5,239,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), (5,239,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
(5,239,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), (5,239,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None),
(5,240,"GtkLabel","label","Disabled Extensions","yes",None,None,None,None,None,None,None,None),
(5,241,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None),
(5,241,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None),
(6,1,"GtkPopover","child",None,None,None,None,None,2,None,None,None,None), (6,1,"GtkPopover","child",None,None,None,None,None,2,None,None,None,None),
(6,1,"GtkPopover","position","left",None,None,None,None,None,None,None,None,None), (6,1,"GtkPopover","position","left",None,None,None,None,None,None,None,None,None),
(6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), (6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),

View file

@ -269,6 +269,17 @@
<property name="hexpand">1</property> <property name="hexpand">1</property>
</object> </object>
</child> </child>
<child>
<object class="GtkLabel" id="label_excluded_extensions">
<property name="label" translatable="yes">Disabled Extensions</property>
</object>
</child>
<child>
<object class="GtkEntry" id="entry_excluded_extensions">
<property name="focusable">1</property>
<property name="hexpand">1</property>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>

View file

@ -13,7 +13,7 @@
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">10</property> <property name="spacing">10</property>
<child> <child>
<object class="GtkGrid" id="grid_progress_stages"> <object class="GtkGrid" id="grid_progress">
<property name="margin-end">2</property> <property name="margin-end">2</property>
<property name="margin-start">2</property> <property name="margin-start">2</property>
<property name="margin-top">2</property> <property name="margin-top">2</property>

View file

@ -19,7 +19,7 @@ New versions of GTK fixes some bugs, so e.g. middle button selection will work o
| Program | Min | What for | | Program | Min | What for |
|---------|--------|--------------------------------------------------------------------------------------| |---------|--------|--------------------------------------------------------------------------------------|
| Rust | 1.72.1 | The minimum version of rust does not depend on anything, so it can change frequently | | Rust | 1.74.0 | The minimum version of rust does not depend on anything, so it can change frequently |
| GTK | 4.6 | Only for the `GTK` backend | | GTK | 4.6 | Only for the `GTK` backend |
#### Debian / Ubuntu #### Debian / Ubuntu

View file

@ -3,7 +3,7 @@ name = "krokiet"
version = "6.1.0" version = "6.1.0"
authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"] authors = ["Rafał Mikrut <mikrutrafal@protonmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.72.1" rust-version = "1.74.0"
description = "Slint frontend of Czkawka Core" description = "Slint frontend of Czkawka Core"
license = "GPL-3" license = "GPL-3"
homepage = "https://github.com/qarmin/czkawka" homepage = "https://github.com/qarmin/czkawka"
@ -13,11 +13,11 @@ build = "build.rs"
[dependencies] [dependencies]
rand = "0.8" rand = "0.8"
czkawka_core = { version = "6.1.0", path = "../czkawka_core" } czkawka_core = { version = "6.1.0", path = "../czkawka_core" }
chrono = "0.4.31" chrono = "0.4.34"
open = "5.0" open = "5.0"
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
handsome_logger = "0.8" handsome_logger = "0.8"
rfd = { version = "0.12", default-features = false, features = ["xdg-portal"] } rfd = { version = "0.13", default-features = false, features = ["xdg-portal", "async-std"] }
home = "0.5" home = "0.5"
log = "0.4.20" log = "0.4.20"
serde = "1.0" serde = "1.0"
@ -26,26 +26,26 @@ humansize = "2.1"
image = "0.24" image = "0.24"
directories-next = "2.0" directories-next = "2.0"
image_hasher = "1.2" image_hasher = "1.2"
rayon = "1.8.0" rayon = "1.8"
# Translations # Translations
i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] } i18n-embed = { version = "0.14", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.7" i18n-embed-fl = "0.8"
rust-embed = { version = "8.2", features = ["debug-embed"] } rust-embed = { version = "8.2", features = ["debug-embed"] }
once_cell = "1.19" once_cell = "1.19"
# Try to use only needed features from https://github.com/slint-ui/slint/blob/master/api/rs/slint/Cargo.toml#L23-L31 # Try to use only needed features from https://github.com/slint-ui/slint/blob/master/api/rs/slint/Cargo.toml#L23-L31
#slint = { path = "/home/rafal/test/slint/api/rs/slint/", default-features = false, features = ["std", #slint = { path = "/home/rafal/test/slint/api/rs/slint/", default-features = false, features = ["std",
slint = { git = "https://github.com/slint-ui/slint.git", default-features = false, features = [ #slint = { git = "https://github.com/slint-ui/slint.git", default-features = false, features = [
# slint = { version = "1.3", default-features = false, features = [ slint = { version = "1.4", default-features = false, features = [
"std", "std",
"backend-winit", "backend-winit",
"compat-1-2" "compat-1-2"
] } ] }
[build-dependencies] [build-dependencies]
#slint-build = { path = "/home/rafal/test/slint/api/rs/build/"} #slint-build = { path = "/home/rafal/test/slint/api/rs/build/"}
slint-build = { git = "https://github.com/slint-ui/slint.git" } #slint-build = { git = "https://github.com/slint-ui/slint.git" }
# slint-build = "1.3" slint-build = "1.4"
[features] [features]
default = ["winit_femtovg", "winit_software"] default = ["winit_femtovg", "winit_software"]

View file

@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
Czkawka Slint Gui Copyright (C) 2023 Rafał Mikrut Czkawka Slint Gui Copyright (C) 2024 Rafał Mikrut
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 Rafał Mikrut Copyright (c) 2020-2024 Rafał Mikrut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,6 +1,6 @@
# Krokiet # Krokiet
Krokiet is new Czkawka frontend written in Slint(written mostly in Rust) in opposite to Gtk 4 frontend which uses mostly Krokiet is new Czkawka frontend written in Slint, which uses Rust in opposite to Gtk 4 frontend which uses mostly
C code. C code.
Different toolkit means different look, limitations and features, so you should not expect same features like in Gtk 4 Different toolkit means different look, limitations and features, so you should not expect same features like in Gtk 4
@ -24,7 +24,7 @@ sudo apt install libfontconfig-dev libfreetype-dev
Default compilation is done by `cargo build --release` and should work on most systems. Default compilation is done by `cargo build --release` and should work on most systems.
You need the latest available version of Rust to compile it, because Krokiet aims to support the latest slint verions, You need the latest available version of Rust to compile it, because Krokiet aims to support the latest slint versions,
that should provide best experience. that should provide best experience.
The only exception is building skia renderer which is non default feature that can be enabled manually if you want to The only exception is building skia renderer which is non default feature that can be enabled manually if you want to
@ -103,25 +103,6 @@ SLINT_STYLE=material-dark cargo run -- --path .
preview - [slint live preview example](https://slint.dev/releases/1.3.0/editor/?load_demo=examples/printerdemo/ui/printerdemo.slint) preview - [slint live preview example](https://slint.dev/releases/1.3.0/editor/?load_demo=examples/printerdemo/ui/printerdemo.slint)
- Improving app rust code - Improving app rust code
## Missing features available in GTK 4 frontend
- icons in buttons
- resizable input files panel
- settings
- moving files
- deleting files
- sorting files
- saving results
- symlink/hardlink
- implementing all modes
- multiple selection
- proper popup windows - slint not handle them properly
- logo
- about window
- reference folders
- translations(problem is only with interface, messages like "Checking {x} file" can be easily translated from rust
side)
## Why Slint? ## Why Slint?
There are multiple reasons why I decided to use Slint as toolkit for Krokiet over other toolkits. There are multiple reasons why I decided to use Slint as toolkit for Krokiet over other toolkits.

View file

@ -1,9 +1,10 @@
use crate::CurrentTab;
use slint::{ModelRc, SharedString, StandardListViewItem, VecModel};
use std::path::PathBuf; use std::path::PathBuf;
use crate::{CurrentTab, ExcludedDirectoriesModel, IncludedDirectoriesModel, MainListModel, MainWindow};
use slint::{ModelRc, SharedString, VecModel};
// Remember to match updated this according to ui/main_lists.slint and connect_scan.rs files // Remember to match updated this according to ui/main_lists.slint and connect_scan.rs files
pub fn get_path_idx(active_tab: CurrentTab) -> usize { pub fn get_str_path_idx(active_tab: CurrentTab) -> usize {
match active_tab { match active_tab {
CurrentTab::EmptyFolders => 1, CurrentTab::EmptyFolders => 1,
CurrentTab::EmptyFiles => 1, CurrentTab::EmptyFiles => 1,
@ -11,7 +12,7 @@ pub fn get_path_idx(active_tab: CurrentTab) -> usize {
CurrentTab::Settings => panic!("Button should be disabled"), CurrentTab::Settings => panic!("Button should be disabled"),
} }
} }
pub fn get_name_idx(active_tab: CurrentTab) -> usize { pub fn get_str_name_idx(active_tab: CurrentTab) -> usize {
match active_tab { match active_tab {
CurrentTab::EmptyFolders => 0, CurrentTab::EmptyFolders => 0,
CurrentTab::EmptyFiles => 0, CurrentTab::EmptyFiles => 0,
@ -19,6 +20,39 @@ pub fn get_name_idx(active_tab: CurrentTab) -> usize {
CurrentTab::Settings => panic!("Button should be disabled"), CurrentTab::Settings => 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"),
}
}
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"),
}
}
pub fn get_int_width_idx(active_tab: CurrentTab) -> usize {
match active_tab {
CurrentTab::SimilarImages => 4,
CurrentTab::Settings => 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"),
_ => panic!("Unable to get height from this tab"),
}
}
pub fn get_is_header_mode(active_tab: CurrentTab) -> bool { pub fn get_is_header_mode(active_tab: CurrentTab) -> bool {
match active_tab { match active_tab {
CurrentTab::EmptyFolders | CurrentTab::EmptyFiles => false, CurrentTab::EmptyFolders | CurrentTab::EmptyFiles => false,
@ -27,6 +61,24 @@ pub fn get_is_header_mode(active_tab: CurrentTab) -> bool {
} }
} }
pub fn get_tool_model(app: &MainWindow, tab: CurrentTab) -> ModelRc<MainListModel> {
match tab {
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"),
}
}
pub fn set_tool_model(app: &MainWindow, tab: CurrentTab, model: ModelRc<MainListModel>) {
match tab {
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"),
}
}
// pub fn create_string_standard_list_view(items: &[String]) -> ModelRc<StandardListViewItem> { // pub fn create_string_standard_list_view(items: &[String]) -> ModelRc<StandardListViewItem> {
// let new_folders_standard_list_view = items // let new_folders_standard_list_view = items
// .iter() // .iter()
@ -38,18 +90,98 @@ pub fn get_is_header_mode(active_tab: CurrentTab) -> bool {
// .collect::<Vec<_>>(); // .collect::<Vec<_>>();
// ModelRc::new(VecModel::from(new_folders_standard_list_view)) // ModelRc::new(VecModel::from(new_folders_standard_list_view))
// } // }
pub fn create_string_standard_list_view_from_pathbuf(items: &[PathBuf]) -> ModelRc<StandardListViewItem> { // pub fn create_string_standard_list_view_from_pathbuf(items: &[PathBuf]) -> ModelRc<StandardListViewItem> {
let new_folders_standard_list_view = items // let new_folders_standard_list_view = items
// .iter()
// .map(|x| {
// let mut element = StandardListViewItem::default();
// element.text = x.to_string_lossy().to_string().into();
// element
// })
// .collect::<Vec<_>>();
// ModelRc::new(VecModel::from(new_folders_standard_list_view))
// }
pub fn create_included_directories_model_from_pathbuf(items: &[PathBuf], referenced: &[PathBuf]) -> ModelRc<IncludedDirectoriesModel> {
let referenced_as_string = referenced.iter().map(|x| x.to_string_lossy().to_string()).collect::<Vec<_>>();
let converted = items
.iter() .iter()
.map(|x| { .map(|x| {
let mut element = StandardListViewItem::default(); let path_as_string = x.to_string_lossy().to_string();
element.text = x.to_string_lossy().to_string().into(); IncludedDirectoriesModel {
element path: x.to_string_lossy().to_string().into(),
referenced_folder: referenced_as_string.contains(&path_as_string),
selected_row: false,
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
ModelRc::new(VecModel::from(new_folders_standard_list_view)) ModelRc::new(VecModel::from(converted))
}
pub fn create_excluded_directories_model_from_pathbuf(items: &[PathBuf]) -> ModelRc<ExcludedDirectoriesModel> {
let converted = items
.iter()
.map(|x| ExcludedDirectoriesModel {
path: x.to_string_lossy().to_string().into(),
selected_row: false,
})
.collect::<Vec<_>>();
ModelRc::new(VecModel::from(converted))
} }
pub fn create_vec_model_from_vec_string(items: Vec<String>) -> VecModel<SharedString> { pub fn create_vec_model_from_vec_string(items: Vec<String>) -> VecModel<SharedString> {
VecModel::from(items.into_iter().map(SharedString::from).collect::<Vec<_>>()) VecModel::from(items.into_iter().map(SharedString::from).collect::<Vec<_>>())
} }
// Workaround for https://github.com/slint-ui/slint/discussions/4596
// Currently there is no way to save u64 in slint, so we need to split it into two i32
pub fn split_u64_into_i32s(value: u64) -> (i32, i32) {
let part1: i32 = (value >> 32) as 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)
}
#[cfg(test)]
mod test {
use crate::common::split_u64_into_i32s;
#[test]
fn test_split_u64_into_i32s_small() {
let value = 1;
let (part1, part2) = split_u64_into_i32s(value);
assert_eq!(part1, 0);
assert_eq!(part2, 1);
}
#[test]
fn test_split_u64_into_i32s_big() {
let value = u64::MAX;
let (part1, part2) = split_u64_into_i32s(value);
assert_eq!(part1, -1);
assert_eq!(part2, -1);
}
#[test]
fn test_connect_i32_into_u64_small() {
let part1 = 0;
let part2 = 1;
let value = super::connect_i32_into_u64(part1, part2);
assert_eq!(value, 1);
}
#[test]
fn test_connect_i32_into_u64_big() {
let part1 = -1;
let part2 = -1;
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] {
let (part1, part2) = split_u64_into_i32s(start_value);
let end_value = super::connect_i32_into_u64(part1, part2);
assert_eq!(start_value, end_value);
}
}
}

View file

@ -1,9 +1,12 @@
use slint::{ComponentHandle, Model, ModelRc, VecModel};
use crate::common::{get_is_header_mode, get_name_idx, get_path_idx};
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow};
use czkawka_core::common::{remove_folder_if_contains_only_empty_folders, CHARACTER};
use rayon::prelude::*; use rayon::prelude::*;
use slint::{ComponentHandle, ModelRc, VecModel};
use czkawka_core::common::remove_folder_if_contains_only_empty_folders;
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};
pub fn connect_delete_button(app: &MainWindow) { pub fn connect_delete_button(app: &MainWindow) {
let a = app.as_weak(); let a = app.as_weak();
@ -12,263 +15,56 @@ pub fn connect_delete_button(app: &MainWindow) {
let active_tab = app.global::<GuiState>().get_active_tab(); let active_tab = app.global::<GuiState>().get_active_tab();
let model = match active_tab { let model = get_tool_model(&app, active_tab);
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"),
};
let new_model = handle_delete_items(&model, active_tab); let remove_to_trash = false;
let (errors, new_model) = handle_delete_items(&model, active_tab, remove_to_trash);
if let Some(new_model) = new_model { if let Some(new_model) = new_model {
match active_tab { set_tool_model(&app, active_tab, new_model);
CurrentTab::EmptyFolders => app.set_empty_folder_model(new_model),
CurrentTab::SimilarImages => app.set_similar_images_model(new_model),
CurrentTab::EmptyFiles => app.set_empty_files_model(new_model),
CurrentTab::Settings => panic!("Button should be disabled"),
}
} }
app.global::<GuiState>().set_info_text(Messages::new_from_errors(errors).create_messages_text().into());
app.global::<GuiState>().set_preview_visible(false); app.global::<GuiState>().set_preview_visible(false);
}); });
} }
fn handle_delete_items(items: &ModelRc<MainListModel>, active_tab: CurrentTab) -> Option<ModelRc<MainListModel>> { fn handle_delete_items(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)); let (entries_to_delete, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab));
if !entries_to_delete.is_empty() { if !entries_to_delete.is_empty() {
remove_selected_items(entries_to_delete, active_tab); 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); deselect_all_items(&mut entries_left);
let r = ModelRc::new(VecModel::from(entries_left)); // TODO here maybe should also stay old model if entries cannot be removed let r = ModelRc::new(VecModel::from(entries_left)); // TODO here maybe should also stay old model if entries cannot be removed
return Some(r); return (errors, Some(r));
} }
None (vec![], None)
} }
// TODO delete in parallel items, consider to add progress bar // 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 // 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 // and at the end should be send signal to main thread to update model
// TODO handle also situations where cannot delete file/folder // TODO handle also situations where cannot delete file/folder
fn remove_selected_items(items: Vec<MainListModel>, active_tab: CurrentTab) { fn remove_selected_items(items_to_remove: Vec<String>, active_tab: CurrentTab, remove_to_trash: bool) -> Vec<String> {
let path_idx = get_path_idx(active_tab);
let name_idx = get_name_idx(active_tab);
let items_to_remove = items
.iter()
.map(|item| {
let path = item.val.iter().nth(path_idx).unwrap();
let name = item.val.iter().nth(name_idx).unwrap();
format!("{}{}{}", path, CHARACTER, name)
})
.collect::<Vec<_>>();
// Iterate over empty folders and not delete them if they are not empty // Iterate over empty folders and not delete them if they are not empty
if active_tab == CurrentTab::EmptyFolders { if active_tab == CurrentTab::EmptyFolders {
items_to_remove.into_par_iter().for_each(|item| { items_to_remove
remove_folder_if_contains_only_empty_folders(item); .into_par_iter()
}); .filter_map(|item| remove_folder_if_contains_only_empty_folders(item, remove_to_trash).err())
.collect()
} else { } else {
items_to_remove.into_par_iter().for_each(|item| { items_to_remove
let _ = std::fs::remove_file(item); .into_par_iter()
}); .filter_map(|item| {
} if let Err(e) = std::fs::remove_file(item) {
} return Some(format!("Error while removing file: {e}"));
fn deselect_all_items(items: &mut [MainListModel]) {
for item in items {
item.selected_row = false;
}
}
fn filter_out_checked_items(items: &ModelRc<MainListModel>, have_header: bool) -> (Vec<MainListModel>, Vec<MainListModel>) {
if cfg!(debug_assertions) {
check_if_header_is_checked(items);
check_if_header_is_selected_but_should_not_be(items, have_header);
}
let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|item| item.checked);
// When have header, we must also throw out orphaned items - this needs to be
if have_header && !entries_left.is_empty() {
// First row must be header
assert!(entries_left[0].header_row);
if entries_left.len() == 3 {
// First row is header, so if second or third is also header, then there is no enough items to fill model
if entries_left[1].header_row || entries_left[2].header_row {
entries_left = Vec::new();
}
} else if entries_left.len() < 3 {
// Not have enough items to fill model
entries_left = Vec::new();
} else {
let mut last_header = 0;
let mut new_items: Vec<MainListModel> = Vec::new();
for i in 1..entries_left.len() {
if entries_left[i].header_row {
if i - last_header > 2 {
new_items.extend(entries_left[last_header..i].iter().cloned());
}
last_header = i;
} }
} None
if entries_left.len() - last_header > 2 { })
new_items.extend(entries_left[last_header..].iter().cloned()); .collect()
}
entries_left = new_items;
}
}
(entries_to_delete, entries_left)
}
// Function to verify if really headers are not checked
// Checked header is big bug
fn check_if_header_is_checked(items: &ModelRc<MainListModel>) {
if cfg!(debug_assertions) {
for item in items.iter() {
if item.header_row {
assert!(!item.checked);
}
}
}
}
// In some modes header should not be visible, but if are, then it is a bug
fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<MainListModel>, can_have_header: bool) {
if cfg!(debug_assertions) {
if !can_have_header {
for item in items.iter() {
assert!(!item.header_row);
}
}
}
}
#[cfg(test)]
mod tests {
use slint::{Model, ModelRc, SharedString, VecModel};
use crate::connect_delete::filter_out_checked_items;
use crate::MainListModel;
#[test]
fn test_filter_out_checked_items_empty() {
let items: ModelRc<MainListModel> = create_new_model(vec![]);
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert!(left.is_empty());
let (to_delete, left) = filter_out_checked_items(&items, true);
assert!(to_delete.is_empty());
assert!(left.is_empty());
}
#[test]
fn test_filter_out_checked_items_one_element_valid_normal() {
let items = create_new_model(vec![(false, false, false, vec![])]);
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert_eq!(left.len(), items.iter().count());
}
#[test]
fn test_filter_out_checked_items_one_element_valid_header() {
let items = create_new_model(vec![(false, true, false, vec![])]);
let (to_delete, left) = filter_out_checked_items(&items, true);
assert!(to_delete.is_empty());
assert!(left.is_empty());
}
#[test]
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_normal() {
let items = create_new_model(vec![(false, true, false, vec![])]);
filter_out_checked_items(&items, false);
}
#[test]
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_header() {
let items = create_new_model(vec![(false, false, false, vec![])]);
filter_out_checked_items(&items, true);
}
#[test]
fn test_filter_out_checked_items_multiple_element_valid_normal() {
let items = create_new_model(vec![
(false, false, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(true, false, false, vec!["4"]),
(false, false, false, vec!["5"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, false);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3", "4"]);
assert_eq!(left_data, vec!["1", "2", "5"]);
}
#[test]
fn test_filter_out_checked_items_multiple_element_valid_header() {
let items = create_new_model(vec![
(false, true, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(false, true, false, vec!["4"]),
(false, false, false, vec!["5"]),
(false, true, false, vec!["6"]),
(false, false, false, vec!["7"]),
(false, false, false, vec!["8"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["6", "7", "8"]);
}
#[test]
fn test_filter_out_checked_items_multiple2_element_valid_header() {
let items = create_new_model(vec![
(false, true, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(false, false, false, vec!["4"]),
(false, false, false, vec!["5"]),
(false, false, false, vec!["6"]),
(false, true, false, vec!["7"]),
(false, false, false, vec!["8"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_from_model(&to_delete);
let left_data = get_single_data_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]);
}
fn get_single_data_from_model(model: &[MainListModel]) -> Vec<String> {
let mut d = model.iter().map(|item| item.val.iter().next().unwrap().to_string()).collect::<Vec<_>>();
d.sort();
d
}
fn create_new_model(items: Vec<(bool, bool, bool, Vec<&'static str>)>) -> ModelRc<MainListModel> {
let model = VecModel::default();
for item in items {
let all_items: Vec<SharedString> = item.3.iter().map(|item| (*item).into()).collect::<Vec<_>>();
let all_items = VecModel::from(all_items);
model.push(MainListModel {
checked: item.0,
header_row: item.1,
selected_row: item.2,
val: ModelRc::new(all_items),
});
}
ModelRc::new(model)
} }
} }

View file

@ -1,7 +1,7 @@
use rfd::FileDialog; use rfd::FileDialog;
use slint::{ComponentHandle, Model, ModelRc, VecModel}; use slint::{ComponentHandle, Model, ModelRc, VecModel};
use crate::{Callabler, MainWindow, Settings}; use crate::{Callabler, ExcludedDirectoriesModel, IncludedDirectoriesModel, MainWindow, Settings};
pub fn connect_add_remove_directories(app: &MainWindow) { pub fn connect_add_remove_directories(app: &MainWindow) {
connect_add_directories(app); connect_add_directories(app);
@ -12,66 +12,67 @@ pub fn connect_add_remove_directories(app: &MainWindow) {
fn connect_add_manual_directories(app: &MainWindow) { fn connect_add_manual_directories(app: &MainWindow) {
let a = app.as_weak(); let a = app.as_weak();
app.global::<Callabler>().on_added_manual_directories(move |included_directories, list_of_files_to_add| { app.global::<Callabler>().on_added_manual_directories(move |included_directories, list_of_files_to_add| {
let non_empty_lines = list_of_files_to_add.lines().filter(|x| !x.is_empty()).collect::<Vec<_>>(); let folders = list_of_files_to_add.lines().filter(|x| !x.is_empty()).map(str::to_string).collect::<Vec<_>>();
if non_empty_lines.is_empty() { if folders.is_empty() {
return; return;
} }
let app = a.upgrade().unwrap(); let app = a.upgrade().unwrap();
let settings = app.global::<Settings>(); let settings = app.global::<Settings>();
if included_directories { if included_directories {
let included_model = settings.get_included_directories(); add_included_directories(&settings, &folders);
let mut included_model = included_model.iter().collect::<Vec<_>>();
included_model.extend(non_empty_lines.iter().map(|x| {
let mut element = slint::StandardListViewItem::default();
element.text = (*x).into();
element
}));
included_model.sort_by_cached_key(|x| x.text.to_string());
included_model.dedup();
settings.set_included_directories(ModelRc::new(VecModel::from(included_model)));
} else { } else {
let excluded_model = settings.get_excluded_directories(); add_excluded_directories(&settings, &folders);
let mut excluded_model = excluded_model.iter().collect::<Vec<_>>();
excluded_model.extend(non_empty_lines.iter().map(|x| {
let mut element = slint::StandardListViewItem::default();
element.text = (*x).into();
element
}));
excluded_model.sort_by_cached_key(|x| x.text.to_string());
excluded_model.dedup();
settings.set_excluded_directories(ModelRc::new(VecModel::from(excluded_model)));
} }
}); });
} }
fn connect_remove_directories(app: &MainWindow) { fn connect_remove_directories(app: &MainWindow) {
let a = app.as_weak(); let a = app.as_weak();
app.global::<Callabler>().on_remove_item_directories(move |included_directories, current_index| { app.global::<Callabler>().on_remove_item_directories(move |included_directories| {
let app = a.upgrade().unwrap();
let settings = app.global::<Settings>();
let current_index = if included_directories {
settings.get_included_directories_model_selected_idx()
} else {
settings.get_excluded_directories_model_selected_idx()
};
// Nothing selected // Nothing selected
if current_index == -1 { if current_index == -1 {
return; return;
} }
let app = a.upgrade().unwrap();
let settings = app.global::<Settings>();
if included_directories { if included_directories {
let included_model = settings.get_included_directories(); let included_model = settings.get_included_directories_model();
let model_count = included_model.iter().count(); let model_count = included_model.iter().count();
if model_count > current_index as usize { if model_count > current_index as usize {
let mut included_model = included_model.iter().collect::<Vec<_>>(); let mut included_model = included_model.iter().collect::<Vec<_>>();
included_model.remove(current_index as usize); included_model.remove(current_index as usize);
settings.set_included_directories(ModelRc::new(VecModel::from(included_model))); if current_index as usize != model_count - 1 {
included_model[current_index as usize].selected_row = true;
settings.set_included_directories_model_selected_idx(current_index);
} else {
settings.set_included_directories_model_selected_idx(-1);
}
settings.set_included_directories_model(ModelRc::new(VecModel::from(included_model)));
} }
} else { } else {
let excluded_model = settings.get_excluded_directories(); let excluded_model = settings.get_excluded_directories_model();
let model_count = excluded_model.iter().count(); let model_count = excluded_model.iter().count();
if model_count > current_index as usize { if model_count > current_index as usize {
let mut excluded_model = excluded_model.iter().collect::<Vec<_>>(); let mut excluded_model = excluded_model.iter().collect::<Vec<_>>();
excluded_model.remove(current_index as usize); excluded_model.remove(current_index as usize);
settings.set_excluded_directories(ModelRc::new(VecModel::from(excluded_model))); if current_index as usize != model_count - 1 {
excluded_model[current_index as usize].selected_row = true;
settings.set_excluded_directories_model_selected_idx(current_index);
} else {
settings.set_excluded_directories_model_selected_idx(-1);
}
settings.set_excluded_directories_model(ModelRc::new(VecModel::from(excluded_model)));
} }
} }
}); });
@ -89,33 +90,54 @@ fn connect_add_directories(app: &MainWindow) {
let Some(folders) = file_dialog.pick_folders() else { let Some(folders) = file_dialog.pick_folders() else {
return; return;
}; };
let folders = folders.iter().map(|x| x.to_string_lossy().to_string()).collect::<Vec<_>>();
let settings = app.global::<Settings>(); let settings = app.global::<Settings>();
let old_folders = if included_directories {
settings.get_included_directories()
} else {
settings.get_excluded_directories()
};
let mut new_folders = old_folders.iter().map(|x| x.text.to_string()).collect::<Vec<_>>();
new_folders.extend(folders.iter().map(|x| x.to_string_lossy().to_string()));
new_folders.sort();
new_folders.dedup();
let new_folders_standard_list_view = new_folders
.iter()
.map(|x| {
let mut element = slint::StandardListViewItem::default();
element.text = x.into();
element
})
.collect::<Vec<_>>();
let new_folders_model = ModelRc::new(VecModel::from(new_folders_standard_list_view));
if included_directories { if included_directories {
settings.set_included_directories(new_folders_model); add_included_directories(&settings, &folders);
} else { } else {
settings.set_excluded_directories(new_folders_model); add_excluded_directories(&settings, &folders);
} }
}); });
} }
fn add_included_directories(settings: &Settings, folders: &[String]) {
let old_folders = settings.get_included_directories_model();
let old_folders_path = old_folders.iter().map(|x| x.path.to_string()).collect::<Vec<_>>();
let mut new_folders = old_folders.iter().collect::<Vec<_>>();
let filtered_folders = folders.iter().filter(|x| !old_folders_path.contains(x)).collect::<Vec<_>>();
new_folders.iter_mut().for_each(|x| x.selected_row = false);
new_folders.extend(filtered_folders.iter().map(|path| IncludedDirectoriesModel {
path: (*path).into(),
referenced_folder: false,
selected_row: false,
}));
new_folders.sort_by_key(|x| x.path.clone());
let new_folders_model = ModelRc::new(VecModel::from(new_folders));
settings.set_included_directories_model(new_folders_model);
}
fn add_excluded_directories(settings: &Settings, folders: &[String]) {
let old_folders = settings.get_excluded_directories_model();
let old_folders_path = old_folders.iter().map(|x| x.path.to_string()).collect::<Vec<_>>();
let mut new_folders = old_folders.iter().collect::<Vec<_>>();
let filtered_folders = folders.iter().filter(|x| !old_folders_path.contains(x)).collect::<Vec<_>>();
new_folders.iter_mut().for_each(|x| x.selected_row = false);
new_folders.extend(filtered_folders.iter().map(|path| ExcludedDirectoriesModel {
path: (*path).into(),
selected_row: false,
}));
new_folders.sort_by_key(|x| x.path.clone());
let new_folders_model = ModelRc::new(VecModel::from(new_folders));
settings.set_excluded_directories_model(new_folders_model);
}

122
krokiet/src/connect_move.rs Normal file
View file

@ -0,0 +1,122 @@
use crate::common::{get_is_header_mode, get_tool_model, set_tool_model};
use crate::model_operations::{collect_path_name_from_model, deselect_all_items, filter_out_checked_items};
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow};
use czkawka_core::common_messages::Messages;
use rayon::prelude::*;
use rfd::FileDialog;
use slint::{ComponentHandle, ModelRc, VecModel};
use std::path::{Path, PathBuf};
use std::{fs, path};
pub fn connect_move(app: &MainWindow) {
let a = app.as_weak();
app.on_folders_move_choose_requested(move || {
let app = a.upgrade().unwrap();
let file_dialog = FileDialog::new();
let Some(folder) = file_dialog.pick_folder() else {
return;
};
let folder_str = folder.to_string_lossy().to_string();
app.invoke_show_move_folders_dialog(folder_str.into());
});
let a = app.as_weak();
app.global::<Callabler>().on_move_items(move |preserve_structure, copy_mode, output_folder| {
let app = a.upgrade().unwrap();
let active_tab = app.global::<GuiState>().get_active_tab();
let current_model = get_tool_model(&app, active_tab);
let (errors, new_model) = move_operation(&current_model, preserve_structure, copy_mode, &output_folder, active_tab);
if let Some(new_model) = new_model {
set_tool_model(&app, active_tab, new_model);
}
app.global::<GuiState>().set_info_text(Messages::new_from_errors(errors).create_messages_text().into());
});
}
fn move_operation(
items: &ModelRc<MainListModel>,
preserve_structure: bool,
copy_mode: bool,
output_folder: &str,
active_tab: CurrentTab,
) -> (Vec<String>, Option<ModelRc<MainListModel>>) {
let (entries_to_move, mut entries_left) = filter_out_checked_items(items, get_is_header_mode(active_tab));
if !entries_to_move.is_empty() {
let vec_items_to_move = collect_path_name_from_model(&entries_to_move, active_tab);
let errors = move_selected_items(vec_items_to_move, preserve_structure, copy_mode, output_folder);
deselect_all_items(&mut entries_left);
let r = ModelRc::new(VecModel::from(entries_left));
return (errors, Some(r));
}
(vec![], None)
}
fn move_selected_items(items_to_move: Vec<(String, String)>, preserve_structure: bool, copy_mode: bool, output_folder: &str) -> Vec<String> {
if let Err(err) = fs::create_dir_all(output_folder) {
return vec![format!("Error while creating folder: {err}")];
}
if copy_mode {
items_to_move
.into_par_iter()
.filter_map(|(path, name)| {
let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure);
if let Err(e) = fs::copy(&input_file, &output_file) {
return Some(format!("Error while copying file {input_file:?} to {output_file:?}, reason {e}"));
}
None
})
.collect()
} else {
items_to_move
.into_par_iter()
.filter_map(|(path, name)| {
let (input_file, output_file) = collect_path_and_create_folders(&path, &name, output_folder, preserve_structure);
if output_file.exists() {
return Some(format!("File {output_file:?} already exists"));
}
// Try to rename file, may fail due various reasons
if fs::rename(&input_file, &output_file).is_ok() {
return None;
}
// It is possible that this failed, because file is on different partition, so
// we need to copy file and then remove old
if let Err(e) = fs::copy(&input_file, &output_file) {
return Some(format!(
"Error while copying file {input_file:?} to {output_file:?}(moving into different partition), reason {e}"
));
}
if let Err(e) = fs::remove_file(&input_file) {
return Some(format!("Error while removing file {input_file:?}(after copying into different partition), reason {e}"));
}
None
})
.collect()
}
}
// Create input/output paths, and create output folder
fn collect_path_and_create_folders(input_path: &str, input_file: &str, output_path: &str, preserve_structure: bool) -> (PathBuf, PathBuf) {
let mut input_full_path = PathBuf::from(input_path);
input_full_path.push(input_file);
let mut output_full_path = PathBuf::from(output_path);
if preserve_structure {
output_full_path.extend(Path::new(input_path).components().filter(|c| matches!(c, path::Component::Normal(_))));
};
let _ = fs::create_dir_all(&output_full_path);
output_full_path.push(input_file);
(input_full_path, output_full_path)
}

View file

@ -1,8 +1,9 @@
use crate::{Callabler, MainWindow};
use directories_next::ProjectDirs; use directories_next::ProjectDirs;
use log::error; use log::error;
use slint::ComponentHandle; use slint::ComponentHandle;
use crate::{Callabler, MainWindow};
pub fn connect_open_items(app: &MainWindow) { pub fn connect_open_items(app: &MainWindow) {
app.global::<Callabler>().on_item_opened(move |path| { app.global::<Callabler>().on_item_opened(move |path| {
match open::that(&*path) { match open::that(&*path) {

View file

@ -1,9 +1,11 @@
use crate::{MainWindow, ProgressToSend}; use std::thread;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use czkawka_core::common_dir_traversal::{ProgressData, ToolType};
use slint::ComponentHandle; use slint::ComponentHandle;
use std::thread;
use czkawka_core::common_dir_traversal::{ProgressData, ToolType};
use crate::{MainWindow, ProgressToSend};
pub fn connect_progress_gathering(app: &MainWindow, progress_receiver: Receiver<ProgressData>) { pub fn connect_progress_gathering(app: &MainWindow, progress_receiver: Receiver<ProgressData>) {
let a = app.as_weak(); let a = app.as_weak();

View file

@ -1,7 +1,12 @@
use crate::settings::{collect_settings, SettingsCustom, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES}; use std::rc::Rc;
use crate::{CurrentTab, GuiState, MainListModel, MainWindow, ProgressToSend}; use std::thread;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
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::{split_path, split_path_compare, DEFAULT_THREAD_SIZE};
use czkawka_core::common_dir_traversal::{FileEntry, ProgressData}; use czkawka_core::common_dir_traversal::{FileEntry, ProgressData};
use czkawka_core::common_tool::CommonData; use czkawka_core::common_tool::CommonData;
@ -9,12 +14,11 @@ use czkawka_core::common_traits::ResultEntry;
use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_files::EmptyFiles;
use czkawka_core::empty_folder::{EmptyFolder, FolderEntry}; use czkawka_core::empty_folder::{EmptyFolder, FolderEntry};
use czkawka_core::similar_images; use czkawka_core::similar_images;
use czkawka_core::similar_images::SimilarImages; use czkawka_core::similar_images::{ImagesEntry, SimilarImages};
use humansize::{format_size, BINARY};
use rayon::prelude::*; use crate::common::split_u64_into_i32s;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel, Weak}; use crate::settings::{collect_settings, SettingsCustom, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES};
use std::rc::Rc; use crate::{CurrentTab, GuiState, MainListModel, MainWindow, ProgressToSend};
use std::thread;
pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>) { pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>) {
let a = app.as_weak(); let a = app.as_weak();
@ -47,7 +51,6 @@ pub fn connect_scan_button(app: &MainWindow, progress_sender: Sender<ProgressDat
}); });
} }
// TODO handle referenced folders
fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) { fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
@ -67,49 +70,88 @@ fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData
.expect("Hash type not found") .expect("Hash type not found")
.2; .2;
finder.set_hash_alg(hash_type); 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_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.set_similarity(custom_settings.similar_images_sub_similarity as u32);
finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender)); finder.find_similar_images(Some(&stop_receiver), Some(&progress_sender));
let mut vector = finder.get_similar_images().clone(); if finder.get_use_reference() {
let messages = finder.get_text_messages().create_messages_text(); let mut vector = finder.get_similar_images_referenced().clone();
let messages = finder.get_text_messages().create_messages_text();
for vec_fe in &mut vector { let hash_size = custom_settings.similar_images_sub_hash_size;
vec_fe.par_sort_unstable_by_key(|e| e.similarity);
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_referenced(&app, vector, messages, hash_size);
})
} else {
let mut vector = finder.get_similar_images().clone();
let messages = finder.get_text_messages().create_messages_text();
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);
}
a.upgrade_in_event_loop(move |app| {
write_similar_images_results(&app, vector, messages, hash_size);
})
} }
let hash_size = custom_settings.similar_images_sub_hash_size;
a.upgrade_in_event_loop(move |app| {
write_similar_images_results(&app, vector, messages, hash_size);
})
}) })
.unwrap(); .unwrap();
} }
fn write_similar_images_results(app: &MainWindow, vector: Vec<Vec<similar_images::ImagesEntry>>, messages: String, hash_size: u8) { fn write_similar_images_results_referenced(app: &MainWindow, vector: Vec<(ImagesEntry, Vec<ImagesEntry>)>, messages: String, hash_size: u8) {
let items_found = vector.len(); let items_found = vector.len();
let items = Rc::new(VecModel::default()); let items = Rc::new(VecModel::default());
for vec_fe in vector { for (ref_fe, vec_fe) in vector {
insert_data_to_model(&items, ModelRc::new(VecModel::default()), true); let (data_model_str, data_model_int) = prepare_data_model_similar_images(&ref_fe, hash_size);
for fe in vec_fe { insert_data_to_model(&items, data_model_str, data_model_int, true);
let (directory, file) = split_path(fe.get_path());
let data_model = VecModel::from_slice(&[
similar_images::get_string_from_similarity(&fe.similarity, hash_size).into(),
format_size(fe.size, BINARY).into(),
fe.dimensions.clone().into(),
file.into(),
directory.into(),
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
]);
insert_data_to_model(&items, data_model, 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);
} }
} }
app.set_similar_images_model(items.into()); app.set_similar_images_model(items.into());
app.invoke_scan_ended(format!("Found {items_found} similar images files").into()); app.invoke_scan_ended(format!("Found {items_found} similar images files").into());
app.global::<GuiState>().set_info_text(messages.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>) {
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) { fn scan_empty_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
@ -133,20 +175,28 @@ fn write_empty_files_results(app: &MainWindow, vector: Vec<FileEntry>, messages:
let items_found = vector.len(); let items_found = vector.len();
let items = Rc::new(VecModel::default()); let items = Rc::new(VecModel::default());
for fe in vector { for fe in vector {
let (directory, file) = split_path(fe.get_path()); let (data_model_str, data_model_int) = prepare_data_model_empty_files(&fe);
let data_model = VecModel::from_slice(&[ insert_data_to_model(&items, data_model_str, data_model_int, false);
file.into(),
directory.into(),
NaiveDateTime::from_timestamp_opt(fe.get_modified_date() as i64, 0).unwrap().to_string().into(),
]);
insert_data_to_model(&items, data_model, false);
} }
app.set_empty_files_model(items.into()); app.set_empty_files_model(items.into());
app.invoke_scan_ended(format!("Found {items_found} empty files").into()); app.invoke_scan_ended(format!("Found {items_found} empty files").into());
app.global::<GuiState>().set_info_text(messages.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)
}
////////////////////////////////////////// Empty Folders
fn scan_empty_folders(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) { fn scan_empty_folders(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>, stop_receiver: Receiver<()>, custom_settings: SettingsCustom) {
thread::Builder::new() thread::Builder::new()
.stack_size(DEFAULT_THREAD_SIZE) .stack_size(DEFAULT_THREAD_SIZE)
@ -170,26 +220,34 @@ fn write_empty_folders_results(app: &MainWindow, vector: Vec<FolderEntry>, messa
let items_found = vector.len(); let items_found = vector.len();
let items = Rc::new(VecModel::default()); let items = Rc::new(VecModel::default());
for fe in vector { for fe in vector {
let (directory, file) = split_path(&fe.path); let (data_model_str, data_model_int) = prepare_data_model_empty_folders(&fe);
let data_model = VecModel::from_slice(&[ insert_data_to_model(&items, data_model_str, data_model_int, false);
file.into(),
directory.into(),
NaiveDateTime::from_timestamp_opt(fe.modified_date as i64, 0).unwrap().to_string().into(),
]);
insert_data_to_model(&items, data_model, false);
} }
app.set_empty_folder_model(items.into()); app.set_empty_folder_model(items.into());
app.invoke_scan_ended(format!("Found {items_found} empty folders").into()); app.invoke_scan_ended(format!("Found {items_found} empty folders").into());
app.global::<GuiState>().set_info_text(messages.into()); app.global::<GuiState>().set_info_text(messages.into());
} }
fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model: ModelRc<SharedString>, header_row: bool) { fn prepare_data_model_empty_folders(fe: &FolderEntry) -> (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)
}
////////////////////////////////////////// Common
fn insert_data_to_model(items: &Rc<VecModel<MainListModel>>, data_model_str: ModelRc<SharedString>, data_model_int: ModelRc<i32>, header_row: bool) {
let main = MainListModel { let main = MainListModel {
checked: false, checked: false,
header_row, header_row,
selected_row: false, selected_row: false,
val: ModelRc::new(data_model), val_str: ModelRc::new(data_model_str),
val_int: ModelRc::new(data_model_int),
}; };
items.push(main); items.push(main);
} }
@ -199,11 +257,13 @@ where
T: CommonData, T: CommonData,
{ {
component.set_included_directory(custom_settings.included_directories.clone()); component.set_included_directory(custom_settings.included_directories.clone());
component.set_reference_directory(custom_settings.included_directories_referenced.clone());
component.set_excluded_directory(custom_settings.excluded_directories.clone()); component.set_excluded_directory(custom_settings.excluded_directories.clone());
component.set_recursive_search(custom_settings.recursive_search); component.set_recursive_search(custom_settings.recursive_search);
component.set_minimal_file_size(custom_settings.minimum_file_size as u64 * 1024); component.set_minimal_file_size(custom_settings.minimum_file_size as u64 * 1024);
component.set_maximal_file_size(custom_settings.maximum_file_size as u64 * 1024); component.set_maximal_file_size(custom_settings.maximum_file_size as u64 * 1024);
component.set_allowed_extensions(custom_settings.allowed_extensions.clone()); component.set_allowed_extensions(custom_settings.allowed_extensions.clone());
component.set_excluded_extensions(custom_settings.excluded_extensions.clone());
component.set_excluded_items(custom_settings.excluded_items.split(',').map(str::to_string).collect()); component.set_excluded_items(custom_settings.excluded_items.split(',').map(str::to_string).collect());
component.set_exclude_other_filesystems(custom_settings.ignore_other_file_systems); component.set_exclude_other_filesystems(custom_settings.ignore_other_file_systems);
component.set_use_cache(custom_settings.use_cache); component.set_use_cache(custom_settings.use_cache);

View file

@ -0,0 +1,216 @@
use crate::common::{
connect_i32_into_u64, get_int_height_idx, get_int_modification_date_idx, get_int_size_idx, get_int_width_idx, get_is_header_mode, get_tool_model, set_tool_model,
};
use crate::{Callabler, CurrentTab, GuiState, MainListModel, MainWindow, SelectMode, SelectModel};
use slint::{ComponentHandle, Model, ModelRc, VecModel};
// TODO optimize this, not sure if it is possible to not copy entire model to just select item
// https://github.com/slint-ui/slint/discussions/4595
pub fn connect_select(app: &MainWindow) {
let a = app.as_weak();
app.global::<Callabler>().on_select_items(move |select_mode| {
let app = a.upgrade().unwrap();
let active_tab = app.global::<GuiState>().get_active_tab();
let current_model = get_tool_model(&app, active_tab);
let new_model = match select_mode {
SelectMode::SelectAll => select_all(current_model),
SelectMode::UnselectAll => deselect_all(current_model),
SelectMode::InvertSelection => invert_selection(current_model),
SelectMode::SelectTheBiggestSize => select_by_size_date(current_model, active_tab, true, true),
SelectMode::SelectTheSmallestSize => select_by_size_date(current_model, active_tab, false, true),
SelectMode::SelectTheBiggestResolution => select_by_resolution(current_model, active_tab, true),
SelectMode::SelectTheSmallestResolution => select_by_resolution(current_model, active_tab, false),
SelectMode::SelectNewest => select_by_size_date(current_model, active_tab, true, false),
SelectMode::SelectOldest => select_by_size_date(current_model, active_tab, false, false),
};
set_tool_model(&app, active_tab, new_model);
});
}
pub fn connect_showing_proper_select_buttons(app: &MainWindow) {
set_select_buttons(app);
let a = app.as_weak();
app.global::<Callabler>().on_tab_changed(move || {
let app = a.upgrade().unwrap();
set_select_buttons(&app);
});
}
fn set_select_buttons(app: &MainWindow) {
let active_tab = app.global::<GuiState>().get_active_tab();
let mut base_buttons = vec![SelectMode::SelectAll, SelectMode::UnselectAll, SelectMode::InvertSelection];
let additional_buttons = match active_tab {
CurrentTab::SimilarImages => vec![
SelectMode::SelectOldest,
SelectMode::SelectNewest,
SelectMode::SelectTheSmallestSize,
SelectMode::SelectTheBiggestSize,
SelectMode::SelectTheSmallestResolution,
SelectMode::SelectTheBiggestResolution,
],
_ => vec![],
};
base_buttons.extend(additional_buttons);
base_buttons.reverse();
let new_select_model = base_buttons
.into_iter()
.map(|e| SelectModel {
name: translate_select_mode(e).into(),
data: e,
})
.collect::<Vec<_>>();
app.global::<GuiState>().set_select_results_list(ModelRc::new(VecModel::from(new_select_model)));
}
fn translate_select_mode(select_mode: SelectMode) -> String {
match select_mode {
SelectMode::SelectAll => "Select all".into(),
SelectMode::UnselectAll => "Unselect all".into(),
SelectMode::InvertSelection => "Invert selection".into(),
SelectMode::SelectTheBiggestSize => "Select the biggest size".into(),
SelectMode::SelectTheBiggestResolution => "Select the biggest resolution".into(),
SelectMode::SelectTheSmallestSize => "Select the smallest size".into(),
SelectMode::SelectTheSmallestResolution => "Select the smallest resolution".into(),
SelectMode::SelectNewest => "Select newest".into(),
SelectMode::SelectOldest => "Select oldest".into(),
}
}
// TODO, when model will be able to contain i64 instead two i32, this function could be merged with select_by_size_date
fn select_by_resolution(model: ModelRc<MainListModel>, active_tab: CurrentTab, biggest: bool) -> ModelRc<MainListModel> {
let is_header_mode = get_is_header_mode(active_tab);
assert!(is_header_mode); // non header modes not really have reason to use this function
let mut old_data = model.iter().collect::<Vec<_>>();
let headers_idx = find_header_idx_and_deselect_all(&mut old_data);
let width_idx = get_int_width_idx(active_tab);
let height_idx = get_int_height_idx(active_tab);
if biggest {
for i in 0..(headers_idx.len() - 1) {
let mut max_item = 0;
let mut max_item_idx = 0;
#[allow(clippy::needless_range_loop)]
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let item = int_data[width_idx] * int_data[height_idx];
if item > max_item {
max_item = item;
max_item_idx = j;
}
}
old_data[max_item_idx].checked = true;
}
} else {
for i in 0..(headers_idx.len() - 1) {
let mut min_item = u64::MAX;
let mut min_item_idx = 0;
#[allow(clippy::needless_range_loop)]
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let item = (int_data[width_idx] * int_data[height_idx]) as u64;
if item < min_item {
min_item = item;
min_item_idx = j;
}
}
old_data[min_item_idx].checked = true;
}
}
ModelRc::new(VecModel::from(old_data))
}
fn select_by_size_date(model: ModelRc<MainListModel>, active_tab: CurrentTab, biggest_newest: bool, size: bool) -> ModelRc<MainListModel> {
let is_header_mode = get_is_header_mode(active_tab);
assert!(is_header_mode); // non header modes not really have reason to use this function
let mut old_data = model.iter().collect::<Vec<_>>();
let headers_idx = find_header_idx_and_deselect_all(&mut old_data);
let item_idx = if size {
get_int_size_idx(active_tab)
} else {
get_int_modification_date_idx(active_tab)
};
if biggest_newest {
for i in 0..(headers_idx.len() - 1) {
let mut max_item = 0;
let mut max_item_idx = 0;
#[allow(clippy::needless_range_loop)]
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let item = connect_i32_into_u64(int_data[item_idx], int_data[item_idx + 1]);
if item > max_item {
max_item = item;
max_item_idx = j;
}
}
old_data[max_item_idx].checked = true;
}
} else {
for i in 0..(headers_idx.len() - 1) {
let mut min_item = u64::MAX;
let mut min_item_idx = 0;
#[allow(clippy::needless_range_loop)]
for j in (headers_idx[i] + 1)..headers_idx[i + 1] {
let int_data = old_data[j].val_int.iter().collect::<Vec<_>>();
let item = connect_i32_into_u64(int_data[item_idx], int_data[item_idx + 1]);
if item < min_item {
min_item = item;
min_item_idx = j;
}
}
old_data[min_item_idx].checked = true;
}
}
ModelRc::new(VecModel::from(old_data))
}
fn select_all(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
let mut old_data = model.iter().collect::<Vec<_>>();
for x in &mut old_data {
if !x.header_row {
x.checked = true;
}
}
ModelRc::new(VecModel::from(old_data))
}
fn deselect_all(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
let mut old_data = model.iter().collect::<Vec<_>>();
old_data.iter_mut().for_each(|x| x.checked = false);
ModelRc::new(VecModel::from(old_data))
}
fn invert_selection(model: ModelRc<MainListModel>) -> ModelRc<MainListModel> {
let mut old_data = model.iter().collect::<Vec<_>>();
for x in &mut old_data {
if !x.header_row {
x.checked = !x.checked;
}
}
ModelRc::new(VecModel::from(old_data))
}
fn find_header_idx_and_deselect_all(old_data: &mut [MainListModel]) -> Vec<usize> {
let mut header_idx = old_data
.iter()
.enumerate()
.filter_map(|(idx, m)| if m.header_row { Some(idx) } else { None })
.collect::<Vec<_>>();
header_idx.push(old_data.len());
for x in old_data.iter_mut() {
if !x.header_row {
x.checked = false;
}
}
header_idx
}

View file

@ -1,10 +1,13 @@
use crate::{Callabler, GuiState, MainWindow}; use std::path::Path;
use czkawka_core::common::{get_dynamic_image_from_raw_image, IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS}; use std::time::{Duration, Instant};
use image::DynamicImage; use image::DynamicImage;
use log::{debug, error}; use log::{debug, error};
use slint::ComponentHandle; use slint::ComponentHandle;
use std::path::Path;
use std::time::{Duration, Instant}; use czkawka_core::common::{get_dynamic_image_from_raw_image, IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS};
use crate::{Callabler, CurrentTab, GuiState, MainWindow, Settings};
pub type ImageBufferRgba = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>; pub type ImageBufferRgba = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
@ -13,6 +16,21 @@ pub fn connect_show_preview(app: &MainWindow) {
app.global::<Callabler>().on_load_image_preview(move |image_path| { app.global::<Callabler>().on_load_image_preview(move |image_path| {
let app = a.upgrade().unwrap(); let app = a.upgrade().unwrap();
let settings = app.global::<Settings>();
let gui_state = app.global::<GuiState>();
let active_tab = gui_state.get_active_tab();
if active_tab == CurrentTab::SimilarImages && !settings.get_similar_images_show_image_preview() {
set_preview_visible(&gui_state, None);
return;
}
// Do not load the same image again
if image_path == gui_state.get_preview_image_path() {
return;
}
let path = Path::new(image_path.as_str()); let path = Path::new(image_path.as_str());
let res = load_image(path); let res = load_image(path);
@ -22,20 +40,30 @@ pub fn connect_show_preview(app: &MainWindow) {
let convert_time = start_timer_convert_time.elapsed(); let convert_time = start_timer_convert_time.elapsed();
let start_set_time = Instant::now(); let start_set_time = Instant::now();
app.global::<GuiState>().set_preview_image(slint_image); gui_state.set_preview_image(slint_image);
let set_time = start_set_time.elapsed(); let set_time = start_set_time.elapsed();
debug!( debug!(
"Loading image took: {:?}, converting image took: {:?}, setting image took: {:?}", "Loading image took: {:?}, converting image took: {:?}, setting image took: {:?}",
load_time, convert_time, set_time load_time, convert_time, set_time
); );
app.global::<GuiState>().set_preview_visible(true); set_preview_visible(&gui_state, Some(image_path.as_str()));
} else { } else {
app.global::<GuiState>().set_preview_visible(false); set_preview_visible(&gui_state, None);
} }
}); });
} }
fn set_preview_visible(gui_state: &GuiState, preview: Option<&str>) {
if let Some(preview) = preview {
gui_state.set_preview_image_path(preview.into());
gui_state.set_preview_visible(true);
} else {
gui_state.set_preview_image_path("".into());
gui_state.set_preview_visible(false);
}
}
fn convert_into_slint_image(img: DynamicImage) -> slint::Image { fn convert_into_slint_image(img: DynamicImage) -> slint::Image {
let image_buffer: ImageBufferRgba = img.to_rgba8(); let image_buffer: ImageBufferRgba = img.to_rgba8();
let buffer = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(image_buffer.as_raw(), image_buffer.width(), image_buffer.height()); let buffer = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(image_buffer.as_raw(), image_buffer.width(), image_buffer.height());

View file

@ -1,6 +1,7 @@
use crate::MainWindow;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use crate::MainWindow;
pub fn connect_stop_button(app: &MainWindow, stop_sender: Sender<()>) { pub fn connect_stop_button(app: &MainWindow, stop_sender: Sender<()>) {
app.on_scan_stopping(move || { app.on_scan_stopping(move || {
stop_sender.send(()).unwrap(); stop_sender.send(()).unwrap();

View file

@ -1,7 +1,9 @@
use std::collections::HashMap;
use slint::{ComponentHandle, Model};
use crate::localizer_krokiet::LANGUAGE_LOADER_GUI; use crate::localizer_krokiet::LANGUAGE_LOADER_GUI;
use crate::{Callabler, MainWindow}; use crate::{Callabler, MainWindow};
use slint::{ComponentHandle, Model};
use std::collections::HashMap;
pub fn connect_translations(app: &MainWindow) { pub fn connect_translations(app: &MainWindow) {
app.global::<Callabler>().on_translate(move |text_to_translate, args| { app.global::<Callabler>().on_translate(move |text_to_translate, args| {

View file

@ -17,38 +17,42 @@
#![allow(clippy::items_after_statements)] // Generated code #![allow(clippy::items_after_statements)] // Generated code
#![allow(clippy::match_same_arms)] // Generated code #![allow(clippy::match_same_arms)] // Generated code
mod common; use std::rc::Rc;
mod connect_delete;
mod connect_directories_changes;
mod connect_open;
mod connect_progress_receiver;
mod connect_scan;
mod connect_show_preview;
mod connect_stop;
mod connect_translation;
mod localizer_krokiet;
mod set_initial_gui_info;
mod settings;
use crossbeam_channel::{unbounded, Receiver, Sender}; use crossbeam_channel::{unbounded, Receiver, Sender};
use slint::VecModel; use slint::VecModel;
use std::rc::Rc;
// use std::rc::Rc; use czkawka_core::common::{print_version_mode, setup_logger};
use czkawka_core::common_dir_traversal::ProgressData;
use crate::connect_delete::connect_delete_button; use crate::connect_delete::connect_delete_button;
use crate::connect_open::connect_open_items;
use crate::connect_scan::connect_scan_button;
use crate::connect_directories_changes::connect_add_remove_directories; use crate::connect_directories_changes::connect_add_remove_directories;
use crate::connect_move::connect_move;
use crate::connect_open::connect_open_items;
use crate::connect_progress_receiver::connect_progress_gathering; use crate::connect_progress_receiver::connect_progress_gathering;
use crate::connect_scan::connect_scan_button;
use crate::connect_select::{connect_select, connect_showing_proper_select_buttons};
use crate::connect_show_preview::connect_show_preview; use crate::connect_show_preview::connect_show_preview;
use crate::connect_stop::connect_stop_button; use crate::connect_stop::connect_stop_button;
use crate::connect_translation::connect_translations; use crate::connect_translation::connect_translations;
use crate::set_initial_gui_info::set_initial_gui_infos; use crate::set_initial_gui_info::set_initial_gui_infos;
use crate::settings::{connect_changing_settings_preset, create_default_settings_files, load_settings_from_file, save_all_settings_to_file}; use crate::settings::{connect_changing_settings_preset, create_default_settings_files, load_settings_from_file, save_all_settings_to_file};
use czkawka_core::common::{print_version_mode, setup_logger};
use czkawka_core::common_dir_traversal::ProgressData; mod common;
// use slint::{ModelRc, VecModel}; mod connect_delete;
mod connect_directories_changes;
mod connect_move;
mod connect_open;
mod connect_progress_receiver;
mod connect_scan;
mod connect_select;
mod connect_show_preview;
mod connect_stop;
mod connect_translation;
mod localizer_krokiet;
mod model_operations;
mod set_initial_gui_info;
mod settings;
slint::include_modules!(); slint::include_modules!();
fn main() { fn main() {
@ -80,6 +84,9 @@ fn main() {
connect_show_preview(&app); connect_show_preview(&app);
connect_translations(&app); connect_translations(&app);
connect_changing_settings_preset(&app); connect_changing_settings_preset(&app);
connect_select(&app);
connect_showing_proper_select_buttons(&app);
connect_move(&app);
app.run().unwrap(); app.run().unwrap();

View file

@ -0,0 +1,238 @@
use crate::common::{get_str_name_idx, get_str_path_idx};
use crate::{CurrentTab, MainListModel};
use slint::{Model, ModelRc};
use std::path::MAIN_SEPARATOR;
pub fn deselect_all_items(items: &mut [MainListModel]) {
for item in items {
item.selected_row = false;
}
}
#[allow(unused)]
pub fn select_all_items(items: &mut [MainListModel]) {
for item in items {
item.selected_row = true;
}
}
pub fn collect_full_path_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<String> {
let path_idx = get_str_path_idx(active_tab);
let name_idx = get_str_name_idx(active_tab);
items
.iter()
.map(|item| {
let path = item.val_str.iter().nth(path_idx).unwrap();
let name = item.val_str.iter().nth(name_idx).unwrap();
format!("{}{}{}", path, MAIN_SEPARATOR, name)
})
.collect::<Vec<_>>()
}
pub fn collect_path_name_from_model(items: &[MainListModel], active_tab: CurrentTab) -> Vec<(String, String)> {
let path_idx = get_str_path_idx(active_tab);
let name_idx = get_str_name_idx(active_tab);
items
.iter()
.map(|item| {
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(),
)
})
.collect::<Vec<_>>()
}
pub fn filter_out_checked_items(items: &ModelRc<MainListModel>, have_header: bool) -> (Vec<MainListModel>, Vec<MainListModel>) {
if cfg!(debug_assertions) {
check_if_header_is_checked(items);
check_if_header_is_selected_but_should_not_be(items, have_header);
}
let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|item| item.checked);
// When have header, we must also throw out orphaned items - this needs to be
if have_header && !entries_left.is_empty() {
// First row must be header
assert!(entries_left[0].header_row);
if entries_left.len() == 3 {
// First row is header, so if second or third is also header, then there is no enough items to fill model
if entries_left[1].header_row || entries_left[2].header_row {
entries_left = Vec::new();
}
} else if entries_left.len() < 3 {
// Not have enough items to fill model
entries_left = Vec::new();
} else {
let mut last_header = 0;
let mut new_items: Vec<MainListModel> = Vec::new();
for i in 1..entries_left.len() {
if entries_left[i].header_row {
if i - last_header > 2 {
new_items.extend(entries_left[last_header..i].iter().cloned());
}
last_header = i;
}
}
if entries_left.len() - last_header > 2 {
new_items.extend(entries_left[last_header..].iter().cloned());
}
entries_left = new_items;
}
}
(entries_to_delete, entries_left)
}
// Function to verify if really headers are not checked
// Checked header is big bug
fn check_if_header_is_checked(items: &ModelRc<MainListModel>) {
if cfg!(debug_assertions) {
for item in items.iter() {
if item.header_row {
assert!(!item.checked);
}
}
}
}
// In some modes header should not be visible, but if are, then it is a bug
fn check_if_header_is_selected_but_should_not_be(items: &ModelRc<MainListModel>, can_have_header: bool) {
if cfg!(debug_assertions) {
if !can_have_header {
for item in items.iter() {
assert!(!item.header_row);
}
}
}
}
#[cfg(test)]
mod tests {
use slint::{Model, ModelRc, SharedString, VecModel};
use crate::model_operations::filter_out_checked_items;
use crate::MainListModel;
#[test]
fn test_filter_out_checked_items_empty() {
let items: ModelRc<MainListModel> = create_new_model(vec![]);
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert!(left.is_empty());
let (to_delete, left) = filter_out_checked_items(&items, true);
assert!(to_delete.is_empty());
assert!(left.is_empty());
}
#[test]
fn test_filter_out_checked_items_one_element_valid_normal() {
let items = create_new_model(vec![(false, false, false, vec![])]);
let (to_delete, left) = filter_out_checked_items(&items, false);
assert!(to_delete.is_empty());
assert_eq!(left.len(), items.iter().count());
}
#[test]
fn test_filter_out_checked_items_one_element_valid_header() {
let items = create_new_model(vec![(false, true, false, vec![])]);
let (to_delete, left) = filter_out_checked_items(&items, true);
assert!(to_delete.is_empty());
assert!(left.is_empty());
}
#[test]
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_normal() {
let items = create_new_model(vec![(false, true, false, vec![])]);
filter_out_checked_items(&items, false);
}
#[test]
#[should_panic]
fn test_filter_out_checked_items_one_element_invalid_header() {
let items = create_new_model(vec![(false, false, false, vec![])]);
filter_out_checked_items(&items, true);
}
#[test]
fn test_filter_out_checked_items_multiple_element_valid_normal() {
let items = create_new_model(vec![
(false, false, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(true, false, false, vec!["4"]),
(false, false, false, vec!["5"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, false);
let to_delete_data = get_single_data_str_from_model(&to_delete);
let left_data = get_single_data_str_from_model(&left);
assert_eq!(to_delete_data, vec!["3", "4"]);
assert_eq!(left_data, vec!["1", "2", "5"]);
}
#[test]
fn test_filter_out_checked_items_multiple_element_valid_header() {
let items = create_new_model(vec![
(false, true, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(false, true, false, vec!["4"]),
(false, false, false, vec!["5"]),
(false, true, false, vec!["6"]),
(false, false, false, vec!["7"]),
(false, false, false, vec!["8"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_str_from_model(&to_delete);
let left_data = get_single_data_str_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["6", "7", "8"]);
}
#[test]
fn test_filter_out_checked_items_multiple2_element_valid_header() {
let items = create_new_model(vec![
(false, true, false, vec!["1"]),
(false, false, false, vec!["2"]),
(true, false, false, vec!["3"]),
(false, false, false, vec!["4"]),
(false, false, false, vec!["5"]),
(false, false, false, vec!["6"]),
(false, true, false, vec!["7"]),
(false, false, false, vec!["8"]),
]);
let (to_delete, left) = filter_out_checked_items(&items, true);
let to_delete_data = get_single_data_str_from_model(&to_delete);
let left_data = get_single_data_str_from_model(&left);
assert_eq!(to_delete_data, vec!["3"]);
assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]);
}
fn get_single_data_str_from_model(model: &[MainListModel]) -> Vec<String> {
let mut d = model.iter().map(|item| item.val_str.iter().next().unwrap().to_string()).collect::<Vec<_>>();
d.sort();
d
}
fn create_new_model(items: Vec<(bool, bool, bool, Vec<&'static str>)>) -> ModelRc<MainListModel> {
let model = VecModel::default();
for item in items {
let all_items: Vec<SharedString> = item.3.iter().map(|item| (*item).into()).collect::<Vec<_>>();
let all_items = VecModel::from(all_items);
model.push(MainListModel {
checked: item.0,
header_row: item.1,
selected_row: item.2,
val_str: ModelRc::new(all_items),
val_int: ModelRc::new(VecModel::default()),
});
}
ModelRc::new(model)
}
}

View file

@ -1,13 +1,14 @@
use czkawka_core::common::get_available_threads;
use slint::{ComponentHandle, SharedString, VecModel}; 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_HASH_SIZE_VALUES, ALLOWED_HASH_TYPE_VALUES, ALLOWED_RESIZE_ALGORITHM_VALUES};
use crate::{GuiState, MainWindow, Settings}; use crate::{GuiState, MainWindow, Settings};
// Some info needs to be send to gui at the start like available thread number in OS. // 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) { pub fn set_initial_gui_infos(app: &MainWindow) {
let threads = get_available_threads(); let threads = get_all_available_threads();
let settings = app.global::<Settings>(); let settings = app.global::<Settings>();
app.global::<GuiState>().set_maximum_threads(threads as f32); app.global::<GuiState>().set_maximum_threads(threads as f32);

View file

@ -9,10 +9,10 @@ use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slint::{ComponentHandle, Model, ModelRc}; use slint::{ComponentHandle, Model, ModelRc};
use czkawka_core::common::{get_available_threads, set_number_of_threads}; use czkawka_core::common::{get_all_available_threads, set_number_of_threads};
use czkawka_core::common_items::{DEFAULT_EXCLUDED_DIRECTORIES, DEFAULT_EXCLUDED_ITEMS}; use czkawka_core::common_items::{DEFAULT_EXCLUDED_DIRECTORIES, DEFAULT_EXCLUDED_ITEMS};
use crate::common::{create_string_standard_list_view_from_pathbuf, create_vec_model_from_vec_string}; 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}; use crate::{Callabler, GuiState, MainWindow, Settings};
pub const DEFAULT_MINIMUM_SIZE_KB: i32 = 16; pub const DEFAULT_MINIMUM_SIZE_KB: i32 = 16;
@ -44,12 +44,16 @@ pub const ALLOWED_HASH_TYPE_VALUES: &[(&str, &str, HashAlg)] = &[
pub struct SettingsCustom { pub struct SettingsCustom {
#[serde(default = "default_included_directories")] #[serde(default = "default_included_directories")]
pub included_directories: Vec<PathBuf>, pub included_directories: Vec<PathBuf>,
#[serde(default)]
pub included_directories_referenced: Vec<PathBuf>,
#[serde(default = "default_excluded_directories")] #[serde(default = "default_excluded_directories")]
pub excluded_directories: Vec<PathBuf>, pub excluded_directories: Vec<PathBuf>,
#[serde(default = "default_excluded_items")] #[serde(default = "default_excluded_items")]
pub excluded_items: String, pub excluded_items: String,
#[serde(default)] #[serde(default)]
pub allowed_extensions: String, pub allowed_extensions: String,
#[serde(default)]
pub excluded_extensions: String,
#[serde(default = "minimum_file_size")] #[serde(default = "minimum_file_size")]
pub minimum_file_size: i32, pub minimum_file_size: i32,
#[serde(default = "maximum_file_size")] #[serde(default = "maximum_file_size")]
@ -236,7 +240,7 @@ pub fn load_settings_from_file(app: &MainWindow) {
} }
} }
base_settings.default_preset = max(min(base_settings.default_preset, 9), 0); base_settings.default_preset = max(min(base_settings.default_preset, 9), 0);
custom_settings.thread_number = max(min(custom_settings.thread_number, get_available_threads() as i32), 0); custom_settings.thread_number = max(min(custom_settings.thread_number, get_all_available_threads() as i32), 0);
// Ended validating // Ended validating
set_settings_to_gui(app, &custom_settings); set_settings_to_gui(app, &custom_settings);
@ -259,7 +263,7 @@ pub fn save_base_settings_to_file(app: &MainWindow) {
pub fn save_custom_settings_to_file(app: &MainWindow) { pub fn save_custom_settings_to_file(app: &MainWindow) {
let current_item = app.global::<Settings>().get_settings_preset_idx(); let current_item = app.global::<Settings>().get_settings_preset_idx();
let result = save_data_to_file(get_config_file(current_item + 1), &collect_settings(app)); let result = save_data_to_file(get_config_file(current_item), &collect_settings(app));
if let Err(e) = result { if let Err(e) = result {
error!("{e}"); error!("{e}");
@ -348,15 +352,16 @@ pub fn set_settings_to_gui(app: &MainWindow, custom_settings: &SettingsCustom) {
let settings = app.global::<Settings>(); let settings = app.global::<Settings>();
// Included directories // Included directories
let included_directories = create_string_standard_list_view_from_pathbuf(&custom_settings.included_directories); let included_directories = create_included_directories_model_from_pathbuf(&custom_settings.included_directories, &custom_settings.included_directories_referenced);
settings.set_included_directories(included_directories); settings.set_included_directories_model(included_directories);
// Excluded directories // Excluded directories
let excluded_directories = create_string_standard_list_view_from_pathbuf(&custom_settings.excluded_directories); let excluded_directories = create_excluded_directories_model_from_pathbuf(&custom_settings.excluded_directories);
settings.set_excluded_directories(excluded_directories); settings.set_excluded_directories_model(excluded_directories);
settings.set_excluded_items(custom_settings.excluded_items.clone().into()); settings.set_excluded_items(custom_settings.excluded_items.clone().into());
settings.set_allowed_extensions(custom_settings.allowed_extensions.clone().into()); settings.set_allowed_extensions(custom_settings.allowed_extensions.clone().into());
settings.set_excluded_extensions(custom_settings.excluded_extensions.clone().into());
settings.set_minimum_file_size(custom_settings.minimum_file_size.to_string().into()); settings.set_minimum_file_size(custom_settings.minimum_file_size.to_string().into());
settings.set_maximum_file_size(custom_settings.maximum_file_size.to_string().into()); settings.set_maximum_file_size(custom_settings.maximum_file_size.to_string().into());
settings.set_use_cache(custom_settings.use_cache); settings.set_use_cache(custom_settings.use_cache);
@ -430,14 +435,20 @@ pub fn set_settings_to_gui(app: &MainWindow, custom_settings: &SettingsCustom) {
pub fn collect_settings(app: &MainWindow) -> SettingsCustom { pub fn collect_settings(app: &MainWindow) -> SettingsCustom {
let settings = app.global::<Settings>(); let settings = app.global::<Settings>();
let included_directories = settings.get_included_directories(); let included_directories_model = settings.get_included_directories_model();
let included_directories = included_directories.iter().map(|x| PathBuf::from(x.text.as_str())).collect::<Vec<_>>(); let included_directories = included_directories_model.iter().map(|model| PathBuf::from(model.path.as_str())).collect::<Vec<_>>();
let included_directories_referenced = included_directories_model
.iter()
.filter(|model| model.referenced_folder)
.map(|model| PathBuf::from(model.path.as_str()))
.collect::<Vec<_>>();
let excluded_directories = settings.get_excluded_directories(); let excluded_directories_model = settings.get_excluded_directories_model();
let excluded_directories = excluded_directories.iter().map(|x| PathBuf::from(x.text.as_str())).collect::<Vec<_>>(); let excluded_directories = excluded_directories_model.iter().map(|model| PathBuf::from(model.path.as_str())).collect::<Vec<_>>();
let excluded_items = settings.get_excluded_items().to_string(); let excluded_items = settings.get_excluded_items().to_string();
let allowed_extensions = settings.get_allowed_extensions().to_string(); let allowed_extensions = settings.get_allowed_extensions().to_string();
let excluded_extensions = settings.get_excluded_extensions().to_string();
let minimum_file_size = settings.get_minimum_file_size().parse::<i32>().unwrap_or(DEFAULT_MINIMUM_SIZE_KB); let minimum_file_size = settings.get_minimum_file_size().parse::<i32>().unwrap_or(DEFAULT_MINIMUM_SIZE_KB);
let maximum_file_size = settings.get_maximum_file_size().parse::<i32>().unwrap_or(DEFAULT_MAXIMUM_SIZE_KB); let maximum_file_size = settings.get_maximum_file_size().parse::<i32>().unwrap_or(DEFAULT_MAXIMUM_SIZE_KB);
@ -478,9 +489,11 @@ pub fn collect_settings(app: &MainWindow) -> SettingsCustom {
let similar_images_sub_similarity = settings.get_similar_images_sub_current_similarity().round() as i32; let similar_images_sub_similarity = settings.get_similar_images_sub_current_similarity().round() as i32;
SettingsCustom { SettingsCustom {
included_directories, included_directories,
included_directories_referenced,
excluded_directories, excluded_directories,
excluded_items, excluded_items,
allowed_extensions, allowed_extensions,
excluded_extensions,
minimum_file_size, minimum_file_size,
maximum_file_size, maximum_file_size,
recursive_search, recursive_search,

View file

@ -20,11 +20,13 @@ export component VisibilityButton inherits Button {
export component ActionButtons inherits HorizontalLayout { export component ActionButtons inherits HorizontalLayout {
callback scan_stopping; callback scan_stopping;
callback scan_starting(CurrentTab); callback scan_starting(CurrentTab);
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: BottomPanelVisibility.Directories;
in-out property <bool> stop_requested: false; in-out property <bool> stop_requested: false;
in-out property <bool> scanning; in-out property <bool> scanning;
in-out property <bool> lists_enabled: GuiState.active_tab != CurrentTab.Settings; in-out property <bool> lists_enabled: GuiState.active_tab != CurrentTab.Settings;
// in-out property <>
out property <int> name; out property <int> name;
height: 30px; height: 30px;
spacing: 4px; spacing: 4px;
@ -57,35 +59,30 @@ export component ActionButtons inherits HorizontalLayout {
horizontal-stretch: 0.5; horizontal-stretch: 0.5;
} }
move_button := Button {
height: parent.height;
enabled: !scanning && lists_enabled;
text: "Move";
clicked => {
request_folder_to_move();
}
}
select_button := Button {
height: parent.height;
enabled: !scanning && lists_enabled;
text: "Select";
clicked => {
show_select_popup(self.x + self.width / 2, self.y + parent.y);
}
}
delete_button := Button { delete_button := Button {
height: parent.height; height: parent.height;
enabled: !scanning && lists_enabled; enabled: !scanning && lists_enabled;
text: "Delete"; text: "Delete";
clicked => { clicked => {
Callabler.delete_selected_items(); show_remove_popup();
}
}
popup_item := PopupWindow {
height: root.height;
width: root.width;
close-on-click: true;
VerticalLayout {
for i[idx] in ["A","B","C"]: Rectangle {
background: red;
}
}
}
select_button := Button {
visible: false;
height: parent.height;
enabled: !scanning && lists_enabled;
text: "Select";
clicked => {
debug("Selected");
popup_item.show();
// Callabler.select_items();
} }
} }

View file

@ -4,10 +4,13 @@ import {Settings} from "settings.slint";
import {BottomPanelVisibility} from "common.slint"; import {BottomPanelVisibility} from "common.slint";
import {Callabler} from "callabler.slint"; import {Callabler} from "callabler.slint";
import {GuiState} from "gui_state.slint"; import {GuiState} from "gui_state.slint";
import {IncludedDirectories, ExcludedDirectories} from "included_directories.slint";
component DirectoriesPanel inherits HorizontalLayout { component DirectoriesPanel inherits HorizontalLayout {
callback folder_choose_requested(bool); callback folder_choose_requested(bool);
callback show_manual_add_dialog(bool); callback show_manual_add_dialog(bool);
spacing: 5px;
// Included directories // Included directories
VerticalLayout { VerticalLayout {
horizontal-stretch: 0.0; horizontal-stretch: 0.0;
@ -22,7 +25,7 @@ component DirectoriesPanel inherits HorizontalLayout {
Button { Button {
text: "Remove"; text: "Remove";
clicked => { clicked => {
Callabler.remove_item_directories(true, included-list.current-item); Callabler.remove_item_directories(true);
} }
} }
@ -46,9 +49,7 @@ component DirectoriesPanel inherits HorizontalLayout {
} }
} }
included_list := StandardListView { included_list := IncludedDirectories { }
model: Settings.included-directories;
}
} }
// Excluded directories // Excluded directories
@ -65,7 +66,7 @@ component DirectoriesPanel inherits HorizontalLayout {
Button { Button {
text: "Remove"; text: "Remove";
clicked => { clicked => {
Callabler.remove_item_directories(false, excluded-list.current-item); Callabler.remove_item_directories(false);
} }
} }
@ -89,9 +90,7 @@ component DirectoriesPanel inherits HorizontalLayout {
} }
} }
excluded_list := StandardListView { excluded_list := ExcludedDirectories { }
model: Settings.excluded-directories;
}
} }
} }

View file

@ -1,13 +1,15 @@
import { SelectMode } from "common.slint";
export global Callabler { export global Callabler {
// Bottom panel operations // Bottom panel operations
callback remove_item_directories(bool, int); callback remove_item_directories(bool);
callback added_manual_directories(bool, string); callback added_manual_directories(bool, string);
// Right click or middle click opener // Right click or middle click opener
callback item_opened(string); callback item_opened(string);
callback delete_selected_items(); callback delete_selected_items();
// callback (); callback select_items(SelectMode);
// Preview // Preview
callback load_image_preview(string); callback load_image_preview(string);
@ -18,6 +20,10 @@ export global Callabler {
callback load_current_preset(); callback load_current_preset();
callback reset_current_preset(); callback reset_current_preset();
callback tab_changed();
callback move_items(bool, bool, string);
// Translations // Translations
pure callback translate(string, [{key: string, value: string}]) -> string; pure callback translate(string, [{key: string, value: string}]) -> string;

View file

@ -20,11 +20,40 @@ export struct MainListModel {
checked: bool, checked: bool,
header_row: bool, header_row: bool,
selected_row: bool, selected_row: bool,
val: [string] val_str: [string],
val_int: [int]
} }
export enum BottomPanelVisibility { export enum BottomPanelVisibility {
NotVisible, NotVisible,
TextErrors, TextErrors,
Directories Directories
}
export struct IncludedDirectoriesModel {
path: string,
referenced_folder: bool,
selected_row: bool,
}
export struct ExcludedDirectoriesModel {
path: string,
selected_row: bool,
}
export enum SelectMode {
SelectAll,
UnselectAll,
InvertSelection,
SelectTheBiggestSize,
SelectTheBiggestResolution,
SelectTheSmallestSize,
SelectTheSmallestResolution,
SelectNewest,
SelectOldest,
}
export struct SelectModel {
data: SelectMode,
name: string
} }

View file

@ -1,6 +1,8 @@
import {CurrentTab} from "common.slint"; import {CurrentTab} from "common.slint";
import {SelectModel, SelectMode} from "common.slint";
// State to show // 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
export global GuiState { export global GuiState {
in-out property <length> app_width; in-out property <length> app_width;
in-out property <length> app_height; in-out property <length> app_height;
@ -8,6 +10,7 @@ export global GuiState {
in-out property <string> info_text: "Nothing to report"; in-out property <string> info_text: "Nothing to report";
in-out property <bool> preview_visible; in-out property <bool> preview_visible;
in-out property <image> preview_image; in-out property <image> preview_image;
in-out property <string> preview_image_path;
in-out property <float> maximum_threads: 40; in-out property <float> maximum_threads: 40;
@ -16,4 +19,5 @@ export global GuiState {
in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages; in-out property <bool> available_subsettings: active_tab == CurrentTab.SimilarImages;
in-out property <CurrentTab> active_tab: CurrentTab.EmptyFiles; in-out property <CurrentTab> active_tab: CurrentTab.EmptyFiles;
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"}];
} }

View file

@ -1,25 +1,24 @@
import {Button, StandardListView, VerticalBox, ListView, ScrollView, TextEdit, CheckBox} from "std-widgets.slint"; import {Button, StandardListView, VerticalBox, ListView, ScrollView, TextEdit, CheckBox} from "std-widgets.slint";
import {Callabler} from "callabler.slint"; import {Callabler} from "callabler.slint";
import {IncludedDirectoriesModel, ExcludedDirectoriesModel} from "common.slint";
import {ColorPalette} from "color_palette.slint";
import {Settings} from "settings.slint";
export struct IncludedDirectoriesModel { export component IncludedDirectories {
path: string, in-out property <[IncludedDirectoriesModel]> model <=> Settings.included_directories_model;
referended_folder: bool, in-out property <int> current_index <=> Settings.included_directories_model_selected_idx;
}
export component InlcudedDirectories { in-out property <length> size_referenced_folder: 33px;
in-out property <[IncludedDirectoriesModel]> model: [{path: "/home/path", referended_folder: false}];
in-out property <int> current_index: -1;
in-out property <length> size_referenced_folder: 40px;
min-width: 50px; min-width: 50px;
VerticalLayout { VerticalLayout {
HorizontalLayout { HorizontalLayout {
spacing: 5px; spacing: 5px;
Text { Text {
text: "Referenced folder"; text: "Ref";
width: size_referenced_folder; width: size_referenced_folder;
horizontal-alignment: center;
} }
Text{ Text{
horizontal-stretch: 1.0; horizontal-stretch: 1.0;
@ -27,61 +26,26 @@ export component InlcudedDirectories {
} }
} }
ListView { ListView {
for data in model : Rectangle { for r[idx] in model : Rectangle {
height: 30px;
border_radius: 5px;
width: parent.width;
HorizontalLayout {
spacing: 5px;
width: parent.width;
CheckBox {
checked: data.referended_folder;
width: size_referenced_folder;
}
Text {
horizontal-stretch: 1.0;
text: data.path;
vertical-alignment: center;
}
}
}
}
}
}
export component ExcludeDirectories {
in-out property <[string]> model: ["/home/path"];
in-out property <int> current_index: -1;
private property <PointerEvent> event;
min-width: 50px;
VerticalLayout {
HorizontalLayout {
spacing: 5px;
Text {
text: "Path";
}
}
ListView {
for data[idx] in model : Rectangle {
height: 30px; height: 30px;
border_radius: 5px; border_radius: 5px;
width: parent.width; width: parent.width;
background: touch-area.has-hover ? (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color) : (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color);
touch_area := TouchArea { touch_area := TouchArea {
clicked => { clicked => {
if (current_index == -1) { if (current_index == -1) {
r.selected_row = true;
} else {
if (current_index != idx) {
model[current_index].selected_row = false;
}
r.selected_row = true;
} }
current_index = idx;
} }
double-clicked => { double-clicked => {
if (event.button == PointerEventButton.middle && event.kind == PointerEventKind.up) { Callabler.item_opened(r.path);
Callabler.item_opened(data)
}
}
pointer-event(event) => {
root.event = event;
} }
} }
@ -89,9 +53,16 @@ export component ExcludeDirectories {
spacing: 5px; spacing: 5px;
width: parent.width; width: parent.width;
CheckBox {
checked: r.referenced_folder;
toggled => {
model[idx].referenced_folder = self.checked;
}
width: size_referenced_folder;
}
Text { Text {
horizontal-stretch: 1.0; horizontal-stretch: 1.0;
text: data; text: r.path;
vertical-alignment: center; vertical-alignment: center;
} }
} }
@ -99,3 +70,53 @@ export component ExcludeDirectories {
} }
} }
} }
export component ExcludedDirectories {
in-out property <[ExcludedDirectoriesModel]> model <=> Settings.excluded_directories_model;
in-out property <int> current_index <=> Settings.excluded_directories_model_selected_idx;
min-width: 50px;
VerticalLayout {
HorizontalLayout {
spacing: 5px;
padding-left: 5px;
Text {
text: "Path";
}
}
ListView {
for r[idx] in model : Rectangle {
height: 30px;
border_radius: 5px;
width: parent.width;
background: touch-area.has-hover ? (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color) : (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color);
touch_area := TouchArea {
clicked => {
if (current_index == -1) {
r.selected_row = true;
} else {
if (current_index != idx) {
model[current_index].selected_row = false;
}
r.selected_row = true;
}
current_index = idx;
}
double-clicked => {
Callabler.item_opened(r.path);
}
}
Text {
x: 5px;
width: parent.width;
height: parent.height;
text: r.path;
vertical-alignment: center;
}
}
}
}
}

View file

@ -2,6 +2,7 @@ import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListV
import {CurrentTab} from "common.slint"; import {CurrentTab} from "common.slint";
import {ColorPalette} from "color_palette.slint"; import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint"; import {GuiState} from "gui_state.slint";
import {Callabler} from "callabler.slint";
component TabItem { component TabItem {
in property <bool> scanning; in property <bool> scanning;
@ -19,6 +20,7 @@ component TabItem {
return; return;
} }
GuiState.active_tab = root.curr-tab; GuiState.active_tab = root.curr-tab;
Callabler.tab_changed();
changed_current_tab(); changed_current_tab();
} }
} }
@ -130,6 +132,7 @@ export component LeftSidePanel {
icon: @image-url("../icons/settings.svg"); icon: @image-url("../icons/settings.svg");
clicked => { clicked => {
GuiState.active_tab = CurrentTab.Settings; GuiState.active_tab = CurrentTab.Settings;
Callabler.tab_changed();
root.changed_current_tab(); root.changed_current_tab();
} }
} }

View file

@ -8,10 +8,10 @@ import {GuiState} from "gui_state.slint";
export component MainList { export component MainList {
in-out property <[MainListModel]> empty_folder_model: [ in-out property <[MainListModel]> empty_folder_model: [
{checked: false, selected_row: false, header_row: true, val: ["kropkarz", "/Xd1", "24.10.2023"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]} {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; in-out property <[MainListModel]> empty_files_model;
in-out property <[MainListModel]> similar_images_model; in-out property <[MainListModel]> similar_images_model;

View file

@ -13,7 +13,9 @@ import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint"; import {GuiState} from "gui_state.slint";
import { Preview } from "preview.slint"; import { Preview } from "preview.slint";
import {PopupNewDirectories} from "popup_new_directories.slint"; import {PopupNewDirectories} from "popup_new_directories.slint";
import { PopupSelect } from "popup_select.slint"; import {PopupDelete} from "popup_delete.slint";
import {PopupMoveFolders} from "popup_move_folders.slint";
import { PopupSelectResults } from "popup_select_results.slint";
import { ToolSettings } from "tool_settings.slint"; import { ToolSettings } from "tool_settings.slint";
export {Settings, Callabler, GuiState} export {Settings, Callabler, GuiState}
@ -23,6 +25,8 @@ export component MainWindow inherits Window {
callback scan_starting(CurrentTab); callback scan_starting(CurrentTab);
callback folder_choose_requested(bool); callback folder_choose_requested(bool);
callback scan_ended(string); callback scan_ended(string);
callback show_move_folders_dialog(string);
callback folders_move_choose_requested();
min-width: 300px; min-width: 300px;
preferred-width: 800px; preferred-width: 800px;
@ -38,19 +42,19 @@ export component MainWindow inherits Window {
step_name: "Cache", step_name: "Cache",
}; };
in-out property <[MainListModel]> empty_folder_model: [ in-out property <[MainListModel]> empty_folder_model: [
{checked: false, selected_row: false, header_row: true, val: ["kropkarz", "/Xd1", "24.10.2023"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]} {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: [ in-out property <[MainListModel]> empty_files_model: [
{checked: false, selected_row: false, header_row: true, val: ["kropkarz", "/Xd1", "24.10.2023"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {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: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]} {checked: true, selected_row: false, header_row: false, val_str: ["lokkaler", "/Xd1/Vide2", "01.23.1911"], val_int: []}
]; ];
in-out property <[MainListModel]> similar_images_model: []; in-out property <[MainListModel]> similar_images_model: [];
VerticalBox { VerticalBox {
HorizontalBox { HorizontalBox {
vertical-stretch: 1.0; vertical-stretch: 1.0;
@ -117,6 +121,17 @@ export component MainWindow inherits Window {
text_summary_text = "Searching..."; text_summary_text = "Searching...";
root.scan_starting(item); root.scan_starting(item);
} }
show_select_popup(x_offset, y_offset) => {
select_popup_window.x_offset = x_offset;
select_popup_window.y_offset = y_offset;
select_popup_window.show_popup();
}
request_folder_to_move => {
folders_move_choose_requested();
}
show_remove_popup => {
delete_popup_window.show_popup();
}
} }
text_summary := LineEdit { text_summary := LineEdit {
@ -142,11 +157,37 @@ export component MainWindow inherits Window {
width: root.width; width: root.width;
} }
// select_popup_window := PopupSelect { select_popup_window := PopupSelectResults {
// height: root.height; property <length> x_offset: 0;
// width: root.width; property <length> y_offset: 0;
// }
x: parent.x + x_offset - self.item_width / 2.0;
y: parent.y + y_offset - self.all_items_height - 5px;
height: root.height;
width: root.width;
}
delete_popup_window := PopupDelete {
height: root.height;
width: root.width;
x: parent.x + (root.width - self.popup_width) / 2.0;
y: parent.y + (parent.height - self.popup_height) / 2.0;
}
move_popup_window := PopupMoveFolders {
height: root.height;
width: root.width;
x: parent.x + (root.width - self.popup_width) / 2.0;
y: parent.y + (parent.height - self.popup_height) / 2.0;
}
show_move_folders_dialog(folder_name) => {
move_popup_window.folder_name = folder_name;
move_popup_window.show_popup();
}
scan_ended(scan_text) => { scan_ended(scan_text) => {
text_summary_text = scan_text; text_summary_text = scan_text;

View file

@ -0,0 +1,73 @@
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
import {SelectableTableView} from "selectable_tree_view.slint";
import {LeftSidePanel} from "left_side_panel.slint";
import {MainList} from "main_lists.slint";
import {CurrentTab, ProgressToSend} from "common.slint";
import { ActionButtons } from "action_buttons.slint";
import { Progress } from "progress.slint";
import {MainListModel, SelectMode, SelectModel} from "common.slint";
import {Settings} from "settings.slint";
import {Callabler} from "callabler.slint";
import { BottomPanel } from "bottom_panel.slint";
import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint";
import { Preview } from "preview.slint";
export component PopupDelete inherits Rectangle {
out property <length> popup_width: 350px;
out property <length> popup_height: 150px;
callback show_popup();
popup_window := PopupWindow {
width: popup_width;
height: popup_height;
close-on-click: true;
Rectangle {
width: parent.width;
height: parent.height;
border-radius: 5px;
background: ColorPalette.popup_background;
VerticalLayout {
Text {
vertical-stretch: 0.0;
text: "Delete items";
vertical-alignment: center;
horizontal-alignment: center;
font-size: 13px;
padding: 10px;
}
Text {
vertical-stretch: 1.0;
text: "Are you sure you want to delete the selected items?";
vertical-alignment: center;
horizontal-alignment: center;
font-size: 13px;
padding: 10px;
}
HorizontalLayout {
Button {
text: "Yes";
clicked => {
popup_window.close();
Callabler.delete_selected_items();
}
}
Rectangle {
}
Button {
text: "No";
clicked => {
popup_window.close();
}
}
}
}
}
}
show_popup() => {
popup_window.show();
}
}

View file

@ -0,0 +1,94 @@
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
import {SelectableTableView} from "selectable_tree_view.slint";
import {LeftSidePanel} from "left_side_panel.slint";
import {MainList} from "main_lists.slint";
import {CurrentTab, ProgressToSend} from "common.slint";
import { ActionButtons } from "action_buttons.slint";
import { Progress } from "progress.slint";
import {MainListModel, SelectMode, SelectModel} from "common.slint";
import {Settings} from "settings.slint";
import {Callabler} from "callabler.slint";
import { BottomPanel } from "bottom_panel.slint";
import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint";
import { Preview } from "preview.slint";
export component PopupMoveFolders inherits Rectangle {
out property <length> popup_width: 500px;
out property <length> popup_height: 150px;
in-out property <string> folder_name: "";
callback show_popup();
popup_window := PopupWindow {
width: popup_width;
height: popup_height;
close-on-click: false;
Rectangle {
width: parent.width;
height: parent.height;
border-radius: 5px;
background: ColorPalette.popup_background;
VerticalLayout {
Text {
vertical-stretch: 0.0;
min-height: 30px;
text: "Moving files";
vertical-alignment: top;
horizontal-alignment: center;
font-size: 13px;
}
Text {
vertical-stretch: 1.0;
text: "Moving entries to folder\n" + folder_name + "\nAre you want to continue?";
vertical-alignment: center;
horizontal-alignment: center;
font-size: 13px;
padding: 10px;
}
VerticalLayout {
HorizontalLayout {
alignment: center;
copy_checkbox := CheckBox {
text: "Copy files instead of moving";
}
}
HorizontalLayout {
alignment: center;
preserve_folder_checkbox := CheckBox {
text: "Preserve folder structure";
}
}
}
HorizontalLayout {
Button {
text: "Yes";
clicked => {
popup_window.close();
Callabler.move_items(preserve_folder_checkbox.checked, copy_checkbox.checked, folder_name);
}
}
Rectangle {
}
Button {
text: "No";
clicked => {
popup_window.close();
}
}
}
}
}
}
init => {
show_popup();
}
show_popup() => {
popup_window.show();
}
}

View file

@ -1,74 +0,0 @@
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
import {SelectableTableView} from "selectable_tree_view.slint";
import {LeftSidePanel} from "left_side_panel.slint";
import {MainList} from "main_lists.slint";
import {CurrentTab, ProgressToSend} from "common.slint";
import { ActionButtons } from "action_buttons.slint";
import { Progress } from "progress.slint";
import {MainListModel} from "common.slint";
import {Settings} from "settings.slint";
import {Callabler} from "callabler.slint";
import { BottomPanel } from "bottom_panel.slint";
import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint";
import { Preview } from "preview.slint";
export component PopupSelect inherits Rectangle {
callback show_popup();
popup_window := PopupWindow {
width: root.width;
height: root.height;
property <bool> included_directories;
private property <string> text_data;
close-on-click: false;
HorizontalLayout {
alignment: LayoutAlignment.center;
VerticalLayout {
alignment: LayoutAlignment.center;
Rectangle {
clip: true;
width: root.width - 20px;
height: root.height - 20px;
border-radius: 20px;
background: ColorPalette.popup_background;
VerticalLayout {
Text {
text: "Please add directories one per line";
horizontal-alignment: TextHorizontalAlignment.center;
}
TextEdit {
vertical-stretch: 1.0;
text <=> text-data;
}
HorizontalLayout {
min-height: 20px;
Button {
enabled: text-data != "";
text: "OK";
clicked => {
Callabler.added_manual_directories(GuiState.choosing_include_directories, text_data);
popup_window.close();
}
}
Button {
text: "Cancel";
clicked => {
popup_window.close();
}
}
}
}
}
}
}
}
show_popup() => {
popup_window.show();
}
}

View file

@ -0,0 +1,51 @@
import { Button, VerticalBox ,TextEdit, HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, LineEdit} from "std-widgets.slint";
import {SelectableTableView} from "selectable_tree_view.slint";
import {LeftSidePanel} from "left_side_panel.slint";
import {MainList} from "main_lists.slint";
import {CurrentTab, ProgressToSend} from "common.slint";
import { ActionButtons } from "action_buttons.slint";
import { Progress } from "progress.slint";
import {MainListModel, SelectMode, SelectModel} from "common.slint";
import {Settings} from "settings.slint";
import {Callabler} from "callabler.slint";
import { BottomPanel } from "bottom_panel.slint";
import {ColorPalette} from "color_palette.slint";
import {GuiState} from "gui_state.slint";
import { Preview } from "preview.slint";
export component PopupSelectResults inherits Rectangle {
callback show_popup();
property <[SelectModel]> model: GuiState.select_results_list;
property <length> item_height: 30px;
out property <length> item_width: 200px;
out property <length> all_items_height: item_height * model.length;
popup_window := PopupWindow {
width: item_width;
height: all_items_height;
close-on-click: true;
Rectangle {
width: parent.width;
height: parent.height;
border-radius: 5px;
background: ColorPalette.popup_background;
VerticalLayout {
for i in model: Button {
text: i.name;
height: item_height;
width: item_width;
clicked => {
Callabler.select_items(i.data);
popup_window.close();
}
}
}
}
}
show_popup() => {
popup_window.show();
}
}

Some files were not shown because too many files have changed in this diff Show more