diff --git a/src/main.rs b/src/main.rs index 4644a6d..f943810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2317,12 +2317,26 @@ fn backfill_missing_content_hashes( .ok() .and_then(|s| s.parse().ok()) .filter(|n: &usize| *n > 0) - .unwrap_or(500); + .unwrap_or(2000); + + // Count the unhashed backlog up front so we can surface "still needs + // backfill: N" in the log — without it, a face-scan that's stuck at + // 44% looks stalled when really it's chipping through hashes. + let unhashed_total = exif_records + .iter() + .filter(|r| r.content_hash.is_none()) + .count(); let mut backfilled = 0usize; let mut errors = 0usize; for record in &exif_records { - if backfilled + errors >= cap { + // Cap on successes only — earlier this counted errors too, so a + // pocket of chronically-unhashable files at the front of the + // table (vanished mid-scan, permission denied, etc.) burned the + // budget every tick and the rest of the backlog never advanced. + // Errors are still bounded by `unhashed_total` (the loop walks + // each unhashed record at most once per tick). + if backfilled >= cap { break; } if record.content_hash.is_some() { @@ -2362,10 +2376,14 @@ fn backfill_missing_content_hashes( } } } - if backfilled > 0 || errors > 0 { + // Always log when there's an unhashed backlog so an operator + // looking at "scan stuck at 44%" can see backfill is running and + // how much remains. Quiet only when there's nothing to do. + if unhashed_total > 0 || backfilled > 0 || errors > 0 { + let remaining = unhashed_total.saturating_sub(backfilled); info!( - "face_watch: backfilled content_hash for {} file(s) in library '{}' ({} error(s); cap={})", - backfilled, library.name, errors, cap + "face_watch: backfilled {}/{} content_hash for library '{}' ({} error(s); {} still need backfill; cap={})", + backfilled, unhashed_total, library.name, errors, remaining, cap ); } }