Fix 12 medium-severity bugs across all crates

- Escape backslashes in Nautilus preset names preventing Python injection
- Fix tiled watermarks starting at (spacing,spacing) instead of (0,0)
- Fix text watermark width overestimation (1.0x to 0.6x multiplier)
- Fix output_dpi forcing re-encoding for metadata-only presets
- Fix AVIF/WebP compression detection comparing against wrong preset values
- Add shared batch_updating guard for Ctrl+A/Ctrl+Shift+A select actions
- Fix overwrite conflict check ignoring preserve_directory_structure
- Add changes_filename()/changes_extension() for smarter overwrite checks
- Fix watch folder hardcoding "Blog Photos" preset
- Fix undo dropping history for partially-trashed batches
- Fix skipped files inflating size statistics
- Make CLI watch config writes atomic
This commit is contained in:
2026-03-07 23:35:32 +02:00
parent 1a174d40a7
commit 7e5d19ab03
8 changed files with 77 additions and 30 deletions

View File

@@ -398,7 +398,7 @@ impl PipelineExecutor {
let output_path = match job.overwrite_behavior {
crate::operations::OverwriteAction::Skip if output_path.exists() => {
return Ok((input_size, 0, std::path::PathBuf::new()));
return Ok((0, 0, std::path::PathBuf::new()));
}
crate::operations::OverwriteAction::AutoRename if output_path.exists() => {
find_unique_path(&output_path)
@@ -563,7 +563,7 @@ impl PipelineExecutor {
crate::operations::OverwriteAction::Skip => {
if output_path.exists() {
// Return 0 bytes written - file was skipped
return Ok((input_size, 0, std::path::PathBuf::new()));
return Ok((0, 0, std::path::PathBuf::new()));
}
output_path
}

View File

@@ -151,8 +151,8 @@ fn install_nautilus() -> Result<()> {
\x20 item.connect('activate', self._on_preset, '{}', files)\n\
\x20 submenu.append_item(item)\n\n",
name.replace(' ', "_"),
name.replace('\'', "\\'"),
name.replace('\'', "\\'"),
name.replace('\\', "\\\\").replace('\'', "\\'"),
name.replace('\\', "\\\\").replace('\'', "\\'"),
));
}

View File

@@ -120,6 +120,11 @@ impl ConvertConfig {
}
}
}
/// Returns true if this conversion will change at least some file extensions.
pub fn changes_extension(&self) -> bool {
!matches!(self, Self::KeepOriginal)
}
}
// --- Compress ---
@@ -318,6 +323,18 @@ pub struct RenameConfig {
fn default_counter_position() -> u32 { 3 }
impl RenameConfig {
/// Returns true if this rename config would actually change any filename.
pub fn changes_filename(&self) -> bool {
!self.prefix.is_empty()
|| !self.suffix.is_empty()
|| self.counter_enabled
|| !self.regex_find.is_empty()
|| self.case_mode > 0
|| self.replace_spaces > 0
|| self.special_chars > 0
|| self.template.is_some()
}
/// Pre-compile the regex for batch use. Call once before a loop of apply_simple_compiled.
pub fn compile_regex(&self) -> Option<regex::Regex> {
rename::compile_rename_regex(&self.regex_find)

View File

@@ -198,7 +198,7 @@ fn render_text_to_image(
opacity: f32,
) -> image::RgbaImage {
let scale = ab_glyph::PxScale::from(font_size);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 1.0) as u32).saturating_add(4).min(16384);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as u32).saturating_add(4).min(16384);
let text_height = ((font_size.min(1000.0) * 1.4) as u32).saturating_add(4).min(4096);
let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
@@ -296,7 +296,7 @@ fn apply_text_watermark(
} else {
// No rotation - draw text directly (faster)
let scale = ab_glyph::PxScale::from(font_size);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 1.0) as u32).saturating_add(4).min(16384);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as u32).saturating_add(4).min(16384);
let text_height = ((font_size.min(1000.0) * 1.4) as u32).saturating_add(4).min(4096);
let text_dims = Dimensions {
width: text_width,
@@ -351,9 +351,9 @@ fn apply_tiled_text_watermark(
let tw = tile.width();
let th = tile.height();
let mut y: i64 = spacing as i64;
let mut y: i64 = 0;
while y < ih as i64 {
let mut x: i64 = spacing as i64;
let mut x: i64 = 0;
while x < iw as i64 {
image::imageops::overlay(&mut rgba, &tile, x, y);
x += tw as i64 + spacing as i64;
@@ -366,12 +366,12 @@ fn apply_tiled_text_watermark(
let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
let draw_color = Rgba([color[0], color[1], color[2], alpha]);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 1.0) as i64 + 4).min(16384);
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as i64 + 4).min(16384);
let text_height = ((font_size.min(1000.0) * 1.4) as i64 + 4).min(4096);
let mut y = spacing as i64;
let mut y: i64 = 0;
while y < ih as i64 {
let mut x = spacing as i64;
let mut x: i64 = 0;
while x < iw as i64 {
draw_text_mut(&mut rgba, draw_color, x as i32, y as i32, scale, &font, text);
x += text_width + spacing as i64;
@@ -413,9 +413,9 @@ fn apply_tiled_image_watermark(
let mut base = img.into_rgba8();
let (iw, ih) = (base.width(), base.height());
let mut ty = spacing;
let mut ty = 0u32;
while ty < ih {
let mut tx = spacing;
let mut tx = 0u32;
while tx < iw {
for oy in 0..oh {
for ox in 0..ow {

View File

@@ -67,7 +67,7 @@ impl Preset {
crate::operations::CompressConfig::Preset(p) => p.avif_speed(),
_ => 6,
}).unwrap_or(6),
output_dpi: 72,
output_dpi: 0,
}
}