Fix overflow, race condition, and format bugs

This commit is contained in:
2026-03-07 22:14:48 +02:00
parent 150d483fbe
commit f471d22767
18 changed files with 600 additions and 113 deletions

View File

@@ -140,9 +140,117 @@ pub fn build_output_page(state: &AppState) -> adw::NavigationPage {
scrolled.set_child(Some(&content));
adw::NavigationPage::builder()
let page = adw::NavigationPage::builder()
.title("Output & Process")
.tag("step-output")
.child(&scrolled)
.build()
.build();
// Refresh stats and summary when navigating to this page
{
let lf = state.loaded_files.clone();
let ef = state.excluded_files.clone();
let jc = state.job_config.clone();
let od = state.output_dir.clone();
let cr = count_row.clone();
let or = output_row.clone();
let sb = summary_box.clone();
page.connect_map(move |_| {
// Update image count and size
let files = lf.borrow();
let excluded = ef.borrow();
let included_count = files.iter().filter(|p| !excluded.contains(*p)).count();
let total_size: u64 = files.iter()
.filter(|p| !excluded.contains(*p))
.filter_map(|p| std::fs::metadata(p).ok())
.map(|m| m.len())
.sum();
cr.set_subtitle(&format!("{} images ({})", included_count, format_size(total_size)));
drop(files);
drop(excluded);
// Update output directory display
let dir_text = od.borrow()
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "processed/ (subfolder next to originals)".to_string());
or.set_subtitle(&dir_text);
// Build operation summary
while let Some(child) = sb.first_child() {
sb.remove(&child);
}
let cfg = jc.borrow();
let mut ops: Vec<(&str, String)> = Vec::new();
if cfg.resize_enabled {
let mode = match cfg.resize_mode {
0 => format!("{}x{} (exact)", cfg.resize_width, cfg.resize_height),
_ => format!("fit {}x{}", cfg.resize_width, cfg.resize_height),
};
ops.push(("Resize", mode));
}
if cfg.adjustments_enabled {
let mut parts = Vec::new();
if cfg.rotation > 0 { parts.push("rotate"); }
if cfg.flip > 0 { parts.push("flip"); }
if cfg.brightness != 0 { parts.push("brightness"); }
if cfg.contrast != 0 { parts.push("contrast"); }
if cfg.saturation != 0 { parts.push("saturation"); }
if cfg.grayscale { parts.push("grayscale"); }
if cfg.sepia { parts.push("sepia"); }
if cfg.sharpen { parts.push("sharpen"); }
if cfg.crop_aspect_ratio > 0 { parts.push("crop"); }
if cfg.trim_whitespace { parts.push("trim"); }
if cfg.canvas_padding > 0 { parts.push("padding"); }
let desc = if parts.is_empty() { "enabled".into() } else { parts.join(", ") };
ops.push(("Adjustments", desc));
}
if cfg.convert_enabled {
let fmt = cfg.convert_format.map(|f| f.extension().to_uppercase())
.unwrap_or_else(|| "per-format mapping".into());
ops.push(("Convert", fmt));
}
if cfg.compress_enabled {
ops.push(("Compress", cfg.quality_preset.label().into()));
}
if cfg.metadata_enabled {
let mode = match &cfg.metadata_mode {
crate::app::MetadataMode::StripAll => "strip all",
crate::app::MetadataMode::KeepAll => "keep all",
crate::app::MetadataMode::Privacy => "privacy mode",
crate::app::MetadataMode::Custom => "custom",
};
ops.push(("Metadata", mode.into()));
}
if cfg.watermark_enabled {
let wm_type = if cfg.watermark_use_image { "image" } else { "text" };
ops.push(("Watermark", wm_type.into()));
}
if cfg.rename_enabled {
ops.push(("Rename", "enabled".into()));
}
if ops.is_empty() {
let row = adw::ActionRow::builder()
.title("No operations enabled")
.subtitle("Go back and enable at least one operation")
.build();
row.add_prefix(&gtk::Image::from_icon_name("dialog-warning-symbolic"));
sb.append(&row);
} else {
for (name, desc) in &ops {
let row = adw::ActionRow::builder()
.title(*name)
.subtitle(desc.as_str())
.build();
row.add_prefix(&gtk::Image::from_icon_name("emblem-ok-symbolic"));
sb.append(&row);
}
}
});
}
page
}