feat(scanner): archive-at-two-scans lifecycle for files missing from disk

Replace the silent hard-delete orphan cleanup (which logged only through
ScannerLogger file logs and whose failure paths left rows undetected)
with an archive lifecycle that preserves reading history:

- A file missing in one scan is marked (missing_scan_count = 1); missing
  in a second consecutive scan archives it (archived_at, hidden from
  browsing, progress/notes/highlights survive). Every branch logs to
  stdout with an [ARCHIVE] prefix so skips are always visible.
- When a file reappears - same path, or identical content at a new path
  via the SHA-256 dedup match - the archived state clears automatically
  and the item returns with its history intact.
- Archived rows older than ARCHIVE_RETENTION_DAYS are hard-purged at
  scan time (cascading deletes); 0 disables auto-purge for manual-only
  management. Retention is read from the environment in NewMediaScanner.
This commit is contained in:
John O'Keefe
2026-09-12 17:50:15 -04:00
parent e6aceae0da
commit 14445a7c3f
2 changed files with 105 additions and 47 deletions
+11
View File
@@ -84,3 +84,14 @@ func getEnvInt(key string, defaultValue int) int {
}
return defaultValue
}
// ArchiveRetentionDays returns how many days a media item stays archived
// (file missing from disk for two consecutive scans) before library scans
// purge it for good. Reading progress, notes, and highlights survive the
// archive window and are restored if the file returns; the purge deletes
// them along with the row.
// Configure via ARCHIVE_RETENTION_DAYS (default 90); 0 keeps archived items
// until an admin purges them manually from the library admin page.
func ArchiveRetentionDays() int {
return getEnvInt("ARCHIVE_RETENTION_DAYS", 90)
}