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:
@@ -8,10 +8,19 @@ use crate::preset::Preset;
|
||||
|
||||
fn default_config_dir() -> PathBuf {
|
||||
dirs::config_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("~/.config"))
|
||||
.or_else(|| dirs::home_dir().map(|h| h.join(".config")))
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join("pixstrip")
|
||||
}
|
||||
|
||||
/// Write to a temporary file then rename, for crash safety.
|
||||
fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
|
||||
let tmp = path.with_extension("tmp");
|
||||
std::fs::write(&tmp, contents)?;
|
||||
std::fs::rename(&tmp, path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sanitize_filename(name: &str) -> String {
|
||||
name.chars()
|
||||
.map(|c| match c {
|
||||
@@ -54,7 +63,7 @@ impl PresetStore {
|
||||
let path = self.preset_path(&preset.name);
|
||||
let json = serde_json::to_string_pretty(preset)
|
||||
.map_err(|e| PixstripError::Preset(e.to_string()))?;
|
||||
std::fs::write(&path, json).map_err(PixstripError::Io)
|
||||
atomic_write(&path, &json).map_err(PixstripError::Io)
|
||||
}
|
||||
|
||||
pub fn load(&self, name: &str) -> Result<Preset> {
|
||||
@@ -103,7 +112,7 @@ impl PresetStore {
|
||||
pub fn export_to_file(&self, preset: &Preset, path: &Path) -> Result<()> {
|
||||
let json = serde_json::to_string_pretty(preset)
|
||||
.map_err(|e| PixstripError::Preset(e.to_string()))?;
|
||||
std::fs::write(path, json).map_err(PixstripError::Io)
|
||||
atomic_write(path, &json).map_err(PixstripError::Io)
|
||||
}
|
||||
|
||||
pub fn import_from_file(&self, path: &Path) -> Result<Preset> {
|
||||
@@ -146,7 +155,7 @@ impl ConfigStore {
|
||||
}
|
||||
let json = serde_json::to_string_pretty(config)
|
||||
.map_err(|e| PixstripError::Config(e.to_string()))?;
|
||||
std::fs::write(&self.config_path, json).map_err(PixstripError::Io)
|
||||
atomic_write(&self.config_path, &json).map_err(PixstripError::Io)
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<AppConfig> {
|
||||
@@ -215,7 +224,7 @@ impl SessionStore {
|
||||
}
|
||||
let json = serde_json::to_string_pretty(state)
|
||||
.map_err(|e| PixstripError::Config(e.to_string()))?;
|
||||
std::fs::write(&self.session_path, json).map_err(PixstripError::Io)
|
||||
atomic_write(&self.session_path, &json).map_err(PixstripError::Io)
|
||||
}
|
||||
|
||||
pub fn load(&self) -> Result<SessionState> {
|
||||
@@ -267,10 +276,11 @@ impl HistoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, entry: HistoryEntry) -> Result<()> {
|
||||
pub fn add(&self, entry: HistoryEntry, max_entries: usize, max_days: u32) -> Result<()> {
|
||||
let mut entries = self.list()?;
|
||||
entries.push(entry);
|
||||
self.write_all(&entries)
|
||||
self.write_all(&entries)?;
|
||||
self.prune(max_entries, max_days)
|
||||
}
|
||||
|
||||
pub fn prune(&self, max_entries: usize, max_days: u32) -> Result<()> {
|
||||
@@ -285,9 +295,9 @@ impl HistoryStore {
|
||||
.as_secs();
|
||||
let cutoff_secs = now_secs.saturating_sub(max_days as u64 * 86400);
|
||||
|
||||
// Remove entries older than max_days
|
||||
// Remove entries older than max_days (keep entries with unparseable timestamps)
|
||||
entries.retain(|e| {
|
||||
e.timestamp.parse::<u64>().unwrap_or(0) >= cutoff_secs
|
||||
e.timestamp.parse::<u64>().map_or(true, |ts| ts >= cutoff_secs)
|
||||
});
|
||||
|
||||
// Trim to max_entries (keep the most recent)
|
||||
@@ -314,13 +324,13 @@ impl HistoryStore {
|
||||
self.write_all(&Vec::<HistoryEntry>::new())
|
||||
}
|
||||
|
||||
fn write_all(&self, entries: &[HistoryEntry]) -> Result<()> {
|
||||
pub fn write_all(&self, entries: &[HistoryEntry]) -> Result<()> {
|
||||
if let Some(parent) = self.history_path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(PixstripError::Io)?;
|
||||
}
|
||||
let json = serde_json::to_string_pretty(entries)
|
||||
.map_err(|e| PixstripError::Config(e.to_string()))?;
|
||||
std::fs::write(&self.history_path, json).map_err(PixstripError::Io)
|
||||
atomic_write(&self.history_path, &json).map_err(PixstripError::Io)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user