Resize all images, before showing them in gui (#1590)

This commit is contained in:
Rafał Mikrut
2025-08-16 17:39:51 +02:00
committed by GitHub
parent 3f7f86a27d
commit 5dc9257375
42 changed files with 602 additions and 547 deletions

View File

@@ -46,14 +46,14 @@ jobs:
export LIBRARY_PATH=$LIBRARY_PATH:$(brew --prefix)/lib
cargo build --release
mv target/release/czkawka_cli macos_czkawka_cli_${{ env.ARCHNAME }}
mv target/release/czkawka_gui macos_czkawka_gui_${{ env.ARCHNAME }}
mv target/release/krokiet macos_krokiet_${{ env.ARCHNAME }}
mv target/release/czkawka_cli mac_czkawka_cli_${{ env.ARCHNAME }}
mv target/release/czkawka_gui mac_czkawka_gui_${{ env.ARCHNAME }}
mv target/release/krokiet mac_krokiet_${{ env.ARCHNAME }}
cargo build --release --features "heif,libavif"
mv target/release/czkawka_cli macos_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mv target/release/czkawka_gui macos_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mv target/release/krokiet macos_krokiet_heif_avif_${{ env.ARCHNAME }}
mv target/release/czkawka_cli mac_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mv target/release/czkawka_gui mac_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mv target/release/krokiet mac_krokiet_heif_avif_${{ env.ARCHNAME }}
- name: Build Debug
if: ${{ github.ref != 'refs/heads/master' }}
@@ -67,26 +67,26 @@ jobs:
export LIBRARY_PATH=$LIBRARY_PATH:$(brew --prefix)/lib
cargo build
mv target/debug/czkawka_cli macos_czkawka_cli_${{ env.ARCHNAME }}
mv target/debug/czkawka_gui macos_czkawka_gui_${{ env.ARCHNAME }}
mv target/debug/krokiet macos_krokiet_${{ env.ARCHNAME }}
mv target/debug/czkawka_cli mac_czkawka_cli_${{ env.ARCHNAME }}
mv target/debug/czkawka_gui mac_czkawka_gui_${{ env.ARCHNAME }}
mv target/debug/krokiet mac_krokiet_${{ env.ARCHNAME }}
cargo build --features "heif,libavif"
mv target/debug/czkawka_cli macos_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mv target/debug/czkawka_gui macos_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mv target/debug/krokiet macos_krokiet_heif_avif_${{ env.ARCHNAME }}
mv target/debug/czkawka_cli mac_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mv target/debug/czkawka_gui mac_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mv target/debug/krokiet mac_krokiet_heif_avif_${{ env.ARCHNAME }}
- name: Store MacOS
uses: actions/upload-artifact@v4
with:
name: all-${{ runner.os }}-${{ runner.arch }}-${{ env.VERS }}
path: |
macos_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
macos_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
macos_krokiet_heif_avif_${{ env.ARCHNAME }}
macos_czkawka_cli_${{ env.ARCHNAME }}
macos_czkawka_gui_${{ env.ARCHNAME }}
macos_krokiet_${{ env.ARCHNAME }}
mac_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mac_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mac_krokiet_heif_avif_${{ env.ARCHNAME }}
mac_czkawka_cli_${{ env.ARCHNAME }}
mac_czkawka_gui_${{ env.ARCHNAME }}
mac_krokiet_${{ env.ARCHNAME }}
- name: Release
if: ${{ github.ref == 'refs/heads/master' }}
@@ -94,10 +94,10 @@ jobs:
with:
tag_name: "Nightly"
files: |
macos_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
macos_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
macos_krokiet_heif_avif_${{ env.ARCHNAME }}
macos_czkawka_cli_${{ env.ARCHNAME }}
macos_czkawka_gui_${{ env.ARCHNAME }}
macos_krokiet_${{ env.ARCHNAME }}
mac_czkawka_cli_heif_avif_${{ env.ARCHNAME }}
mac_czkawka_gui_heif_avif_${{ env.ARCHNAME }}
mac_krokiet_heif_avif_${{ env.ARCHNAME }}
mac_czkawka_cli_${{ env.ARCHNAME }}
mac_czkawka_gui_${{ env.ARCHNAME }}
mac_krokiet_${{ env.ARCHNAME }}
token: ${{ secrets.PAT_REPOSITORY }}

View File

@@ -182,7 +182,9 @@ jobs:
- name: Prepare files to release
run: |
zip -r windows_czkawka_gui_gtk_412.zip ./package
cd package
zip -r ../windows_czkawka_gui_gtk_412.zip .
cd ..
- name: Release
uses: softprops/action-gh-release@v2
@@ -266,7 +268,9 @@ jobs:
- name: Prepare files to release
run: |
zip -r windows_czkawka_gui_gtk_46.zip ./package
cd package
zip -r ../windows_czkawka_gui_gtk_46.zip .
cd ..
- name: Release
uses: softprops/action-gh-release@v2

14
Cargo.lock generated
View File

@@ -4041,6 +4041,7 @@ dependencies = [
"crossbeam-channel",
"czkawka_core",
"dunce",
"fast_image_resize",
"fs_extra",
"home",
"humansize",
@@ -4348,9 +4349,9 @@ dependencies = [
[[package]]
name = "lopdf"
version = "0.36.0"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fa2559e99ba0f26a12458aabc754432c805bbb8cba516c427825a997af1fb7"
checksum = "674a3504c1224247e00762afb90690991b673c461f6779565e055e91926a49da"
dependencies = [
"aes",
"bitflags 2.9.1",
@@ -4359,6 +4360,7 @@ dependencies = [
"ecb",
"encoding_rs",
"flate2",
"getrandom 0.3.3",
"indexmap",
"itoa",
"jiff",
@@ -7337,15 +7339,15 @@ dependencies = [
[[package]]
name = "trash"
version = "5.2.2"
version = "5.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22746c6b0c6d85d60a8f0d858f7057dfdf11297c132679f452ec908fba42b871"
checksum = "65a334451012017a39758aa85a30827c13ac684245bf6b08249483c063f64ff3"
dependencies = [
"chrono",
"libc",
"log",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2 0.6.2",
"objc2-foundation 0.3.1",
"once_cell",
"percent-encoding",
"scopeguard",

View File

@@ -1,7 +1,6 @@
## Version ?.?.? - ?
### Release blockers:
- Missing support for hardlinking/softlinking files in Krokiet
- Unnecessary tokio dependency in rawler
- crash when sorting by size, elements in empty folders
### Breaking changes
#### Users
@@ -51,6 +50,7 @@
- Improved appearance of bottom directories panel - [#1569](https://github.com/qarmin/czkawka/pull/1569)
- Some buttons, are disabled, when there is no files selected - [#1586](https://github.com/qarmin/czkawka/pull/1586)
- Added info about the number of items selected to delete - [#1589](https://github.com/qarmin/czkawka/pull/1589)
- Limit image preview to max 1024 width/height, to speedup preview loading and fixing crash in software renderer - [#1590](https://github.com/qarmin/czkawka/pull/1590)
### External
- There is a new unofficial Tauri-based frontend for Czkawka - [Czkawka Tauri](https://github.com/shixinhuang99/czkawka-tauri)
@@ -60,7 +60,6 @@
- Compilation for 32-bit targets is now checked in CI
- Czkawka binaries are now checked for reproducibility in CI
### Prebuilt binaries
- AppImage binaries are no longer provided due to random bugs (not present in other packaging formats) and minimal added value compared to prebuilt Linux binaries or Flatpak
- HEIF Mac binaries are now provided

View File

@@ -54,32 +54,35 @@ two apps shouldn't be compared directly or be considered as an alternative to on
In this comparison remember, that even if app have same features they may work different(e.g. one app may have more
options to choose than other).
| | Czkawka | Krokiet | FSlint | DupeGuru | Bleachbit |
|:------------------------:|:-----------:|:-----------:|:------:|:-----------------:|:-----------:|
| Language | Rust | Rust | Python | Python/Obj-C | Python |
| Framework base language | C | Rust | C | C/C++/Obj-C/Swift | C |
| Framework | GTK 4 | Slint | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 |
| OS | Lin,Mac,Win | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win |
| Duplicate finder | ✔ | ✔ | ✔ | ✔ | |
| Empty files | ✔ | ✔ | ✔ | | |
| Empty folders | ✔ | ✔ | ✔ | | |
| Temporary files | ✔ | ✔ | ✔ | | ✔ |
| Big files | ✔ | ✔ | | | |
| Similar images | ✔ | ✔ | | ✔ | |
| Similar videos | ✔ | ✔ | | | |
| Music duplicates(tags) | ✔ | ✔ | | ✔ | |
| Invalid symlinks | ✔ | ✔ | | | |
| Broken files | ✔ | ✔ | | | |
| Names conflict | ✔ | ✔ | | | |
| Invalid names/extensions | ✔ | ✔ | ✔ | | |
| Installed packages | | | ✔ | | |
| Bad ID | | | ✔ | | |
| Non stripped binaries | | | ✔ | | |
| Redundant whitespace | | | ✔ | | |
| Overwriting files | | | ✔ | | |
| Multiple languages | | | ✔ | | ✔ |
| Cache support | ✔ | ✔ | | ✔ | |
| In active development | Yes | Yes | No | Yes | Yes |
| | Czkawka | Krokiet | FSlint | DupeGuru | Bleachbit |
|:-------------------------:|:-----------:|:-----------:|:------:|:-----------------:|:-----------:|
| Language | Rust | Rust | Python | Python/Obj-C | Python |
| Framework base language | C | Rust | C | C/C++/Obj-C/Swift | C |
| Framework | GTK 4 | Slint | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 |
| OS | Lin,Mac,Win | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win |
| Duplicate finder | ✔ | ✔ | ✔ | ✔ | |
| Empty files | ✔ | ✔ | ✔ | | |
| Empty folders | ✔ | ✔ | ✔ | | |
| Temporary files | ✔ | ✔ | ✔ | | ✔ |
| Big files | ✔ | ✔ | | | |
| Similar images | ✔ | ✔ | | ✔ | |
| Similar videos | ✔ | ✔ | | | |
| Music duplicates(tags) | ✔ | ✔ | | ✔ | |
| Music duplicates(content) | ✔ | ✔ | | | |
| Invalid symlinks | ✔ | ✔ | | | |
| Broken files | ✔ | ✔ | | | |
| Invalid names/extensions | ✔ | ✔ | ✔ | | |
| Names conflict | | | ✔ | | |
| Installed packages | | | ✔ | | |
| Bad ID | | | ✔ | | |
| Non stripped binaries | | | ✔ | | |
| Redundant whitespace | | | ✔ | | |
| Overwriting files | | | ✔ | | ✔ |
| Multiple languages | ✔ | ✔ | | ✔ | |
| Cache support | | | | | |
| In active development | Yes | Yes | No | No* | Yes |
* Last commit in 2024 and last version released in 2023
## Other apps
@@ -131,3 +134,4 @@ The Czkawka GTK GUI and CLI applications are licensed under the [MIT](https://mi
If you are using the app, I would appreciate a donation for its further development, which can be
done [here](https://github.com/sponsors/qarmin).

View File

@@ -1,50 +1,56 @@
# Czkawka CLI
CLI frontend, allows to use Czkawka from terminal.
CLI frontend that allows you to use Czkawka from the terminal.
## Requirements
Precompiled binaries should work without any additional dependencies with Linux(Ubuntu 20.04+), Windows(10+) and macOS(
10.15+).
Precompiled binaries should work without any additional dependencies on Linux (Ubuntu 22.04+), Windows (10+), and macOS (10.15+).
If you decide to compile the app, you probably will be able to run it on even older versions of OS, like Ubuntu 16.04 or
Windows 7.
On Linux, it is even possible (with eyra) to avoid libc entirely and use a fully static Rust binary, but alternatively you can use musl for this task.
On linux it is even possible with eyra to avoid entirely libc and using fully static rust binary.
If you want to use the similar videos tool, you need to install ffmpeg (runtime dependency).
If you want to use heif/libraw/libavif (build/runtime dependency), you need to install the required packages.
If you want to use similar videos tool, you need to install ffmpeg(runtime dependency).
If you want to use heif/libraw/libavif(build/runtime dependency) you need to install required packages(may require
bigger os version than czkawka).
- mac - `brew install ffmpeg libraw libheif libavif` - https://formulae.brew.sh/formula/ffmpeg
- linux - `sudo apt install ffmpeg libraw-dev libheif-dev libavif-dev libdav1d-dev`
- windows - `choco install ffmpeg` - or if not working, download from https://ffmpeg.org/download.html#build-windows and
unpack to location with `czkawka_cli.exe`, heif and libraw are not supported on windows
- macOS: `brew install ffmpeg libraw libheif libavif dav1d` [ffmpeg formula](https://formulae.brew.sh/formula/ffmpeg)
- Linux: `sudo apt install ffmpeg libraw-dev libheif-dev libavif-dev libdav1d-dev`
- Windows: `choco install ffmpeg` or, if not working, download from [ffmpeg.org](https://ffmpeg.org/download.html#build-windows) and
unpack to the location with `czkawka_cli.exe`. Heif and libraw are not supported on Windows.
## Compilation
For compilation, you need to have installed Rust via rustup - https://rustup.rs/ and compile it e.g. via
To compile, you need to have Rust installed via [rustup](https://rustup.rs/). Then, build with:
```shell
cargo run --release --bin czkawka_cli
```
you can enable additional features via
You can enable additional features with:
```shell
cargo run --release --bin czkawka_cli --features "heif,libraw,libavif"
```
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)
## How to use
The application includes concise help for each tool, which you can display by running:
```
czkawka_cli --help
```
You can also get detailed information about the parameters of a specific tool by running, for example:
```
czkawka_cli dup --help
```
Example usage:
```shell
rustup default nightly-2025-01-01 # or any newer nightly that works fine with eyra
cd czkawka_cli
cargo add eyra --rename=std
echo 'fn main() { println!("cargo:rustc-link-arg=-nostartfiles"); }' > build.rs
cd ..
cargo build --release --bin czkawka_cli
czkawka dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo
czkawka empty-folders -d /home/rafal/rr /home/gateway -f results.txt
czkawka big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -x VIDEO -f results.txt
czkawka empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt
czkawka temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
czkawka music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt
czkawka symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt
```
## LICENSE

View File

@@ -26,7 +26,7 @@ pub enum Commands {
#[clap(
name = "dup",
about = "Finds duplicate files",
after_help = "EXAMPLE:\n czkawka dup -d /home/rafal - -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo"
after_help = "EXAMPLE:\n czkawka dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo"
)]
Duplicates(DuplicatesArgs),
#[clap(
@@ -73,7 +73,7 @@ pub enum Commands {
after_help = "EXAMPLE:\n czkawka broken -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt"
)]
BrokenFiles(BrokenFilesArgs),
#[clap(name = "video", about = "Finds similar video files", after_help = "EXAMPLE:\n czkawka videos -d /home/rafal -f results.txt")]
#[clap(name = "video", about = "Finds similar video files", after_help = "EXAMPLE:\n czkawka video -d /home/rafal -f results.txt")]
SimilarVideos(SimilarVideosArgs),
#[clap(
name = "ext",
@@ -281,8 +281,8 @@ pub struct SameMusicArgs {
long,
default_value = "track_title,track_artist",
value_parser = parse_music_duplicate_type,
help = "Search method (track_title,track_artist,year,bitrate,genre,length))",
long_help = "Sets which rows must be equal to set this files as duplicates(may be mixed, but must be divided by commas)."
help = "Search method (track_title,track_artist,year,bitrate,genre,length)",
long_help = "Sets which rows must be equal to set these files as duplicates (may be mixed, but must be divided by commas)."
)]
pub music_similarity: MusicSimilarity,
#[clap(
@@ -291,7 +291,7 @@ pub struct SameMusicArgs {
default_value = "TAGS",
value_parser = parse_checking_method_same_music,
help = "Search method (CONTENT, TAGS)",
long_help = "Methods to search files.\nCONTENT - finds similar audio files by content, TAGS - finds similar images by tags, needs to set"
long_help = "Methods to search files.\nCONTENT - finds similar audio files by content, TAGS - finds similar music by tags."
)]
pub search_method: CheckingMethod,
#[clap(
@@ -724,7 +724,8 @@ fn parse_similar_images_similarity(src: &str) -> Result<SimilarityPreset, &'stat
"medium" => Ok(SimilarityPreset::Medium),
"high" => Ok(SimilarityPreset::High),
"veryhigh" => Ok(SimilarityPreset::VeryHigh),
_ => Err("Couldn't parse the image similarity preset (allowed: Minimal, VerySmall, Small, Medium, High, VeryHigh)"),
"original" => Ok(SimilarityPreset::Original),
_ => Err("Couldn't parse the image similarity preset (allowed: Minimal, VerySmall, Small, Medium, High, VeryHigh, Original)"),
}
}
@@ -831,7 +832,7 @@ OPTIONS:
SUBCOMMANDS:
{subcommands}
try "{usage} -h" to get more info about a specific tool
try "{usage} <COMMAND> -h" to get more info about a specific tool
EXAMPLES:
{bin} dup -d /home/rafal -e /home/rafal/Obrazy -m 25 -x 7z rar IMAGE -s hash -f results.txt -D aeo
@@ -840,7 +841,7 @@ EXAMPLES:
{bin} empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt
{bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D
{bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt
{bin} music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt
{bin} music -d /home/rafal -e /home/rafal/Pulpit -z \"artist,year,ARTISTALBUM,ALBUM___tiTlE\" -f results.txt
{bin} symlinks -d /home/kicikici/ /home/szczek -e /home/kicikici/jestempsem -x jpg -f results.txt
{bin} broken -d /home/mikrut/ -e /home/mikrut/trakt -f results.txt
{bin} extnp -d /home/mikrut/ -e /home/mikrut/trakt -f results.txt"#;
{bin} ext -d /home/mikrut/ -e /home/mikrut/trakt -f results.txt"#;

View File

@@ -67,7 +67,6 @@ pub(crate) fn get_progress_message(progress_data: &ProgressData) -> String {
CurrentStage::SimilarVideosCalculatingHashes => "Reading similar values",
CurrentStage::BrokenFilesChecking => "Checking broken files",
CurrentStage::BadExtensionsChecking => "Checking extensions of files",
CurrentStage::DeletingFiles => "Deleting files/folders",
_ => unreachable!("Unsupported stage {:?}", progress_data.sstage),
}

View File

@@ -31,7 +31,7 @@ lofty = "0.22"
# Needed by broken files
zip = { version = "4.0", features = ["aes-crypto", "bzip2", "deflate", "time"], default-features = false }
audio_checker = "0.1"
lopdf = "0.36.0"
lopdf = "0.37.0"
# Needed by audio similarity feature
rusty-chromaprint = "0.3"

View File

@@ -131,29 +131,29 @@ pub(crate) fn get_raw_image(path: impl AsRef<Path> + std::fmt::Debug) -> Result<
let raw_source = RawSource::new(path.as_ref()).map_err(|err| format!("Failed to create RawSource from path {path:?}: {err}"))?;
timer.checkpoint("After creating RawSource");
timer.checkpoint("Created RawSource");
let decoder = rawler::get_decoder(&raw_source).map_err(|e| e.to_string())?;
timer.checkpoint("After getting decoder");
timer.checkpoint("Got decoder");
let raw_image = decoder.raw_image(&raw_source, &RawDecodeParams::default(), false).map_err(|e| e.to_string())?;
timer.checkpoint("After decoding raw image");
timer.checkpoint("Decoded raw image");
let developer = RawDevelop::default();
let developed_image = developer.develop_intermediate(&raw_image).map_err(|e| e.to_string())?;
timer.checkpoint("After developing raw image");
timer.checkpoint("Developed raw image");
let dynamic_image = developed_image.to_dynamic_image().ok_or("Failed to convert image to DynamicImage".to_string())?;
timer.checkpoint("After converting to DynamicImage");
timer.checkpoint("Converted to DynamicImage");
let rgb_image = DynamicImage::from(dynamic_image.to_rgb8());
timer.checkpoint("After reconverting to RGB");
timer.checkpoint("Reconverted to RGB");
trace!("{}", timer.report(false));
trace!("{}", timer.report("Everything", false));
Ok(rgb_image)
}

View File

@@ -1,13 +1,63 @@
use std::time::{Duration, Instant};
/// Timer for measuring elapsed time between checkpoints.
///
/// # How to use - examples
///
/// Basic usage:
/// ```
/// use czkawka_core::helpers::debug_timer::Timer;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let mut timer = Timer::new("MyTimer");
/// sleep(Duration::from_millis(50));
/// timer.checkpoint("step1");
/// sleep(Duration::from_millis(30));
/// timer.checkpoint("step2");
/// let report = timer.report("all_steps", false);
/// println!("{}", report);
/// ```
///
/// Output example:
/// ```text
/// MyTimer - step1: 50.0ms,
/// MyTimer - step2: 30.0ms,
/// MyTimer - all_steps: 80.0ms
/// ```
///
/// One-line output:
/// ```
/// use czkawka_core::helpers::debug_timer::Timer;
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// let mut timer = Timer::new("MyTimer");
/// sleep(Duration::from_millis(10));
/// timer.checkpoint("a");
/// sleep(Duration::from_millis(20));
/// timer.checkpoint("b");
/// let report = timer.report("total", true);
/// println!("{}", report);
/// ```
///
/// Output example:
/// ```text
/// MyTimer - a: 10.0ms, b: 20.0ms, total: 30.0ms
/// ```
pub struct Timer {
/// Name or label for the timer.
base: String,
/// Time when the timer was started.
start_time: Instant,
/// Time of the last checkpoint.
last_time: Instant,
/// List of (checkpoint name, duration since last checkpoint).
times: Vec<(String, Duration)>,
}
impl Timer {
/// Creates a new timer with a given label.
pub fn new(base: &str) -> Self {
Self {
base: base.to_string(),
@@ -17,21 +67,30 @@ impl Timer {
}
}
/// Records a checkpoint with the given name.
pub fn checkpoint(&mut self, name: &str) {
let elapsed = self.last_time.elapsed();
self.times.push((name.to_string(), elapsed));
self.last_time = Instant::now();
}
pub fn report(&mut self, in_one_line: bool) -> String {
/// Returns a formatted report of all checkpoints and total time.
///
/// If `in_one_line` is true, outputs all checkpoints in a single line.
/// Otherwise, outputs each checkpoint on a separate line.
pub fn report(&mut self, all_steps_name: &str, in_one_line: bool) -> String {
let all_elapsed = self.start_time.elapsed();
self.times.push(("Everything".to_string(), all_elapsed));
self.times.push((all_steps_name.to_string(), all_elapsed));
let joiner = if in_one_line { ", " } else { ", \n" };
self.times
.iter()
.map(|(name, time)| format!("{} - {name}: {time:?}", self.base))
.collect::<Vec<_>>()
.join(joiner)
if in_one_line {
let times = self.times.iter().map(|(name, time)| format!("{name}: {time:?}")).collect::<Vec<_>>().join(", ");
format!("{} - {}", self.base, times)
} else {
self.times
.iter()
.map(|(name, time)| format!("{} - {name}: {time:?}", self.base))
.collect::<Vec<_>>()
.join(", \n")
}
}
}

View File

@@ -1,18 +1,26 @@
//! DelayedSender: A utility for batching or throttling messages sent between threads.
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
/// A sender that delays sending values until a specified wait time has passed since the last sent value.
///
/// This is useful for batching updates or reducing the frequency of sending messages in a multi-threaded environment.
/// It is not ideal - using mutexes in send function from multiple threads can lead to performance issues(waiting for mutex release), but at least for now I don't see too much performance impact.
/// In future here could be used something like one element channel which would drop all other messages, but not sure if currently something like this exists
/// Note: Using mutexes in the send function from multiple threads can lead to performance issues (waiting for mutex release),
/// but for now, the performance impact is minimal. In the future, a more efficient channel could be used.
pub struct DelayedSender<T: Send + 'static> {
slot: Arc<Mutex<Option<T>>>,
stop_flag: Arc<AtomicBool>,
}
impl<T: Send + 'static> DelayedSender<T> {
/// Creates a new DelayedSender.
///
/// # Arguments
/// * `sender` - The channel sender to forward values to.
/// * `wait_time` - The minimum duration to wait between sends.
pub fn new(sender: crossbeam_channel::Sender<T>, wait_time: Duration) -> Self {
let slot = Arc::new(Mutex::new(None));
let slot_clone = Arc::clone(&slot);
@@ -51,6 +59,7 @@ impl<T: Send + 'static> DelayedSender<T> {
Self { slot, stop_flag }
}
/// Sends a value, replacing any previous value that has not yet been sent.
pub fn send(&self, value: T) {
let mut slot = self.slot.lock().expect("Failed to lock slot in DelayedSender");
*slot = Some(value);
@@ -59,8 +68,8 @@ impl<T: Send + 'static> DelayedSender<T> {
impl<T: Send + 'static> Drop for DelayedSender<T> {
fn drop(&mut self) {
// We need to know, that after dropping DelayedSender, no more values will be sent
// Previously some values were cached and sent after other later operations
// After dropping DelayedSender, no more values will be sent.
// Previously, some values were cached and sent after later operations.
self.stop_flag.store(true, std::sync::atomic::Ordering::Relaxed);
}
}

View File

@@ -1,29 +1,44 @@
//! Messages: Utility for collecting and printing messages, warnings, and errors.
/// Stores messages, warnings, and errors for reporting.
#[derive(Debug, Default, Clone)]
pub struct Messages {
/// Informational messages.
pub messages: Vec<String>,
/// Warning messages.
pub warnings: Vec<String>,
/// Error messages.
pub errors: Vec<String>,
}
impl Messages {
/// Creates a new, empty `Messages` struct.
pub fn new() -> Self {
Default::default()
}
/// Creates a new `Messages` struct with errors.
pub fn new_from_errors(errors: Vec<String>) -> Self {
Self { errors, ..Default::default() }
}
/// Creates a new `Messages` struct with warnings.
pub fn new_from_warnings(warnings: Vec<String>) -> Self {
Self { warnings, ..Default::default() }
}
/// Creates a new `Messages` struct with messages.
pub fn new_from_messages(messages: Vec<String>) -> Self {
Self { messages, ..Default::default() }
}
/// Prints all messages, warnings, and errors to the provided writer.
pub fn print_messages_to_writer<T: std::io::Write>(&self, writer: &mut T) -> std::io::Result<()> {
let text = self.create_messages_text();
writer.write_all(text.as_bytes())
}
/// Creates a formatted string containing all messages, warnings, and errors.
pub fn create_messages_text(&self) -> String {
let mut text_to_return: String = String::new();
@@ -38,7 +53,6 @@ impl Messages {
if !self.warnings.is_empty() {
text_to_return += "-------------------------------WARNINGS--------------------------------\n";
for i in &self.warnings {
text_to_return += i;
text_to_return += "\n";
@@ -48,7 +62,6 @@ impl Messages {
if !self.errors.is_empty() {
text_to_return += "--------------------------------ERRORS---------------------------------\n";
for i in &self.errors {
text_to_return += i;
text_to_return += "\n";
@@ -59,6 +72,7 @@ impl Messages {
text_to_return
}
/// Extends this `Messages` struct with another, appending all messages, warnings, and errors.
pub fn extend_with_another_messages(&mut self, messages: Self) {
let (messages, warnings, errors) = (messages.messages, messages.warnings, messages.errors);
self.messages.extend(messages);

View File

@@ -1,4 +1,5 @@
/// Helpers, generic modules, traits, structs, ready to copy/paste to other projects
//! Helper modules: generic utilities, traits, structs, ready to copy/paste to other projects.
pub mod debug_timer;
pub mod delayed_sender;
pub mod messages;

View File

@@ -134,11 +134,12 @@ impl SimilarVideos {
return None;
}
let size = file_entry.size;
// Currently size is not too much relevant
// let size = file_entry.size;
let res = self.check_video_file_entry(file_entry);
progress_handler.increase_items(1);
progress_handler.increase_size(size);
// progress_handler.increase_size(size);
Some(res)
})

View File

@@ -1,79 +1,91 @@
# Czkawka GUI
Czkawka GUI is a graphical user interface for Czkawka Core written with GTK 4.
Czkawka GUI is a graphical user interface for Czkawka Core, built with GTK 4.
![Screenshot from 2023-11-26 12-43-32](https://github.com/qarmin/czkawka/assets/41945903/722ed490-0be1-4dac-bcfc-182a4d0787dc)
## Maintenance mode
## Maintenance Mode
Czkawka Gtk is currently in maintenance mode.
While no new features will be added (at least by me), bug fixes and compatibility updates with the Czkawka core package will continue to be provided.
Czkawka GTK is currently in maintenance mode.
No new features will be added (at least by me), but bug fixes and compatibility updates with the Czkawka core package will continue.
Active development is now focused on the Krokiet GUI.
## Requirements
Requirements depend on your platform.
Prebuilt binaries are available here - https://github.com/qarmin/czkawka/releases/
Prebuilt binaries are available here: https://github.com/qarmin/czkawka/releases/
Additional features like heif, libraw, libavif require additional libraries to be installed, and may increase
Additional features such as HEIF, libraw, and libavif require extra libraries to be installed, which may increase the number of dependencies.
### Linux
#### Prebuild binaries
#### Prebuilt binaries / Self-compiled
Ubuntu - `sudo apt install libgtk-4-bin libheif1 libraw-bin ffmpeg -y`
Ubuntu:
`sudo apt install libgtk-4-bin libheif1 libraw-bin ffmpeg -y`
### Mac
```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install gtk4 ffmpeg librsvg libheif libraw dav1d
```
### Windows
#### Prebuilt binaries
All required libraries are bundled in the zip (except ffmpeg, which you can install manually and place `ffmpeg.exe` in a directory included in your system PATH).
## Installation
### Prebuilt binaries (All OS)
After installing the required dependencies, download the prebuilt binaries for your platform from the [releases page](https://github.com/qarmin/czkawka/releases).
### Linux
#### Flatpak
```
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install flathub com.github.qarmin.czkawka
```
none - all needed libraries are bundled - https://flathub.org/apps/com.github.qarmin.czkawka
#### Debian package (Unofficial)
Requires Debian 13 (or derivatives) or later.
```
sudo apt install czkawka_gui
```
#### PPA(Unofficial)
#### PPA (Unofficial) - Debian-based distributions (Ubuntu, Linux Mint, etc.)
```
sudo add-apt-repository ppa:xtradeb/apps
sudo apt update
sudo apt install czkawka
```
[PPA page](https://launchpad.net/~xtradeb/+archive/ubuntu/apps)
### Mac
### Homebrew
Czkawka gui is available in homebrew - https://formulae.brew.sh/formula/czkawka and can be installed via
#### Homebrew (Unofficial)
```
brew install czkawka
```
### Manual installation requirements
```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install gtk4 adwaita-icon-theme ffmpeg librsvg libheif libraw
```
[Formula page](https://formulae.brew.sh/formula/czkawka)
### Windows
All needed libraries should be bundled in zip (except ffmpeg which you need download and unpack to location
with `czkawka_gui.exe` - https://ffmpeg.org/download.html#build-windows)
You can also install the app via msys2 (webp and heif should work here) - https://www.msys2.org/#installation (czkawka
package - https://packages.msys2.org/base/mingw-w64-czkawka)
#### MSYS2 (Unofficial)
```
pacman -S mingw-w64-x86_64-czkawka-gui
```
[Package link](https://packages.msys2.org/base/mingw-w64-czkawka)
and you can create a shortcut to `C:\msys64\mingw64\bin\czkawka_gui.exe`
The file should be installed to `C:\msys64\mingw64\bin\czkawka_gui.exe` and can be run from there.
This version is likely the most feature-complete on Windows, as it is compiled with optional features enabled.
## Compilation
Compiling the gui is harder than compiling cli or core, because it uses gtk4 which is written in C and also requires a
lot build and runtime dependencies.
Compiling the GUI is more complex than compiling the CLI, core, or Krokiet, because it uses GTK4 (written in C) and requires many build and runtime dependencies.
### Requirements
@@ -84,7 +96,7 @@ lot build and runtime dependencies.
The Rust version corresponds to the latest rustc available in Debian Sid: https://packages.debian.org/sid/rustc
### Linux (Ubuntu, but on other OS should work similar)
### Linux (Ubuntu; similar steps apply to other distributions)
```shell
sudo apt install libgtk-4-dev -y # Base
@@ -107,31 +119,26 @@ cargo run --release --bin czkawka_gui --features "heif,libraw,libavif"
### Windows
Currently, there is are no instructions on how to compile the app on Windows natively.</br>
You can check for CI for instructions how to cross-compile the app from linux to windows (uses prebuilt docker
image) - [CI Instructions](../.github/workflows/windows.yml)</br>
There exists a mingw recipe which you can try to convert for your
purposes - https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-czkawka/PKGBUILD
Currently, there are no instructions for compiling the app natively on Windows.</br>
You can check the CI for instructions on how to cross-compile the app from Linux to Windows (using a prebuilt Docker image): [CI Instructions](../.github/workflows/windows.yml)</br>
There is also a mingw recipe you can try to adapt for your needs: https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-czkawka/PKGBUILD
## Limitations
Not all available features and/or components implemented here, this is the list of limitations:
Not all features and components are implemented here. The main limitations are:
- Snap versions does not allow to use the similar videos feature
- Windows version does not support heif and webp files with prebuilt binaries(msys2 version support them)
- Prebuilt binaries for mac arm do not exist
- On Windows, text may appear very small on high resolution displays, a solution is to manually change DPI scaling for
this app, see:
- [recommended fix](https://github.com/qarmin/czkawka/issues/787#issuecomment-1292253437) (modify gtk.css),
- [or this workaround](https://github.com/qarmin/czkawka/issues/863#issuecomment-1416761308) (modify windows DPI settings for this app (this works too but the text is a bit blurry)).
- The Windows version does not support HEIF and WebP files with prebuilt binaries (the MSYS2 version supports them).
- On Windows, text may appear very small on high-resolution displays. You can manually change DPI scaling for this app:
- [Recommended fix](https://github.com/qarmin/czkawka/issues/787#issuecomment-1292253437) (modify gtk.css)
- [Alternative workaround](https://github.com/qarmin/czkawka/issues/863#issuecomment-1416761308) (modify Windows DPI settings for this app; this works too, but the text may be a bit blurry).
## License
Code is distributed under MIT license.
The code is distributed under the MIT license.
Icon was created by [jannuary](https://github.com/jannuary) and licensed CC-BY-4.0.
The icon was created by [jannuary](https://github.com/jannuary) and is licensed under CC-BY-4.0.
Windows dark theme is used from project [WhiteSur](https://github.com/slypy/whitesur-gtk4-theme) with MIT license.
The Windows dark theme is from the [WhiteSur](https://github.com/slypy/whitesur-gtk4-theme) project, licensed under MIT.
The program is completely free to use.
@@ -139,15 +146,10 @@ The program is completely free to use.
## Name
Czkawka is a Polish word which means _hiccup_.
Czkawka is a Polish word meaning _hiccup_.
I chose this name because I wanted to hear people speaking other languages pronounce it, so feel free to spell it the
way you want.
I chose this name because I wanted to hear people speaking other languages pronounce it, so feel free to say it however you like.
This name is not as bad as it seems, because I was also thinking about using words like _żółć_, _gżegżółka_ or _żołądź_,
but I gave up on these ideas because they contained Polish characters, which would cause difficulty in searching for the
project.
This name is not as difficult as it seems; I also considered words like _żółć_, _gżegżółka_, or _żołądź_, but decided against them because they contain Polish characters, which would make searching for the project harder.
At the beginning of the program creation, if the response concerning the name was unanimously negative, I prepared
myself
for a possible change of the name of the program, and the opinions were extremely mixed.
At the beginning of the project, if the response to the name was unanimously negative, I was prepared to change it, but the opinions were extremely mixed.

View File

@@ -43,9 +43,9 @@ pub(crate) fn connect_button_move(gui_data: &GuiData) {
let mut folders: Vec<PathBuf> = Vec::new();
let g_files = file_chooser.files();
for index in 0..g_files.n_items() {
let file = &g_files.item(index);
let file = g_files.item(index);
if let Some(file) = file {
let ss = file.clone().downcast::<gtk4::gio::File>().expect("Failed to downcast to gio::File");
let ss = file.downcast::<gtk4::gio::File>().expect("Failed to downcast to gio::File");
if let Some(path_buf) = ss.path() {
folders.push(path_buf);
}
@@ -190,7 +190,7 @@ fn move_files_common(
let path = model.get::<String>(&iter, column_path);
let thing = get_full_name_from_path_name(&path, &file_name);
let destination_file = destination_folder.join(file_name);
let destination_file = destination_folder.join(&file_name);
if Path::new(&thing).is_dir() {
if let Err(e) = fs_extra::dir::move_dir(&thing, &destination_file, &CopyOptions::new()) {
messages += flg!("move_folder_failed", name = thing, reason = e.to_string()).as_str();

View File

@@ -29,7 +29,7 @@ fn change_language(gui_data: &GuiData) {
let lang_identifier = vec![LanguageIdentifier::from_bytes(lang_short.as_bytes()).expect("Failed to create LanguageIdentifier")];
for (lib, localizer) in localizers {
if let Err(error) = localizer.select(&lang_identifier) {
error!("Error while loadings languages for {lib} {error:?}");
error!("Error while loading languages for {lib} {error:?}");
}
}
gui_data.update_language();
@@ -41,7 +41,7 @@ pub(crate) fn load_system_language(gui_data: &GuiData) {
if let Some(language) = requested_languages.first() {
let old_short_lang = language.to_string();
let mut short_lang = String::new();
// removes from e.g. en_zb, ending _zd since Czkawka don't support this(maybe could add this in future, but only when)
// removes from e.g. en_zb, ending _zd since Czkawka doesn't support this (maybe could add this in future)
for i in old_short_lang.chars() {
if i.is_ascii_alphabetic() {
short_lang.push(i);
@@ -49,18 +49,11 @@ pub(crate) fn load_system_language(gui_data: &GuiData) {
break;
}
}
// let mut found: bool = false;
for (index, lang) in LANGUAGES_ALL.iter().enumerate() {
if lang.short_text == short_lang {
// found = true;
gui_data.settings.combo_box_settings_language.set_active(Some(index as u32));
break;
}
}
// if found {
// println!("INFO: Default system language {} is available, so choosing them", short_lang);
// } else {
// println!("INFO: Default system language {} is not available, using English(en) instead", short_lang);
// }
}
}

View File

@@ -33,7 +33,6 @@ impl GuiAbout {
"Rafał Mikrut",
"Alexis Lefebvre",
"Thomas Andreas Jung",
"Alexis Lefebvre",
"Peter Blackson",
"TheEvilSkeleton",
"Ben Bodenmiller",
@@ -50,6 +49,7 @@ impl GuiAbout {
"Aarni Koskela",
"Adam Boguszewski",
"Alex",
"Andreas Gerstmayr",
"AshesOfEther",
"Caduser2020",
"CalunVier",
@@ -93,9 +93,11 @@ impl GuiAbout {
"codingnewcode",
"cyqsimon",
"endolith",
"freeducks-debug",
"jann",
"kamilek96",
"kuskov",
"leapwill",
"rugk",
"santiago fn",
"tecome",

View File

@@ -348,7 +348,7 @@ pub(crate) fn get_list_store(tree_view: &TreeView) -> ListStore {
}
pub(crate) fn get_dialog_box_child(dialog: &gtk4::Dialog) -> gtk4::Box {
dialog.child().expect("Dialog have no chile").downcast::<gtk4::Box>().expect("Dialog child is not Box")
dialog.child().expect("Dialog has no child").downcast::<gtk4::Box>().expect("Dialog child is not Box")
}
pub(crate) fn change_dimension_to_krotka(dimensions: &str) -> (u64, u64) {

View File

@@ -30,7 +30,7 @@ pub const LANGUAGES_ALL: &[Language] = &[
short_text: "uk",
},
Language {
combo_box_text: "한국 (Korean)",
combo_box_text: "한국 (Korean)",
short_text: "ko",
},
Language {
@@ -42,7 +42,7 @@ pub const LANGUAGES_ALL: &[Language] = &[
short_text: "de",
},
Language {
combo_box_text: "やまと (Japanese)",
combo_box_text: "日本語 (Japanese)",
short_text: "ja",
},
Language {
@@ -70,27 +70,27 @@ pub const LANGUAGES_ALL: &[Language] = &[
short_text: "no",
},
Language {
combo_box_text: "Swedish (Svenska)",
combo_box_text: "Svenska (Swedish)",
short_text: "sv-SE",
},
Language {
combo_box_text: "المملكة العربية السعودية (Saudi Arabia)",
combo_box_text: "العربية (Arabic)",
short_text: "ar",
},
Language {
combo_box_text: "България (Bulgaria)",
combo_box_text: "Български (Bulgarian)",
short_text: "bg",
},
Language {
combo_box_text: "Ελλάδα (Greece)",
combo_box_text: "Ελληνικά (Greek)",
short_text: "el",
},
Language {
combo_box_text: "Nederland (Netherlands)",
combo_box_text: "Nederlands (Dutch)",
short_text: "nl",
},
Language {
combo_box_text: "România (Romania)",
combo_box_text: "Română (Romanian)",
short_text: "ro",
},
];

View File

@@ -124,8 +124,11 @@ bloat:
cargo bloat --release --crates --bin czkawka_gui
cargo bloat --release --crates --bin krokiet
check_complilations:
check_compilations:
git checkout Cargo.toml
# cargo install --path misc/test_compilation_speed_size
test_compilation_speed_size misc/test_compilation_speed_size/krokiet.json
python3 misc/test_compilation_speed_size/generate_md_and_plots.py
test_compilation_speed_size misc/test_compilation_speed_size/test.json
python3 misc/test_compilation_speed_size/generate_md_and_plots.py
tags:
tags=($(git tag --sort=version:refname | grep -v Nightly)); for ((i=0; i<${#tags[@]}-1; i++)); do from=${tags[$i]}; to=${tags[$i+1]}; echo "$from -> $to : $(git diff --shortstat "$from" "$to")"; done; last=${tags[-1]}; echo "$last -> master : $(git diff --shortstat "$last" master)"

View File

@@ -27,6 +27,7 @@ rayon = "1.10"
fs_extra = "1.3" # TODO replace with less buggy library
trash = "5.1"
dunce = "1.0.5"
fast_image_resize = { version = "=5.1.4", features = ["image"] } # TODO, greater versions uses unstable features, that were stabilized after 1.85.0
# Just for enums
vid_dup_finder_lib = "0.4"

View File

@@ -75,6 +75,8 @@ pub enum StrDataEmptyFiles {
pub enum IntDataTemporaryFiles {
ModificationDatePart1,
ModificationDatePart2,
SizePart1,
SizePart2,
}
#[repr(u8)]
pub enum StrDataTemporaryFiles {
@@ -264,8 +266,9 @@ impl ActiveTab {
Self::SimilarMusic => IntDataSimilarMusic::SizePart1 as usize,
Self::BrokenFiles => IntDataBrokenFiles::SizePart1 as usize,
Self::BadExtensions => IntDataBadExtensions::SizePart1 as usize,
Self::TemporaryFiles => IntDataTemporaryFiles::SizePart1 as usize,
Self::Settings | Self::About => return None,
Self::EmptyFolders | Self::InvalidSymlinks | Self::TemporaryFiles => return None,
Self::EmptyFolders | Self::InvalidSymlinks => return None,
};
Some(res)
}

View File

@@ -514,7 +514,7 @@ fn rows_select_all_by_mode(selection: &mut SelectionData, model: &ModelRc<MainLi
}
fn rows_select_all_one_by_one(model: &ModelRc<MainListModel>) {
let items_to_update = model.iter().filter_map(|e| if !e.selected_row && !e.header_row { Some(e) } else { None }).count();
let items_to_update = model.iter().filter(|e| !e.selected_row && !e.header_row).count();
trace!("[FAST][ONE_BY_ONE] select all {}/{} items", items_to_update, model.row_count());
for id in 0..model.row_count() {
let mut model_data = model

View File

@@ -826,7 +826,8 @@ fn prepare_data_model_temporary_files(fe: &TemporaryFileEntry) -> (ModelRc<Share
.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]);
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)
}
////////////////////////////////////////// Broken Files

View File

@@ -1,13 +1,13 @@
use std::path::Path;
use std::time::{Duration, Instant};
use czkawka_core::common::image::{check_if_can_display_image, get_dynamic_image_from_path};
use czkawka_core::helpers::debug_timer::Timer;
use fast_image_resize::{FilterType, ResizeAlg, ResizeOptions, Resizer};
use image::DynamicImage;
use log::{debug, error};
use slint::ComponentHandle;
use crate::{ActiveTab, Callabler, GuiState, MainWindow, Settings};
pub type ImageBufferRgba = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
pub(crate) fn connect_show_preview(app: &MainWindow) {
@@ -39,17 +39,41 @@ pub(crate) fn connect_show_preview(app: &MainWindow) {
let path = Path::new(image_path.as_str());
let res = load_image(path);
if let Some((load_time, img)) = res {
let start_timer_convert_time = Instant::now();
let slint_image = convert_into_slint_image(&img);
let convert_time = start_timer_convert_time.elapsed();
// Looks that resizing image before sending it to GUI is faster than resizing it in Slint
// Additionally it fixes issues with
if let Some((mut timer, img)) = load_image(path) {
let img_to_use = if img.width() > 1024 || img.height() > 1024 {
let bigger_side = img.width().max(img.height());
let scale_factor = bigger_side as f32 / 1024.0;
let new_width = (img.width() as f32 / scale_factor) as u32;
let new_height = (img.height() as f32 / scale_factor) as u32;
let mut dst_img = DynamicImage::new(new_width, new_height, img.color());
timer.checkpoint("creating new image buffer");
let resize_options = ResizeOptions::new().resize_alg(ResizeAlg::Interpolation(FilterType::Lanczos3));
match Resizer::new().resize(&img, &mut dst_img, Some(&resize_options)) {
Ok(()) => {
timer.checkpoint("resizing image with fast-image-resize");
dst_img
}
Err(_) => {
let r = img.resize(new_width, new_height, image::imageops::Lanczos3);
timer.checkpoint("resizing image with image-rs");
r
}
}
} else {
img
};
let slint_image = convert_into_slint_image(&img_to_use);
timer.checkpoint("converting image to Slint image");
let start_set_time = Instant::now();
gui_state.set_preview_image(slint_image);
let set_time = start_set_time.elapsed();
timer.checkpoint("setting image in GUI");
debug!("Loading image took: {load_time:?}, converting image took: {convert_time:?}, setting image took: {set_time:?}");
debug!("{}", timer.report("total", true));
set_preview_visible(&gui_state, Some(image_path.as_str()));
} else {
set_preview_visible(&gui_state, None);
@@ -73,12 +97,12 @@ fn convert_into_slint_image(img: &DynamicImage) -> slint::Image {
slint::Image::from_rgba8(buffer)
}
fn load_image(image_path: &Path) -> Option<(Duration, DynamicImage)> {
fn load_image(image_path: &Path) -> Option<(Timer, DynamicImage)> {
if !image_path.is_file() {
return None;
}
let load_img_start_timer = Instant::now();
let mut debug_timer = Timer::new("Loading and converting image in slint");
let img = match get_dynamic_image_from_path(&image_path.to_string_lossy()) {
Ok(img) => img,
@@ -87,5 +111,8 @@ fn load_image(image_path: &Path) -> Option<(Duration, DynamicImage)> {
return None;
}
};
Some((load_img_start_timer.elapsed(), img))
debug_timer.checkpoint("loading image");
Some((debug_timer, img))
}

View File

@@ -4,7 +4,8 @@ use slint::{ComponentHandle, Model, ModelRc, VecModel};
use crate::common::connect_i32_into_u64;
use crate::connect_row_selection::recalculate_small_selection_if_needed;
use crate::{ActiveTab, Callabler, GuiState, MainListModel, MainWindow, SortMode};
use crate::connect_translation::translate_sort_mode;
use crate::{ActiveTab, Callabler, GuiState, MainListModel, MainWindow, SortMode, SortModel};
pub(crate) fn connect_sort(app: &MainWindow) {
let a = app.as_weak();
@@ -28,6 +29,46 @@ pub(crate) fn connect_sort(app: &MainWindow) {
});
}
pub(crate) fn connect_showing_proper_sort_buttons(app: &MainWindow) {
set_sort_buttons(app);
let a = app.as_weak();
app.global::<Callabler>().on_tab_changed(move || {
let app = a.upgrade().expect("Failed to upgrade app :(");
set_sort_buttons(&app);
});
}
fn set_sort_buttons(app: &MainWindow) {
let active_tab = app.global::<GuiState>().get_active_tab();
let mut base_buttons = vec![
SortMode::Checked,
SortMode::FullName,
SortMode::ItemName,
SortMode::ModificationDate,
SortMode::ParentName,
SortMode::Reverse,
SortMode::Selection,
];
let additional_buttons = match active_tab.get_int_size_opt_idx() {
Some(_) => vec![SortMode::Size],
None => vec![],
};
base_buttons.extend(additional_buttons);
base_buttons.reverse();
let new_sort_model = base_buttons
.into_iter()
.map(|e| SortModel {
name: translate_sort_mode(e),
data: e,
})
.collect::<Vec<_>>();
app.global::<GuiState>().set_sort_results_list(ModelRc::new(VecModel::from(new_sort_model)));
}
mod sorts {
use super::*;

View File

@@ -44,7 +44,7 @@ pub const LANGUAGE_LIST: &[Language] = &[
left_panel_size: 185.0,
},
Language {
long_name: "한국 (Korean)",
long_name: "한국 (Korean)",
short_name: "ko",
left_panel_size: 145.0,
},
@@ -59,7 +59,7 @@ pub const LANGUAGE_LIST: &[Language] = &[
left_panel_size: 155.0,
},
Language {
long_name: "やまと (Japanese)",
long_name: "日本語 (Japanese)",
short_name: "ja",
left_panel_size: 155.0,
},
@@ -94,32 +94,32 @@ pub const LANGUAGE_LIST: &[Language] = &[
left_panel_size: 135.0,
},
Language {
long_name: "Swedish (Svenska)",
long_name: "Svenska (Swedish)",
short_name: "sv-SE",
left_panel_size: 130.0,
},
Language {
long_name: "المملكة العربية السعودية (Saudi Arabia)",
long_name: "العربية (Arabic)",
short_name: "ar",
left_panel_size: 135.0,
},
Language {
long_name: "България (Bulgaria)",
long_name: "Български (Bulgarian)",
short_name: "bg",
left_panel_size: 165.0,
},
Language {
long_name: "Ελλάδα (Greece)",
long_name: "Ελληνικά (Greek)",
short_name: "el",
left_panel_size: 160.0,
},
Language {
long_name: "Nederland (Netherlands)",
long_name: "Nederlands (Dutch)",
short_name: "nl",
left_panel_size: 165.0,
},
Language {
long_name: "România (Romania)",
long_name: "Română (Romanian)",
short_name: "ro",
left_panel_size: 140.0,
},
@@ -166,7 +166,7 @@ pub(crate) fn change_language(app: &MainWindow) {
let lang_identifier = vec![LanguageIdentifier::from_bytes(lang_items.short_name.as_bytes()).expect("Failed to create LanguageIdentifier")];
for (lib, localizer) in localizers {
if let Err(error) = localizer.select(&lang_identifier) {
error!("Error while loadings languages for {lib} {error:?}");
error!("Error while loading languages for {lib} {error:?}");
}
}
@@ -417,3 +417,16 @@ pub(crate) fn translate_select_mode(select_mode: SelectMode) -> SharedString {
SelectMode::SelectOldest => flk!("selection_oldest").into(),
}
}
pub(crate) fn translate_sort_mode(sort_mode: SortMode) -> SharedString {
match sort_mode {
SortMode::ItemName => flk!("sort_by_item_name").into(),
SortMode::ParentName => flk!("sort_by_parent_name").into(),
SortMode::FullName => flk!("sort_by_full_name").into(),
SortMode::Size => flk!("sort_by_size").into(),
SortMode::ModificationDate => flk!("sort_by_modification_date").into(),
SortMode::Selection => flk!("sort_by_selection").into(),
SortMode::Checked => flk!("sort_by_checked").into(),
SortMode::Reverse => flk!("sort_reverse").into(),
}
}

View File

@@ -31,7 +31,7 @@ use crate::connect_save::connect_save;
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_sort::connect_sort;
use crate::connect_sort::{connect_showing_proper_sort_buttons, connect_sort};
use crate::connect_stop::connect_stop_button;
use crate::connect_translation::connect_translations;
// TODO - at start this should be used, to be sure that rust models are in sync with slint models
@@ -105,6 +105,7 @@ fn main() {
connect_save(&app, Arc::clone(&shared_models));
connect_row_selections(&app);
connect_sort(&app);
connect_showing_proper_sort_buttons(&app);
// Popups gather their size, after starting/closing popup at least once
// This is simpler solution, than setting sizes of popups manually for each language

View File

@@ -4,11 +4,11 @@ use crate::MainListModel;
pub(crate) fn get_main_list_model() -> MainListModel {
MainListModel {
selected_row: false,
val_int: Default::default(),
checked: false,
filled_header_row: false,
header_row: false,
selected_row: false,
val_int: Default::default(),
val_str: Default::default(),
}
}

View File

@@ -21,6 +21,8 @@ export global ColorPalette {
in-out property <color> line_item_color: Settings.dark_theme ? #222222 : #dddddd;
in-out property <color> remove_item_color_button: Settings.dark_theme ? #222222 : #dddddd;
public pure function get_listview_color(selected: bool, hovered: bool) -> color {
if (selected) {
return hovered ? self.list_view_item_selected_hovered_color : self.list_view_item_selected_color;

View File

@@ -64,7 +64,7 @@ export component IncludedDirectories {
HorizontalLayout {
padding-left: 7.5px;
spacing: 5px;
spacing: 0px;
width: parent.width;
CheckBox {
@@ -75,20 +75,26 @@ export component IncludedDirectories {
width: size_referenced_folder;
}
HorizontalLayout {
Text {
horizontal-stretch: 1.0;
height: parent.height;
text: r.path;
vertical-alignment: center;
horizontal-alignment: left;
}
}
HorizontalLayout {
width: parent.width;
padding-left: 5px;
Rectangle { }
Rectangle {
width: 50px;
background: ColorPalette.remove_item_color_button;
horizontal-stretch: 1.0;
padding-left: 5px;
Text {
horizontal-stretch: 1.0;
height: parent.height;
text: r.path;
vertical-alignment: center;
}
Button {
width: 50px;
horizontal-stretch: 1.0;
icon: @image-url("../icons/krokiet_delete.svg");
colorize-icon: true;
clicked => {
@@ -153,23 +159,30 @@ export component ExcludedDirectories {
}
}
Text {
width: parent.width;
horizontal-stretch: 1.0;
height: parent.height;
text: r.path;
vertical-alignment: center;
horizontal-alignment: left;
x: 5px;
}
HorizontalLayout {
width: parent.width;
padding-left: 5px;
Text {
horizontal-stretch: 1.0;
height: parent.height;
text: r.path;
vertical-alignment: center;
}
Button {
Rectangle { }
Rectangle {
width: 50px;
background: ColorPalette.remove_item_color_button;
horizontal-stretch: 1.0;
icon: @image-url("../icons/krokiet_delete.svg");
colorize-icon: true;
clicked => {
Callabler.remove_item_directories(false, idx);
Button {
width: 50px;
icon: @image-url("../icons/krokiet_delete.svg");
colorize-icon: true;
clicked => {
Callabler.remove_item_directories(false, idx);
}
}
}
}

View File

@@ -8,9 +8,9 @@ export global Settings {
in-out property <int> language_index: 0;
in-out property <string> language_value: "English";
in-out property <[IncludedDirectoriesModel]> included_directories_model: [{ path: "/home/path", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false }];
in-out property <[IncludedDirectoriesModel]> included_directories_model: [{ path: "/home/pathssssssssssssssssssssss", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false },{ path: "/home/path", referenced_folder: false, selected_row: false }];
in-out property <int> included_directories_model_selected_idx: -1;
in-out property <[ExcludedDirectoriesModel]> excluded_directories_model: [{ path:"/home/path", selected_row: false },{ path:"/home/path", selected_row: false },{ path:"/home/path", selected_row: false },{ path:"/home/path", selected_row: false }, { path:"/home/a", selected_row: false }];
in-out property <[ExcludedDirectoriesModel]> excluded_directories_model: [{ path:"/home/pathssssssssssssssssssssssss", selected_row: false },{ path:"/home/path", selected_row: false },{ path:"/home/path", selected_row: false },{ path:"/home/path", selected_row: false }, { path:"/home/a", selected_row: false }];
in-out property <int> excluded_directories_model_selected_idx: -1;
// Settings

View File

@@ -9,10 +9,7 @@ humansize = "2.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.142"
strum = { version = "0.27.2", features = ["strum_macros"] }
#polars = { version = "0.50.0", features = ["full"] }
#polars-io ="0.50.0"
#polars-core = "0.50.0"
#plotters = "0.3.7"
plotters = { version = "0.3.7", features = ["all_elements", "all_series"] }
[profile.release]
panic = "unwind"

View File

@@ -0,0 +1,12 @@
# Test compilation speed size
This project is used to get the compilation speed and size of generated rust binaries for different configurations.
## How to use?
- install app `cargo install --path .`
- configure json config, just like `test.json` or `krokiet.json` - allowed values you can find in `src/model.rs`
- go to your project root(if project is in workspace, you need to go to the workspace root)
- run `test_compilation_speed_size config.json`
- fix compilation errors if any happens(mixing some compilation flags can cause compilation errors)
- install python dependencies with `sudo apt install python3-matplotlib python3-pandas python3-tabulate` or similar command
- generate charts with `python3 generate_md_and_plots.py`
- generated md and png files will be in `charts` folder

View File

@@ -47,7 +47,7 @@ def plot_barh(
data = data.dropna(subset=[value_col])
data_sorted = data.sort_values(value_col, ascending=False)
plt.figure(figsize=(12, 10))
plt.figure(figsize=(12, 10), dpi=300)
bars = plt.barh(data_sorted[config_col], data_sorted[value_col] / unit_div, color=color)
ax = plt.gca()

View File

@@ -9,6 +9,10 @@
"cranelift": "Both"
},
"build_config": [
{
"name": "debug",
"rust_base_config": "Debug"
},
{
"name": "debug + debug disabled",
"rust_base_config": "Debug",
@@ -19,11 +23,20 @@
"rust_base_config": "Debug",
"build_or_check": "Check"
},
{
"name": "release",
"rust_base_config": "Release"
},
{
"name": "release + debug info",
"rust_base_config": "Release",
"debug": "Full"
},
{
"name": "release + native",
"rust_base_config": "Release",
"native": true
},
{
"name": "release + opt o2",
"rust_base_config": "Release",
@@ -71,6 +84,14 @@
"codegen_units": "One",
"panic": "Abort"
},
{
"name": "release + fat lto + cu 1 + panic abort + native",
"rust_base_config": "Release",
"lto": "Fat",
"codegen_units": "One",
"panic": "Abort",
"native": true
},
{
"name": "release + fat lto + cu 1 + panic abort + build-std",
"rust_base_config": "Release",

View File

@@ -1,279 +0,0 @@
// TODO - this is bigger task, simple changing almost 1:1 code from python to rust, not works
// use polars_core::prelude::*;
// use polars_io::prelude::*;
// use std::fs;
// use plotters::prelude::*;
//
// fn load_dataframe_from_csv(file_path: &str) -> PolarsResult<DataFrame> {
// let mut df = CsvReadOptions::default()
// .with_has_header(true)
// .map_parse_options(|opts| opts.with_separator(b'|'))
// .try_into_reader_with_file_path(Some(file_path.into()))?
// .finish()?;
//
// // Strip whitespace from string columns (bez unsafe)
// let col_names: Vec<String> = df.get_column_names().iter().map(|s| s.to_string()).collect();
// for name in col_names {
// if let Ok(col) = df.column(&name) {
// if matches!(col.dtype(), DataType::String) {
// let trimmed = col.str()?.apply(|v| v.map(|s| std::borrow::Cow::Owned(s.trim().to_string())));
// df.with_column(trimmed.into_series())?;
// }
// }
// }
//
// // Convert columns to Float64, regardless of their current type
// let try_parse_numeric = |df: &mut DataFrame, col_name: &str| -> PolarsResult<()> {
// if let Ok(col) = df.column(col_name) {
// let float_col = match col.dtype() {
// DataType::Float64 => col.clone(),
// DataType::Int64 => col.cast(&DataType::Float64)?,
// DataType::Int32 => col.cast(&DataType::Float64)?,
// DataType::UInt64 => col.cast(&DataType::Float64)?,
// DataType::UInt32 => col.cast(&DataType::Float64)?,
// DataType::String => {
// let chunked = col
// .str()?
// .into_iter()
// .map(|v| v.and_then(|s| s.parse::<f64>().ok()))
// .collect::<Float64Chunked>();
// chunked.into_series().into_column()
// }
// _ => col.cast(&DataType::Float64)?,
// };
// df.with_column(float_col)?;
// }
// Ok(())
// };
//
// try_parse_numeric(&mut df, "Output File Size(in bytes)")?;
// try_parse_numeric(&mut df, "Target Folder Size(in bytes)")?;
// try_parse_numeric(&mut df, "Compilation Time(seconds)")?;
// try_parse_numeric(&mut df, "Rebuild Time(seconds)")?;
//
// Ok(df)
// }
//
// fn save_as_md(df: &DataFrame, path: &str) -> PolarsResult<()> {
// // Save markdown table (without types, shape, or empty columns)
// let columns: Vec<String> = df
// .get_column_names()
// .iter()
// .filter(|c| !c.contains('(') && !c.contains("Thread"))
// .map(|s| s.to_string())
// .collect();
// let mut subdf = df.select(&columns)?;
//
// // Remove last column if it's empty or all nulls
// let drop_col = subdf
// .get_columns()
// .last()
// .filter(|last| last.null_count() == last.len())
// .map(|last| last.name().to_string());
// if let Some(col_name) = drop_col {
// subdf.drop_in_place(&col_name)?;
// }
//
// // Write markdown manually (no types, no shape, no quotes for strings)
// let mut md = String::new();
// // Header
// let header: Vec<String> = subdf.get_column_names().iter().map(|s| s.to_string()).collect();
// md.push('|');
// md.push_str(&header.join("|"));
// md.push_str("|\n|");
// md.push_str(&header.iter().map(|_| "---").collect::<Vec<_>>().join("|"));
// md.push_str("|\n");
// // Rows
// for row in 0..subdf.height() {
// md.push('|');
// for (i, col) in subdf.get_columns().iter().enumerate() {
// let val = col.get(row);
// let s = match val {
// Ok(AnyValue::Null) => "".to_string(),
// Ok(v) => {
// let mut s = v.to_string();
// // Remove surrounding quotes if present
// if s.starts_with('"') && s.ends_with('"') && s.len() > 1 {
// s = s[1..s.len()-1].to_string();
// }
// s
// },
// Err(_) => "".to_string(),
// };
// md.push_str(&s);
// if i != subdf.width() - 1 {
// md.push('|');
// }
// }
// md.push_str("|\n");
// }
// std::fs::write(path, md)?;
// Ok(())
// }
//
// fn plot_barh(
// df: &DataFrame,
// value_col: &str,
// xlabel: &str,
// title: &str,
// filename: &str,
// _fmt: &str,
// unit_div: f64,
// dropna: bool,
// color: RGBColor,
// ) -> PolarsResult<()> {
// // Helper to find a Series by name, handling possible quotes and whitespace
// fn find_col<'a>(df: &'a DataFrame, name: &str) -> Option<&'a Series> {
// let name_trimmed = name.trim();
// println!("Columns {:?}", df.get_column_names());
// println!("Looking for column: {}", name_trimmed);
// df.get_columns().iter().find_map(|s| {
// let sname = s.name().trim();
// if sname == name_trimmed
// || sname == name_trimmed.replace('\"', "")
// || sname == format!("\"{}\"", name_trimmed)
// {
// Some(s)
// } else {
// None
// }
// }).and_then(|e|e.as_series())
// }
//
// let config_col = find_col(df, "BuildConfig")
// .or_else(|| find_col(df, "Config"))
// .ok_or_else(|| polars_core::error::PolarsError::ComputeError("Config column not found".into()))?;
//
// let value_col = find_col(df, value_col)
// .ok_or_else(|| polars_core::error::PolarsError::ComputeError(format!("{} not found", value_col).into()))?;
//
// // Use .str()? on Series
// let config_utf8 = config_col.str()?.into_iter();
// let value_f64 = value_col.f64()?.into_iter();
//
// let mut configs = Vec::new();
// let mut values = Vec::new();
//
// for (conf, val) in config_utf8.zip(value_f64) {
// if dropna && val.is_none() {
// continue;
// }
// configs.push(conf.unwrap_or("").to_string());
// values.push(val.unwrap_or(0.0) / unit_div);
// }
//
// // Sort descending by value
// let mut zipped: Vec<_> = configs.into_iter().zip(values.into_iter()).collect();
// zipped.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
// let (configs, values): (Vec<_>, Vec<_>) = zipped.into_iter().unzip();
//
// // Plot with plotters
// let root = BitMapBackend::new(filename, (1200, 900)).into_drawing_area();
// root.fill(&WHITE).unwrap();
//
// let max_val = values.iter().cloned().fold(0.0/0.0, f64::max);
// let x_max = if max_val.is_finite() && max_val > 0.0 { max_val * 1.15 } else { 1.0 };
//
// let mut chart = ChartBuilder::on(&root)
// .caption(title, ("Noto Sans", 30).into_font())
// .margin(40)
// .x_label_area_size(60)
// .y_label_area_size(300)
// .build_cartesian_2d(0f64..x_max, 0..configs.len())
// .unwrap();
//
// chart
// .configure_mesh()
// .x_desc(xlabel)
// .y_desc("")
// .y_labels(configs.len())
// .y_label_formatter(&|idx| configs.get(*idx).cloned().unwrap_or_default())
// .x_label_formatter(&|x| format!("{}", x))
// .axis_desc_style(("Noto Sans", 20))
// .label_style(("Noto Sans", 16))
// .draw()
// .unwrap();
//
// for (i, v) in values.iter().enumerate() {
// chart
// .draw_series(std::iter::once(Rectangle::new(
// [(0.0, i), (*v, i + 1)],
// color.filled(),
// )))
// .unwrap()
// .label(configs[i].clone())
// .legend(move |(x, y)| {
// Rectangle::new([(x, y - 5), (x + 20, y + 5)], color.filled())
// });
//
// // Draw value label
// chart
// .draw_series(std::iter::once(Text::new(
// format!("{}", v),
// (*v, i),
// ("Noto Sans", 16).into_font().color(&BLACK),
// )))
// .unwrap();
// }
//
// root.present().unwrap();
//
// Ok(())
// }
//
// pub fn create_graphs() -> PolarsResult<()> {
// let df = load_dataframe_from_csv("compilation_results.txt")?;
//
// // Create output directory
// fs::create_dir_all("charts")?;
//
// plot_barh(
// &df,
// "Compilation Time(seconds)",
// "Compilation Time (seconds)",
// "Compilation Time by Config",
// "charts/compilation_time.png",
// "{:.1}s",
// 1.0,
// false,
// RGBColor(31, 119, 180),
// )?;
// plot_barh(
// &df,
// "Rebuild Time(seconds)",
// "Rebuild Time (seconds)",
// "Rebuild Time by Config",
// "charts/rebuild_time.png",
// "{:.1}s",
// 1.0,
// false,
// RGBColor(255, 127, 14),
// )?;
// plot_barh(
// &df,
// "Output File Size(in bytes)",
// "Output File Size (MB)",
// "Output File Size by Config",
// "charts/output_file_size.png",
// "{:.1} MB",
// 1024.0 * 1024.0,
// true,
// RGBColor(44, 160, 44),
// )?;
// plot_barh(
// &df,
// "Target Folder Size(in bytes)",
// "Target Folder Size (GB)",
// "Target Folder Size by Config",
// "charts/target_folder_size.png",
// "{:.1} GB",
// 1024.0 * 1024.0 * 1024.0,
// false,
// RGBColor(214, 39, 40),
// )?;
//
// save_as_md(&df, "charts/compilation_results.md")?;
//
// Ok(())
// }

View File

@@ -1,4 +1,5 @@
mod model;
mod new_chart;
use crate::model::{
BuildConfig, BuildOrCheck, Config, Panic, Project, Results,
@@ -9,11 +10,13 @@ use std::io::Write;
use std::path::Path;
use std::process::exit;
use walkdir::WalkDir;
// use crate::new_chart::create_chart;
const PROFILE_NAME: &str = "fff";
const RESULTS_FILE_NAME: &str = "compilation_results.txt";
fn main() {
// create_chart(); // TODO currently is broken a little
let Some(first_arg) = std::env::args().nth(1) else {
eprintln!("Please provide a path to the configuration json file as the first argument.");
exit(1);
@@ -104,9 +107,10 @@ fn run_cargo_build(mold: bool, cranelift: bool, build_config: &BuildConfig, proj
}
let mut rust_flags = None;
if mold {
let to_add = "-C link-arg=-fuse-ld=mold";
rust_flags = match rust_flags {
None => Some("-C link-arg=-fuse-ld=mold".to_string()),
Some(flags) => Some(format!("{} -C link-arg=-fuse-ld=mold", flags)),
None => Some(to_add.to_string()),
Some(flags) => Some(format!("{flags} {to_add}")),
};
}
if build_config.build_std {
@@ -116,6 +120,13 @@ fn run_cargo_build(mold: bool, cranelift: bool, build_config: &BuildConfig, proj
command.args(["-Z", "build-std=std"]);
}
}
if build_config.native {
let to_add = "-C target-cpu=native";
rust_flags = match rust_flags {
None => Some(to_add.to_string()),
Some(flags) => Some(format!("{flags} {to_add}")),
};
}
if let Some(rust_flags) = rust_flags {
command.env("RUSTFLAGS", rust_flags);

View File

@@ -40,6 +40,7 @@ pub struct BuildConfigRead {
pub overflow_checks: Option<OverflowChecks>,
pub incremental: Option<Incremental>,
pub build_std: Option<bool>,
pub native: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -56,6 +57,7 @@ pub struct BuildConfig {
pub overflow_checks: OverflowChecks,
pub incremental: Incremental,
pub build_std: bool,
pub native: bool,
}
impl From<BuildConfigRead> for BuildConfig {
@@ -74,6 +76,7 @@ impl From<BuildConfigRead> for BuildConfig {
overflow_checks: OverflowChecks::Off,
incremental: Incremental::Off,
build_std: false,
native: false,
},
RustBaseConfig::Debug => BuildConfig {
name: "debug".to_string(),
@@ -88,6 +91,7 @@ impl From<BuildConfigRead> for BuildConfig {
overflow_checks: OverflowChecks::On,
incremental: Incremental::On,
build_std: false,
native: false,
}
};
@@ -104,6 +108,7 @@ impl From<BuildConfigRead> for BuildConfig {
overflow_checks: config.overflow_checks.unwrap_or(base_config.overflow_checks),
incremental: config.incremental.unwrap_or(base_config.incremental),
build_std: config.build_std.unwrap_or(false),
native: config.native.unwrap_or(false),
}
}
}

View File

@@ -0,0 +1,87 @@
use plotters::prelude::*;
use std::fs::{self, File};
use std::io::{BufRead, BufReader}; use plotters::style::text_anchor::Pos;
use plotters::style::text_anchor::VPos; use plotters::style::text_anchor::HPos;
pub fn create_chart() -> Result<(), Box<dyn std::error::Error>> {
// Prepare output directory
fs::create_dir_all("charts")?;
// Open and read the file
let file = File::open("compilation_results.txt")?;
let reader = BufReader::new(file);
// Read header and find column indices
let mut lines = reader.lines();
let header = lines.next().unwrap()?;
let headers: Vec<&str> = header.split('|').collect();
let config_idx = headers.iter().position(|&h| h.trim() == "BuildConfig").unwrap();
let time_idx = headers.iter().position(|&h| h.trim() == "Compilation Time(seconds)").unwrap();
// Parse data
let mut data = Vec::new();
for line in lines {
let line = line?;
let cols: Vec<&str> = line.split('|').collect();
if cols.len() <= time_idx { continue; }
let config = cols[config_idx].trim();
let time: f64 = cols[time_idx].trim().parse().unwrap_or(0.0);
data.push((config.to_string(), time));
}
// Sort by time descending
data.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
// Plot
let root = BitMapBackend::new("charts/compilation_time.png", (1200, 800)).into_drawing_area();
root.fill(&WHITE)?;
let max_time = data.iter().map(|(_, t)| *t).fold(0.0, f64::max);
let mut chart = ChartBuilder::on(&root)
.caption("Compilation Time by Config", ("Noto Sans", 50))
.margin(20)
.x_label_area_size(80)
.y_label_area_size(500)
.build_cartesian_2d(0f64..(max_time * 1.2), 0..data.len())?;
chart
.configure_mesh()
.x_desc("Compilation Time (seconds)")
.y_labels(data.len())
.y_label_formatter(&|idx| {
if let Some((label, _)) = data.get(*idx) {
label.clone()
} else {
"".to_string()
}
})
.y_label_style(("Noto Sans", 30).into_font().style(FontStyle::Bold))
.x_label_style(("Noto Sans", 30).into_font())
.y_label_offset(-150)
.draw()?;
chart.draw_series(
data.iter().enumerate().map(|(i, (_label, value))| {
Rectangle::new(
[(0.0, i), (*value, i + 1)],
BLUE.filled(),
)
}),
)?;
chart.draw_series(
data.iter().enumerate().map(|(i, (_label, value))| {
let x = *value;
let y = i;
Text::new(
format!("{:.2}", value),
(x, y),
("Noto Sans", 35).into_font().color(&RED).pos(Pos::new(HPos::Right, VPos::Center)),
)
}),
)?;
root.present()?;
Ok(())
}