Fix 26 bugs, edge cases, and consistency issues from fifth audit pass

Critical: undo toast now trashes only batch output files (not entire dir),
JPEG scanline write errors propagated, selective metadata write result returned.

High: zero-dimension guards in ResizeConfig/fit_within, negative aspect ratio
rejection, FM integration toggle infinite recursion guard, saturating counter
arithmetic in executor.

Medium: PNG compression level passed to oxipng, pct mode updates job_config,
external file loading updates step indicator, CLI undo removes history entries,
watch config write failures reported, fast-copy path reads image dimensions for
rename templates, discovery excludes unprocessable formats (heic/svg/ico/jxl),
CLI warns on invalid algorithm/overwrite values, resolve_collision trailing dot
fix, generation guards on all preview threads to cancel stale results, default
DPI aligned to 0, watermark text width uses char count not byte length.

Low: binary path escaped in Nautilus extension, file dialog filter aligned with
discovery, reset_wizard clears preset_mode and output_dir.
This commit is contained in:
2026-03-07 19:47:23 +02:00
parent 270a7db60d
commit b432cc7431
44 changed files with 5748 additions and 2221 deletions

View File

@@ -5,6 +5,7 @@ use crate::pipeline::ProcessingJob;
use crate::types::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Preset {
pub name: String,
pub description: String,
@@ -20,6 +21,25 @@ pub struct Preset {
pub rename: Option<RenameConfig>,
}
impl Default for Preset {
fn default() -> Self {
Self {
name: String::new(),
description: String::new(),
icon: "image-x-generic-symbolic".into(),
is_custom: true,
resize: None,
rotation: None,
flip: None,
convert: None,
compress: None,
metadata: None,
watermark: None,
rename: None,
}
}
}
impl Preset {
pub fn to_job(
&self,
@@ -40,7 +60,7 @@ impl Preset {
metadata: self.metadata.clone(),
watermark: self.watermark.clone(),
rename: self.rename.clone(),
overwrite_behavior: crate::operations::OverwriteBehavior::default(),
overwrite_behavior: crate::operations::OverwriteAction::default(),
preserve_directory_structure: false,
progressive_jpeg: false,
avif_speed: 6,
@@ -58,6 +78,7 @@ impl Preset {
Self::builtin_photographer_export(),
Self::builtin_archive_compress(),
Self::builtin_fediverse_ready(),
Self::builtin_print_ready(),
]
}
@@ -119,8 +140,12 @@ impl Preset {
suffix: String::new(),
counter_start: 1,
counter_padding: 3,
counter_enabled: true,
counter_position: 3,
template: None,
case_mode: 0,
replace_spaces: 0,
special_chars: 0,
regex_find: String::new(),
regex_replace: String::new(),
}),
@@ -179,8 +204,12 @@ impl Preset {
suffix: String::new(),
counter_start: 1,
counter_padding: 4,
counter_enabled: true,
counter_position: 3,
template: Some("{exif_date}_{name}_{counter:4}".into()),
case_mode: 0,
replace_spaces: 0,
special_chars: 0,
regex_find: String::new(),
regex_replace: String::new(),
}),
@@ -204,6 +233,23 @@ impl Preset {
}
}
pub fn builtin_print_ready() -> Preset {
Preset {
name: "Print Ready".into(),
description: "Maximum quality, convert to PNG, keep all metadata".into(),
icon: "printer-symbolic".into(),
is_custom: false,
resize: None,
rotation: None,
flip: None,
convert: Some(ConvertConfig::SingleFormat(ImageFormat::Png)),
compress: Some(CompressConfig::Preset(QualityPreset::Maximum)),
metadata: Some(MetadataConfig::KeepAll),
watermark: None,
rename: None,
}
}
pub fn builtin_fediverse_ready() -> Preset {
Preset {
name: "Fediverse Ready".into(),