fix(dates): year-gate the filename wall-clock patterns (#160)
match_filename_date only gated the unix-timestamp branch to years 1995..=now+1; the five wall patterns returned any NaiveDateTime. Counters and IDs shaped like YYYYMMDD_HHMMSS (an app's VID_<counter>_<HHMMSS>, Facebook's <8 digits>_<id>_o.jpg) became dates of record in years from 267 to 9985: 67 rows on prod, all via screenshot_compact. Both branches now share plausible_filename_year; a wall miss falls through to the next pattern and then the rest of the waterfall. The filename audit no longer proposes these dates. A data migration clears the five date columns on filename-sourced rows dated outside [1995, 2028) UTC, so backfill_missing_date_taken re-resolves them from EXIF / exiftool / fs time on the next ticks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f1779358b5
commit
36e4af9fa6
4 files changed
+211
-16
No files matched your search
@@ -474,6 +474,100 @@ mod tests {
|
||||
assert!(!unidx.changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_leaves_out_counter_and_id_names_with_implausible_years() {
|
||||
// #160: these matched screenshot_compact as years 2229 / 8580 /
|
||||
// 1053 and the audit proposed them as dates of record.
|
||||
let tmp = tempdir().unwrap();
|
||||
for path in [
|
||||
"VID_22290524_080551_818.mp4",
|
||||
"VID_185800302_235056_842.mp4",
|
||||
"10530519_10152700326093436_5479865808523667420_o.jpg",
|
||||
"Screenshot_2014-06-01-20-44-50.png",
|
||||
] {
|
||||
fs::write(tmp.path().join(path), b"fixture").unwrap();
|
||||
}
|
||||
let mut conn = migrated_conn();
|
||||
let lib = library(1, "photos", tmp.path(), true);
|
||||
let stored = stored_dates_for_library(&mut conn, 1).unwrap();
|
||||
let rows = audit_library(&lib, &[], &stored);
|
||||
let rels: Vec<&str> = rows.iter().map(|r| r.rel_path.as_str()).collect();
|
||||
assert_eq!(rels, vec!["Screenshot_2014-06-01-20-44-50.png"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_clears_implausible_filename_dates_for_re_resolution() {
|
||||
// Migrate up to just before the #160 data fix, seed rows as the
|
||||
// old matcher left them, then apply it.
|
||||
const FIX: &str = "reresolve_implausible_filename_dates";
|
||||
let mut conn = SqliteConnection::establish(":memory:").expect("in-memory sqlite");
|
||||
for m in conn.pending_migrations(DB_MIGRATIONS).expect("pending") {
|
||||
if m.name().to_string().contains(FIX) {
|
||||
break;
|
||||
}
|
||||
conn.run_migration(&*m).expect("migration");
|
||||
}
|
||||
// 2229-05-24 08:05:51 and 1053-05-19 10:15:27 (wall-as-UTC), a
|
||||
// real 2014 filename date, and an fs_time row that isn't ours to
|
||||
// touch even though its date is odd.
|
||||
seed_row(
|
||||
&mut conn,
|
||||
1,
|
||||
"VID_22290524_080551_818.mp4",
|
||||
Some(8_185_651_551),
|
||||
Some("filename"),
|
||||
);
|
||||
seed_row(
|
||||
&mut conn,
|
||||
1,
|
||||
"10530519_1015270_o.jpg",
|
||||
Some(-28_925_732_673),
|
||||
Some("filename"),
|
||||
);
|
||||
seed_row(
|
||||
&mut conn,
|
||||
1,
|
||||
"Screenshot_2014-06-01-20-44-50.png",
|
||||
Some(1_401_655_490),
|
||||
Some("filename"),
|
||||
);
|
||||
seed_row(&mut conn, 1, "old_scan.jpg", Some(100), Some("fs_time"));
|
||||
diesel::update(image_exif::table)
|
||||
.set((
|
||||
image_exif::date_utc.eq(image_exif::date_taken),
|
||||
image_exif::date_taken_tz_source.eq("assumed_server"),
|
||||
))
|
||||
.execute(&mut conn)
|
||||
.unwrap();
|
||||
|
||||
conn.run_pending_migrations(DB_MIGRATIONS)
|
||||
.expect("fix migration");
|
||||
|
||||
type Dates = (
|
||||
String,
|
||||
Option<i64>,
|
||||
Option<String>,
|
||||
Option<i64>,
|
||||
Option<String>,
|
||||
);
|
||||
let rows: Vec<Dates> = image_exif::table
|
||||
.select((
|
||||
image_exif::rel_path,
|
||||
image_exif::date_taken,
|
||||
image_exif::date_taken_source,
|
||||
image_exif::date_utc,
|
||||
image_exif::date_taken_tz_source,
|
||||
))
|
||||
.order(image_exif::rel_path)
|
||||
.load(&mut conn)
|
||||
.unwrap();
|
||||
let cleared = |r: &Dates| r.1.is_none() && r.2.is_none() && r.3.is_none() && r.4.is_none();
|
||||
for r in &rows {
|
||||
let expect_cleared = r.0.starts_with("VID_") || r.0.starts_with("1053");
|
||||
assert_eq!(cleared(r), expect_cleared, "{:?}", r);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_audit_skips_disabled_and_offline_libraries() {
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user