Dates: serve the display wall clock (wall_clock) to clients
Every payload a client renders a capture time from now carries `wall_clock`, a naive ISO-8601 string computed by the existing `date_view::row_display_wall`: the camera's wall clock (true instant + camera offset; the owner's display zone without an offset), with the per-source fallback for unconverted rows. Clients render it verbatim and do no zone math, which is the FileViewer-React #33 / Apollo #37 flip with the rule kept server-side: the drain's permanent `date_utc IS NULL` floor (#120: edited/exported files) would otherwise force every client to carry the per-source legacy conventions forever, and insight prompts already use the same function, so prompt and UI agree by construction. - `/image/metadata` (+ the GPS / date / date-clear mutation responses): `file_wall_clock` — the row's wall, else the filename-class date, else the earliest fs time in the display zone (unindexed files). - `/memories` items and `/photos/gps-summary` photos: `row_wall_clock`. - `get_all_with_gps` returns full rows instead of a 7-tuple that dropped `date_taken_source`, which unconverted rows need. - `date_view::owner_display_zone` resolves prefs -> app default per request. Legacy fields are untouched, so current clients see byte-identical responses (additive; safe to deploy ahead of the client flip, per #120). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e0c6c696f2
commit
e2f33684c8
8 files changed
+257
-93
No files matched your search
+40
-5
@@ -524,11 +524,18 @@ pub async fn get_file_metadata(
|
||||
.map(memories::FilenameDate::legacy_timestamp);
|
||||
|
||||
// Query EXIF data if available
|
||||
if let Ok(mut dao) = exif_dao.lock()
|
||||
&& let Ok(Some(exif)) = dao.get_exif(&path.path)
|
||||
{
|
||||
response.exif = Some(exif.into());
|
||||
}
|
||||
let row = exif_dao
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut dao| dao.get_exif(&path.path).ok().flatten());
|
||||
response.wall_clock = crate::date_view::file_wall_clock(
|
||||
row.as_ref(),
|
||||
&path.path,
|
||||
earliest_fs_time(&response),
|
||||
crate::date_view::owner_display_zone(&app_state.user_ai_prefs_dao),
|
||||
crate::camera_tz::server_zone(),
|
||||
);
|
||||
response.exif = row.map(Into::into);
|
||||
|
||||
span.add_event(
|
||||
"Metadata fetched",
|
||||
@@ -706,8 +713,16 @@ pub async fn set_image_gps(
|
||||
filename_date: None,
|
||||
library_id: None,
|
||||
library_name: None,
|
||||
wall_clock: None,
|
||||
},
|
||||
};
|
||||
response.wall_clock = crate::date_view::file_wall_clock(
|
||||
Some(&row),
|
||||
&normalized_path,
|
||||
earliest_fs_time(&response),
|
||||
crate::date_view::owner_display_zone(&app_state.user_ai_prefs_dao),
|
||||
crate::camera_tz::server_zone(),
|
||||
);
|
||||
response.exif = Some(row.into());
|
||||
response.library_id = Some(resolved_library.id);
|
||||
response.library_name = Some(resolved_library.name.clone());
|
||||
@@ -848,6 +863,7 @@ fn build_metadata_response_for_date_mutation(
|
||||
library: &libraries::Library,
|
||||
rel_path: &str,
|
||||
exif: ImageExif,
|
||||
display_zone: chrono_tz::Tz,
|
||||
) -> MetadataResponse {
|
||||
let full_path = is_valid_full_path(&library.root_path, &rel_path.to_string(), false);
|
||||
let fs_meta = full_path
|
||||
@@ -864,8 +880,16 @@ fn build_metadata_response_for_date_mutation(
|
||||
filename_date: None,
|
||||
library_id: None,
|
||||
library_name: None,
|
||||
wall_clock: None,
|
||||
},
|
||||
};
|
||||
response.wall_clock = crate::date_view::file_wall_clock(
|
||||
Some(&exif),
|
||||
rel_path,
|
||||
earliest_fs_time(&response),
|
||||
display_zone,
|
||||
crate::camera_tz::server_zone(),
|
||||
);
|
||||
response.exif = Some(exif.into());
|
||||
response.library_id = Some(library.id);
|
||||
response.library_name = Some(library.name.clone());
|
||||
@@ -874,6 +898,15 @@ fn build_metadata_response_for_date_mutation(
|
||||
response
|
||||
}
|
||||
|
||||
/// Earlier of the file's created / modified times — the ingest waterfall's
|
||||
/// `fs_time` rule — for the wall-clock fallback of unindexed files.
|
||||
fn earliest_fs_time(response: &MetadataResponse) -> Option<i64> {
|
||||
match (response.created, response.modified) {
|
||||
(Some(c), Some(m)) => Some(c.min(m)),
|
||||
(c, m) => c.or(m),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/image/exif/date")]
|
||||
#[tracing::instrument(name = "set_image_date", skip_all)]
|
||||
pub async fn set_image_date(
|
||||
@@ -924,6 +957,7 @@ pub async fn set_image_date(
|
||||
library,
|
||||
&normalized_path,
|
||||
row,
|
||||
crate::date_view::owner_display_zone(&app_state.user_ai_prefs_dao),
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -972,6 +1006,7 @@ pub async fn clear_image_date(
|
||||
library,
|
||||
&normalized_path,
|
||||
row,
|
||||
crate::date_view::owner_display_zone(&app_state.user_ai_prefs_dao),
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
Reference in new issue
Block a user