feat(dates): backfill drain converts mixed-convention rows to true instants (#118)
Per-tick drain (backfill_utc_dates) at the existing date-drain seam, inside the per-library online gate. Selects date_utc IS NULL AND date_taken IS NOT NULL (new partial index idx_image_exif_utc_backfill), re-runs resolve_dates_batch with empty priors (one exiftool subprocess per batch, same call shape as the #117 audit), and writes only the new-contract triple; the legacy pair stays frozen. Capped by DATE_BACKFILL_MAX_PER_TICK; missing-file rows are paged past without spending the cap. Legacy manual overrides convert via manual_date_resolution rather than being re-resolved from the file. Day-boundary invalidation deletes the day-span precomputed reels covering a re-dated row's old/new display-zone month-day so the nightly pre-gen regenerates them. Progress: imageserver_date_backfill_remaining gauge + date_backfill_remaining on GET /libraries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab16bc7da6
commit
98db2a48db
11 files changed
+1185
-4
No files matched your search
+44
-1
@@ -26,7 +26,10 @@ use log::{debug, error, info, warn};
|
||||
use crate::backfill;
|
||||
use crate::content_hash;
|
||||
use crate::database::models::InsertImageExif;
|
||||
use crate::database::{ExifDao, PreviewDao, SqliteExifDao, SqlitePreviewDao};
|
||||
use crate::database::{
|
||||
ExifDao, PrecomputedReelDao, PreviewDao, SqliteExifDao, SqlitePrecomputedReelDao,
|
||||
SqlitePreviewDao, SqliteUserAiPrefsDao, UserAiPrefsDao,
|
||||
};
|
||||
use crate::date_resolver;
|
||||
use crate::exif;
|
||||
use crate::face_watch;
|
||||
@@ -336,6 +339,17 @@ pub fn watch_files(
|
||||
Box::new(SqliteTagDao::default()) as Box<dyn tags::TagDao>
|
||||
));
|
||||
|
||||
// True-instant date drain (#118): day-reel invalidation + the
|
||||
// display zone the day comparison runs in.
|
||||
let precomputed_reel_dao = Arc::new(Mutex::new(
|
||||
Box::new(SqlitePrecomputedReelDao::new()) as Box<dyn PrecomputedReelDao>
|
||||
));
|
||||
let user_ai_prefs_dao: Mutex<Box<dyn UserAiPrefsDao>> =
|
||||
Mutex::new(Box::new(SqliteUserAiPrefsDao::new()));
|
||||
// Last-known per-library remaining count behind the
|
||||
// `imageserver_date_backfill_remaining` gauge.
|
||||
let mut utc_backfill_remaining: HashMap<i32, i64> = HashMap::new();
|
||||
|
||||
let mut last_quick_scan = SystemTime::now();
|
||||
// Initialize to UNIX_EPOCH so the *first* tick is treated as a
|
||||
// full scan. That replaces the legacy startup ScanDirectoryMessage
|
||||
@@ -494,6 +508,35 @@ pub fn watch_files(
|
||||
{
|
||||
let context = opentelemetry::Context::new();
|
||||
backfill::backfill_missing_date_taken(&context, lib, &exif_dao);
|
||||
|
||||
// True-instant drain (ADR-0001, #118): convert
|
||||
// legacy mixed-convention rows, invalidating day
|
||||
// reels whose day moved. Inside the online gate, so a
|
||||
// stale library is skipped whole and resumes next
|
||||
// tick. The display zone is re-read per tick so a
|
||||
// prefs change applies without restart.
|
||||
let display_tz = {
|
||||
let mut prefs = user_ai_prefs_dao.lock().expect("prefs dao poisoned");
|
||||
match prefs.get_prefs(&context) {
|
||||
Ok(Some(p)) => {
|
||||
crate::camera_tz::display_zone_for(p.display_tz.as_deref())
|
||||
}
|
||||
_ => crate::camera_tz::display_zone_for(None),
|
||||
}
|
||||
};
|
||||
backfill::backfill_utc_dates(
|
||||
&context,
|
||||
lib,
|
||||
&exif_dao,
|
||||
&precomputed_reel_dao,
|
||||
&display_tz,
|
||||
);
|
||||
backfill::refresh_utc_backfill_progress(
|
||||
&context,
|
||||
lib,
|
||||
&exif_dao,
|
||||
&mut utc_backfill_remaining,
|
||||
);
|
||||
}
|
||||
|
||||
if is_full_scan {
|
||||
|
||||
Reference in new issue
Block a user