From 87be53279b60aa3357825ec1c0bde2dd32756628 Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Mon, 12 Oct 2026 01:10:31 +0700 Subject: [PATCH] fix(preview): account for replaced thumbnail cache entries --- crates/preview/src/disk.rs | 16 ++++++++-------- crates/preview/src/tests.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/crates/preview/src/disk.rs b/crates/preview/src/disk.rs index 10a7166c..26d180ec 100644 --- a/crates/preview/src/disk.rs +++ b/crates/preview/src/disk.rs @@ -94,20 +94,20 @@ impl DiskCache { if std::fs::create_dir_all(parent).is_err() { return; } - // know the current total before adding (the first scan must not count this file twice) - let _ = self.size(); + // Serialize the file replacement and its size accounting. A repeated render of + // the same key replaces its JPEG; it must not count as another cache entry. + let mut total = self.total.lock().unwrap_or_else(|e| e.into_inner()); + let used = total.get_or_insert_with(|| self.scan().iter().map(|f| f.1).sum()); + let old_len = std::fs::metadata(&p).map_or(0, |m| m.len()); let tmp = p.with_extension(format!("tmp{:?}", std::thread::current().id()).replace(['(', ')'], "")); if std::fs::write(&tmp, &bytes).is_err() || std::fs::rename(&tmp, &p).is_err() { let _ = std::fs::remove_file(&tmp); return; } self.writes.fetch_add(1, Ordering::Relaxed); - let over = { - let mut t = self.total.lock().unwrap_or_else(|e| e.into_inner()); - let total = t.get_or_insert(0); - *total += bytes.len() as u64; - *total > self.budget() - }; + *used = used.saturating_sub(old_len).saturating_add(bytes.len() as u64); + let over = *used > self.budget(); + drop(total); if over { self.prune(); } diff --git a/crates/preview/src/tests.rs b/crates/preview/src/tests.rs index d733ad82..7c560782 100644 --- a/crates/preview/src/tests.rs +++ b/crates/preview/src/tests.rs @@ -123,6 +123,34 @@ fn disk_cache_roundtrip_and_prune() { let _ = std::fs::remove_dir_all(&dir); } +#[test] +fn disk_cache_overwrite_accounts_for_replaced_jpeg_only_once() { + let dir = temp_dir("overwrite"); + let cache = DiskCache::new(&dir, 1 << 20); + let (a, b) = (hash_bytes(b"overwrite-a"), hash_bytes(b"overwrite-b")); + let first = gradient(80, 60, 4); + cache.put(a, &first); + cache.put(b, &gradient(80, 60, 7)); + let initial = cache.size(); + + // Re-rendering the same key used to double-count bytes although the + // replacement occupied exactly the same pathname on disk. + cache.put(a, &first); + assert_eq!(cache.size(), initial); + assert!(cache.get(b).is_some()); + + // A replacement with a different encoded size must also update the total. + cache.put(a, &gradient(120, 90, 9)); + let path = |key: Hash128| { + let hex = key.to_string(); + dir.join(&hex[..2]).join(format!("{hex}.jpg")) + }; + let actual = std::fs::metadata(path(a)).unwrap().len() + std::fs::metadata(path(b)).unwrap().len(); + assert_eq!(cache.size(), actual); + cache.clear(); + let _ = std::fs::remove_dir_all(&dir); +} + #[test] fn preview_cache_memory_then_disk() { let dir = temp_dir("pc");