Tracing: DAO layer + handler spans on tracing, drop all Context params (#141)
trace_db_call no longer takes an OpenTelemetry Context: it opens a db.<query_type>.<operation> span (same name via otel.name, same db.query_type / db.operation attrs) on the caller's ambient tracing span and runs the DAO body inside it. The cx / ctx / context parameter is gone from every DAO trait method, impl and call site, and from the helpers and background drains that only threaded it. The remaining request-handler spans (files, tags, faces, image, video, favorites, duplicates, memories) move to #[instrument]; the Context::current_with_span / with_remote_span_context plumbing is gone, as is extract_context_from_request (the tracing-actix-web root span covers it). otel::set_span_attributes replaces BoxedSpan::set_attributes. Background-job raw spans (thumbnails, video actors) are left for #142. tests/otel_db_spans.rs pins name, attributes, status and nesting (across an instrumented spawn); otel_traceparent now uses an #[instrument] handler. CLAUDE.md documents the tracing conventions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c508cdabd
commit
b6deca2a52
55 files changed
+1942
-3852
No files matched your search
+137
-297
@@ -9,6 +9,7 @@ use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::time::SystemTime;
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
|
||||
use crate::AppState;
|
||||
use crate::data::{
|
||||
@@ -23,17 +24,17 @@ use crate::thumbnails::create_thumbnails;
|
||||
use crate::utils::earliest_fs_time;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{
|
||||
HttpRequest, HttpResponse,
|
||||
HttpResponse,
|
||||
web::{self, Query},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
||||
use opentelemetry::trace::{Status, Tracer};
|
||||
|
||||
use crate::data::SortType::NameAsc;
|
||||
use crate::error::IntoHttpError;
|
||||
use crate::otel::{extract_context_from_request, global_tracer};
|
||||
use crate::otel::global_tracer;
|
||||
use crate::tags::{FileWithTagCount, TagDao};
|
||||
use crate::video::actors::StreamActor;
|
||||
use path_absolutize::*;
|
||||
@@ -58,7 +59,6 @@ fn apply_sorting_with_exif(
|
||||
file_libraries: Vec<i32>,
|
||||
sort_type: SortType,
|
||||
exif_dao: &mut Box<dyn ExifDao>,
|
||||
span_context: &opentelemetry::Context,
|
||||
libraries: &[crate::libraries::Library],
|
||||
limit: Option<i64>,
|
||||
offset: i64,
|
||||
@@ -73,7 +73,6 @@ fn apply_sorting_with_exif(
|
||||
file_libraries,
|
||||
sort_type,
|
||||
exif_dao,
|
||||
span_context,
|
||||
libraries,
|
||||
limit,
|
||||
offset,
|
||||
@@ -103,7 +102,6 @@ fn in_memory_date_sort(
|
||||
file_libraries: Vec<i32>,
|
||||
sort_type: SortType,
|
||||
exif_dao: &mut Box<dyn ExifDao>,
|
||||
span_context: &opentelemetry::Context,
|
||||
libraries: &[crate::libraries::Library],
|
||||
limit: Option<i64>,
|
||||
offset: i64,
|
||||
@@ -122,7 +120,7 @@ fn in_memory_date_sort(
|
||||
_ => None,
|
||||
};
|
||||
let exif_rows = exif_dao
|
||||
.get_exif_batch(span_context, scope_library, &file_paths)
|
||||
.get_exif_batch(scope_library, &file_paths)
|
||||
.unwrap_or_default();
|
||||
let exif_map: std::collections::HashMap<(String, i32), i64> = exif_rows
|
||||
.into_iter()
|
||||
@@ -193,9 +191,9 @@ fn in_memory_date_sort(
|
||||
(result, result_libs, total_count)
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "list_photos", skip_all)]
|
||||
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
req: Query<FilesRequest>,
|
||||
app_state: Data<AppState>,
|
||||
file_system: Data<FS>,
|
||||
@@ -204,75 +202,76 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
) -> HttpResponse {
|
||||
let search_path = &req.path;
|
||||
|
||||
let tracer = global_tracer();
|
||||
let context = extract_context_from_request(&request);
|
||||
let mut span = tracer.start_with_context("list_photos", &context);
|
||||
span.set_attributes(vec![
|
||||
KeyValue::new("path", search_path.to_string()),
|
||||
KeyValue::new("recursive", req.recursive.unwrap_or(false).to_string()),
|
||||
KeyValue::new(
|
||||
"tag_ids",
|
||||
req.tag_ids.clone().unwrap_or_default().to_string(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"tag_filter_mode",
|
||||
format!("{:?}", req.tag_filter_mode.unwrap_or(FilterMode::Any)),
|
||||
),
|
||||
KeyValue::new(
|
||||
"exclude_tag_ids",
|
||||
req.exclude_tag_ids.clone().unwrap_or_default().to_string(),
|
||||
),
|
||||
KeyValue::new("sort", format!("{:?}", &req.sort.unwrap_or(NameAsc))),
|
||||
// EXIF search parameters
|
||||
KeyValue::new("camera_make", req.camera_make.clone().unwrap_or_default()),
|
||||
KeyValue::new("camera_model", req.camera_model.clone().unwrap_or_default()),
|
||||
KeyValue::new("lens_model", req.lens_model.clone().unwrap_or_default()),
|
||||
KeyValue::new(
|
||||
"gps_lat",
|
||||
req.gps_lat.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"gps_lon",
|
||||
req.gps_lon.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"gps_radius_km",
|
||||
req.gps_radius_km.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"date_from",
|
||||
req.date_from.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"date_to",
|
||||
req.date_to.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"media_type",
|
||||
req.media_type
|
||||
.as_ref()
|
||||
.map(|mt| format!("{:?}", mt))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
// Pagination parameters
|
||||
KeyValue::new("pagination.enabled", req.limit.is_some().to_string()),
|
||||
KeyValue::new(
|
||||
"pagination.limit",
|
||||
req.limit.map(|l| l.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new("pagination.offset", req.offset.unwrap_or(0).to_string()),
|
||||
// Optimization flags
|
||||
KeyValue::new("optimization.batch_tags", "true"),
|
||||
KeyValue::new(
|
||||
"optimization.db_sort",
|
||||
matches!(
|
||||
req.sort,
|
||||
Some(SortType::DateTakenAsc | SortType::DateTakenDesc)
|
||||
)
|
||||
.to_string(),
|
||||
),
|
||||
KeyValue::new("library", req.library.clone().unwrap_or_default()),
|
||||
]);
|
||||
let span = tracing::Span::current();
|
||||
crate::otel::set_span_attributes(
|
||||
&span,
|
||||
vec![
|
||||
KeyValue::new("path", search_path.to_string()),
|
||||
KeyValue::new("recursive", req.recursive.unwrap_or(false).to_string()),
|
||||
KeyValue::new(
|
||||
"tag_ids",
|
||||
req.tag_ids.clone().unwrap_or_default().to_string(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"tag_filter_mode",
|
||||
format!("{:?}", req.tag_filter_mode.unwrap_or(FilterMode::Any)),
|
||||
),
|
||||
KeyValue::new(
|
||||
"exclude_tag_ids",
|
||||
req.exclude_tag_ids.clone().unwrap_or_default().to_string(),
|
||||
),
|
||||
KeyValue::new("sort", format!("{:?}", &req.sort.unwrap_or(NameAsc))),
|
||||
// EXIF search parameters
|
||||
KeyValue::new("camera_make", req.camera_make.clone().unwrap_or_default()),
|
||||
KeyValue::new("camera_model", req.camera_model.clone().unwrap_or_default()),
|
||||
KeyValue::new("lens_model", req.lens_model.clone().unwrap_or_default()),
|
||||
KeyValue::new(
|
||||
"gps_lat",
|
||||
req.gps_lat.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"gps_lon",
|
||||
req.gps_lon.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"gps_radius_km",
|
||||
req.gps_radius_km.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"date_from",
|
||||
req.date_from.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"date_to",
|
||||
req.date_to.map(|v| v.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new(
|
||||
"media_type",
|
||||
req.media_type
|
||||
.as_ref()
|
||||
.map(|mt| format!("{:?}", mt))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
// Pagination parameters
|
||||
KeyValue::new("pagination.enabled", req.limit.is_some().to_string()),
|
||||
KeyValue::new(
|
||||
"pagination.limit",
|
||||
req.limit.map(|l| l.to_string()).unwrap_or_default(),
|
||||
),
|
||||
KeyValue::new("pagination.offset", req.offset.unwrap_or(0).to_string()),
|
||||
// Optimization flags
|
||||
KeyValue::new("optimization.batch_tags", "true"),
|
||||
KeyValue::new(
|
||||
"optimization.db_sort",
|
||||
matches!(
|
||||
req.sort,
|
||||
Some(SortType::DateTakenAsc | SortType::DateTakenDesc)
|
||||
)
|
||||
.to_string(),
|
||||
),
|
||||
KeyValue::new("library", req.library.clone().unwrap_or_default()),
|
||||
],
|
||||
);
|
||||
|
||||
// Resolve the optional library filter. Unknown values return 400. A
|
||||
// `None` result means "union across all libraries" and downstream
|
||||
@@ -286,8 +285,6 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
}
|
||||
};
|
||||
|
||||
let span_context = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
// Check if EXIF filtering is requested
|
||||
let has_exif_filters = req.camera_make.is_some()
|
||||
|| req.camera_model.is_some()
|
||||
@@ -303,9 +300,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
&& !(req.gps_lat.is_some() && req.gps_lon.is_some() && req.gps_radius_km.is_some())
|
||||
{
|
||||
warn!("GPS search requires lat, lon, and radius_km to all be specified");
|
||||
span_context
|
||||
.span()
|
||||
.set_status(Status::error("Invalid GPS parameters"));
|
||||
span.set_status(Status::error("Invalid GPS parameters"));
|
||||
return HttpResponse::BadRequest().body("GPS search requires lat, lon, and radius_km");
|
||||
}
|
||||
|
||||
@@ -326,7 +321,6 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
let exif_results = exif_dao_guard
|
||||
.query_by_exif(
|
||||
&span_context,
|
||||
library.map(|l| l.id),
|
||||
req.camera_make.as_deref(),
|
||||
req.camera_model.as_deref(),
|
||||
@@ -407,12 +401,8 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
.collect::<Vec<i32>>();
|
||||
|
||||
return match filter_mode {
|
||||
FilterMode::Any => {
|
||||
dao.get_files_with_any_tag_ids(tag_ids.clone(), exclude_tag_ids, &span_context)
|
||||
}
|
||||
FilterMode::All => {
|
||||
dao.get_files_with_all_tag_ids(tag_ids.clone(), exclude_tag_ids, &span_context)
|
||||
}
|
||||
FilterMode::Any => dao.get_files_with_any_tag_ids(tag_ids.clone(), exclude_tag_ids),
|
||||
FilterMode::All => dao.get_files_with_all_tag_ids(tag_ids.clone(), exclude_tag_ids),
|
||||
}
|
||||
.context(format!(
|
||||
"Failed to get files with tag_ids: {:?} with filter_mode: {:?}",
|
||||
@@ -467,13 +457,8 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
}
|
||||
let siblings = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
match dao
|
||||
.find_content_hash_anywhere(&span_context, &f.file_name)
|
||||
.unwrap_or(None)
|
||||
{
|
||||
Some(hash) => dao
|
||||
.get_rel_paths_by_hash(&span_context, &hash)
|
||||
.unwrap_or_default(),
|
||||
match dao.find_content_hash_anywhere(&f.file_name).unwrap_or(None) {
|
||||
Some(hash) => dao.get_rel_paths_by_hash(&hash).unwrap_or_default(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
};
|
||||
@@ -511,7 +496,6 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
file_libs,
|
||||
sort_type,
|
||||
&mut exif_dao_guard,
|
||||
&span_context,
|
||||
&app_state.libraries,
|
||||
limit,
|
||||
offset,
|
||||
@@ -543,13 +527,9 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
(None, None, None)
|
||||
};
|
||||
|
||||
span_context
|
||||
.span()
|
||||
.set_attribute(KeyValue::new("file_count", tagged_files.len().to_string()));
|
||||
span_context
|
||||
.span()
|
||||
.set_attribute(KeyValue::new("total_count", total_count.to_string()));
|
||||
span_context.span().set_status(Status::Ok);
|
||||
span.set_attribute("file_count", tagged_files.len().to_string());
|
||||
span.set_attribute("total_count", total_count.to_string());
|
||||
span.set_status(Status::Ok);
|
||||
|
||||
HttpResponse::Ok().json(PhotosResponse {
|
||||
photos: tagged_files,
|
||||
@@ -589,7 +569,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
let include_duplicates = req.include_duplicates.unwrap_or(false);
|
||||
let rows = {
|
||||
let mut dao = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
dao.list_rel_paths_for_libraries(&span_context, &lib_ids, prefix, include_duplicates)
|
||||
dao.list_rel_paths_for_libraries(&lib_ids, prefix, include_duplicates)
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("list_rel_paths_for_libraries failed: {:?}", e);
|
||||
Vec::new()
|
||||
@@ -689,9 +669,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
|
||||
if !any_library_resolved {
|
||||
error!("Bad photos request: {}", req.path);
|
||||
span_context
|
||||
.span()
|
||||
.set_status(Status::error("Invalid path"));
|
||||
span.set_status(Status::error("Invalid path"));
|
||||
return HttpResponse::BadRequest().finish();
|
||||
}
|
||||
|
||||
@@ -714,7 +692,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
let tag_counts = {
|
||||
let mut tag_dao_guard = tag_dao.lock().expect("Unable to get TagDao");
|
||||
tag_dao_guard
|
||||
.get_tag_counts_batch(&span_context, &file_names)
|
||||
.get_tag_counts_batch(&file_names)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
info!("Batch tag count query took {:?}", start_tags.elapsed());
|
||||
@@ -731,7 +709,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
.iter()
|
||||
.filter_map(|file_name| {
|
||||
tag_dao_guard
|
||||
.get_tags_for_path(&span_context, file_name)
|
||||
.get_tags_for_path(file_name)
|
||||
.ok()
|
||||
.map(|tags| (file_name.clone(), tags))
|
||||
})
|
||||
@@ -826,7 +804,6 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
file_libs_sorted_input,
|
||||
sort_type,
|
||||
&mut exif_dao_guard,
|
||||
&span_context,
|
||||
&app_state.libraries,
|
||||
limit,
|
||||
offset,
|
||||
@@ -875,17 +852,10 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||
(None, None, None)
|
||||
};
|
||||
|
||||
span_context.span().set_attribute(KeyValue::new(
|
||||
"file_count",
|
||||
response_files.len().to_string(),
|
||||
));
|
||||
span_context
|
||||
.span()
|
||||
.set_attribute(KeyValue::new("returned_count", returned_count.to_string()));
|
||||
span_context
|
||||
.span()
|
||||
.set_attribute(KeyValue::new("total_count", total_count.to_string()));
|
||||
span_context.span().set_status(Status::Ok);
|
||||
span.set_attribute("file_count", response_files.len().to_string());
|
||||
span.set_attribute("returned_count", returned_count.to_string());
|
||||
span.set_attribute("total_count", total_count.to_string());
|
||||
span.set_status(Status::Ok);
|
||||
|
||||
HttpResponse::Ok().json(PhotosResponse {
|
||||
photos: response_files,
|
||||
@@ -965,11 +935,11 @@ fn sort_with_metadata(
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "list_files", skip_all)]
|
||||
pub fn list_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
let tracer = global_tracer();
|
||||
let mut span = tracer.start("list_files");
|
||||
let span = tracing::Span::current();
|
||||
let dir_name_string = dir.to_str().unwrap_or_default().to_string();
|
||||
span.set_attribute(KeyValue::new("dir", dir_name_string));
|
||||
span.set_attribute("dir", dir_name_string);
|
||||
info!("Listing files in: {:?}", dir);
|
||||
|
||||
let files = read_dir(dir)?
|
||||
@@ -978,7 +948,7 @@ pub fn list_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
.map(|entry| entry.path())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
span.set_attribute(KeyValue::new("file_count", files.len().to_string()));
|
||||
span.set_attribute("file_count", files.len().to_string());
|
||||
span.set_status(Status::Ok);
|
||||
info!("Found {:?} files in directory: {:?}", files.len(), dir);
|
||||
Ok(files)
|
||||
@@ -1084,28 +1054,19 @@ fn is_path_above_base_dir<P: AsRef<Path> + Debug>(
|
||||
|
||||
/// Handler for GPS summary endpoint
|
||||
/// Returns lightweight GPS data for all photos with coordinates
|
||||
#[tracing::instrument(name = "get_gps_summary", skip_all)]
|
||||
pub async fn get_gps_summary(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
req: Query<FilesRequest>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
app_state: Data<AppState>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
use crate::data::{GpsPhotoSummary, GpsPhotosResponse};
|
||||
|
||||
let parent_cx = extract_context_from_request(&request);
|
||||
let tracer = global_tracer();
|
||||
let mut span = tracer
|
||||
.span_builder("get_gps_summary")
|
||||
.start_with_context(&tracer, &parent_cx);
|
||||
let span = tracing::Span::current();
|
||||
|
||||
span.set_attribute(KeyValue::new("path", req.path.clone()));
|
||||
span.set_attribute(KeyValue::new(
|
||||
"recursive",
|
||||
req.recursive.unwrap_or(false).to_string(),
|
||||
));
|
||||
|
||||
let cx = opentelemetry::Context::current_with_span(span);
|
||||
span.set_attribute("path", req.path.clone());
|
||||
span.set_attribute("recursive", req.recursive.unwrap_or(false).to_string());
|
||||
|
||||
// The database stores relative paths, so we use the path as-is.
|
||||
// Normalize empty path or "/" to return all GPS photos. Validation
|
||||
@@ -1122,7 +1083,7 @@ pub async fn get_gps_summary(
|
||||
});
|
||||
if !validated {
|
||||
warn!("Invalid path for GPS summary: {}", req.path);
|
||||
cx.span().set_status(Status::error("Invalid path"));
|
||||
span.set_status(Status::error("Invalid path"));
|
||||
return Ok(HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"error": "Invalid path"
|
||||
})));
|
||||
@@ -1138,7 +1099,7 @@ pub async fn get_gps_summary(
|
||||
|
||||
// Query database for all photos with GPS
|
||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
match exif_dao_guard.get_all_with_gps(&cx, &requested_path, recursive) {
|
||||
match exif_dao_guard.get_all_with_gps(&requested_path, recursive) {
|
||||
Ok(gps_data) => {
|
||||
let mut photos: Vec<GpsPhotoSummary> = gps_data
|
||||
.into_iter()
|
||||
@@ -1194,16 +1155,14 @@ pub async fn get_gps_summary(
|
||||
}
|
||||
|
||||
let total = photos.len();
|
||||
cx.span()
|
||||
.set_attribute(KeyValue::new("result_count", total as i64));
|
||||
cx.span().set_status(Status::Ok);
|
||||
span.set_attribute("result_count", total as i64);
|
||||
span.set_status(Status::Ok);
|
||||
|
||||
Ok(HttpResponse::Ok().json(GpsPhotosResponse { photos, total }))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error querying GPS data: {:?}", e);
|
||||
cx.span()
|
||||
.set_status(Status::error(format!("Database error: {:?}", e)));
|
||||
span.set_status(Status::error(format!("Database error: {:?}", e)));
|
||||
Ok(HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"error": "Failed to query GPS data"
|
||||
})))
|
||||
@@ -1224,31 +1183,24 @@ pub async fn get_gps_summary(
|
||||
/// supplied). Filename-extracted dates are not synthesized here; if a
|
||||
/// caller needs that fallback, fetch the row separately via
|
||||
/// `/image/metadata` (rare path).
|
||||
#[tracing::instrument(name = "list_exif_summary", skip_all)]
|
||||
pub async fn list_exif_summary(
|
||||
_: Claims,
|
||||
request: HttpRequest,
|
||||
req: Query<ExifBatchRequest>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
app_state: Data<AppState>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
let parent_cx = extract_context_from_request(&request);
|
||||
let tracer = global_tracer();
|
||||
let mut span = tracer
|
||||
.span_builder("list_exif_summary")
|
||||
.start_with_context(&tracer, &parent_cx);
|
||||
let span = tracing::Span::current();
|
||||
|
||||
span.set_attribute(KeyValue::new(
|
||||
span.set_attribute(
|
||||
"date_from",
|
||||
req.date_from.map(|v| v.to_string()).unwrap_or_default(),
|
||||
));
|
||||
span.set_attribute(KeyValue::new(
|
||||
);
|
||||
span.set_attribute(
|
||||
"date_to",
|
||||
req.date_to.map(|v| v.to_string()).unwrap_or_default(),
|
||||
));
|
||||
span.set_attribute(KeyValue::new(
|
||||
"library",
|
||||
req.library.clone().unwrap_or_default(),
|
||||
));
|
||||
);
|
||||
span.set_attribute("library", req.library.clone().unwrap_or_default());
|
||||
|
||||
// Resolve the library filter up front so a bad id/name 400s before we
|
||||
// ever take the DAO mutex. None == union across all libraries.
|
||||
@@ -1260,7 +1212,6 @@ pub async fn list_exif_summary(
|
||||
return Ok(HttpResponse::BadRequest().json(serde_json::json!({ "error": msg })));
|
||||
}
|
||||
};
|
||||
let cx = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
// Pre-build an id → name map so we don't linear-scan libraries per row.
|
||||
let library_names: std::collections::HashMap<i32, String> = app_state
|
||||
@@ -1271,7 +1222,6 @@ pub async fn list_exif_summary(
|
||||
|
||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
match exif_dao_guard.query_by_exif(
|
||||
&cx,
|
||||
library_filter,
|
||||
None,
|
||||
None,
|
||||
@@ -1303,16 +1253,14 @@ pub async fn list_exif_summary(
|
||||
.collect();
|
||||
|
||||
let total = photos.len();
|
||||
cx.span()
|
||||
.set_attribute(KeyValue::new("result_count", total as i64));
|
||||
cx.span().set_status(Status::Ok);
|
||||
span.set_attribute("result_count", total as i64);
|
||||
span.set_status(Status::Ok);
|
||||
|
||||
Ok(HttpResponse::Ok().json(ExifBatchResponse { photos, total }))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error querying EXIF batch: {:?}", e);
|
||||
cx.span()
|
||||
.set_status(Status::error(format!("Database error: {:?}", e)));
|
||||
span.set_status(Status::error(format!("Database error: {:?}", e)));
|
||||
Ok(HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"error": "Failed to query EXIF data"
|
||||
})))
|
||||
@@ -1543,7 +1491,6 @@ mod tests {
|
||||
impl ExifDao for MockExifDao {
|
||||
fn store_exif(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
data: crate::database::models::InsertImageExif,
|
||||
) -> Result<crate::database::models::ImageExif, DbError> {
|
||||
// Return a dummy ImageExif for tests
|
||||
@@ -1589,7 +1536,6 @@ mod tests {
|
||||
|
||||
fn get_exif(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_: &str,
|
||||
) -> Result<Option<crate::database::models::ImageExif>, DbError> {
|
||||
Ok(None)
|
||||
@@ -1597,7 +1543,6 @@ mod tests {
|
||||
|
||||
fn update_exif(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
data: crate::database::models::InsertImageExif,
|
||||
) -> Result<crate::database::models::ImageExif, DbError> {
|
||||
// Return a dummy ImageExif for tests
|
||||
@@ -1641,17 +1586,12 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_exif(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_: &str,
|
||||
) -> Result<(), DbError> {
|
||||
fn delete_exif(&mut self, _: &str) -> Result<(), DbError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_all_with_date_taken(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
) -> Result<Vec<(String, i64)>, DbError> {
|
||||
Ok(Vec::new())
|
||||
@@ -1659,7 +1599,6 @@ mod tests {
|
||||
|
||||
fn get_exif_batch(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
_: &[String],
|
||||
) -> Result<Vec<crate::database::models::ImageExif>, DbError> {
|
||||
@@ -1668,7 +1607,6 @@ mod tests {
|
||||
|
||||
fn query_by_exif(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
_: Option<&str>,
|
||||
_: Option<&str>,
|
||||
@@ -1680,32 +1618,20 @@ mod tests {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn get_camera_makes(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
) -> Result<Vec<(String, i64)>, DbError> {
|
||||
fn get_camera_makes(&mut self) -> Result<Vec<(String, i64)>, DbError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn update_file_path(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_old_path: &str,
|
||||
_new_path: &str,
|
||||
) -> Result<(), DbError> {
|
||||
fn update_file_path(&mut self, _old_path: &str, _new_path: &str) -> Result<(), DbError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_all_file_paths(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
fn get_all_file_paths(&mut self) -> Result<Vec<String>, DbError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn get_all_with_gps(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_base_path: &str,
|
||||
_recursive: bool,
|
||||
) -> Result<
|
||||
@@ -1723,17 +1649,12 @@ mod tests {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_rows_missing_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_limit: i64,
|
||||
) -> Result<Vec<(i32, String)>, DbError> {
|
||||
fn get_rows_missing_hash(&mut self, _limit: i64) -> Result<Vec<(i32, String)>, DbError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn backfill_content_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
_hash: &str,
|
||||
@@ -1742,16 +1663,12 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_distinct_content_hashes(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
fn list_distinct_content_hashes(&mut self) -> Result<Vec<String>, DbError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn list_paths_and_hashes_for_library(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
) -> Result<Vec<(String, Option<String>)>, DbError> {
|
||||
Ok(Vec::new())
|
||||
@@ -1759,7 +1676,6 @@ mod tests {
|
||||
|
||||
fn get_rows_needing_date_backfill(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_limit: i64,
|
||||
) -> Result<Vec<(i32, String)>, DbError> {
|
||||
@@ -1768,7 +1684,6 @@ mod tests {
|
||||
|
||||
fn backfill_date_taken(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
_date_taken: i64,
|
||||
@@ -1782,7 +1697,6 @@ mod tests {
|
||||
|
||||
fn get_rows_needing_utc_backfill(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_after_id: i32,
|
||||
_limit: i64,
|
||||
@@ -1792,7 +1706,6 @@ mod tests {
|
||||
|
||||
fn backfill_date_utc(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_id: i32,
|
||||
_date_utc: i64,
|
||||
_camera_offset_minutes: Option<i32>,
|
||||
@@ -1801,17 +1714,12 @@ mod tests {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn count_rows_needing_utc_backfill(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
) -> Result<i64, DbError> {
|
||||
fn count_rows_needing_utc_backfill(&mut self, _library_id: i32) -> Result<i64, DbError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn set_manual_date_taken(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
library_id: i32,
|
||||
rel_path: &str,
|
||||
date_taken: i64,
|
||||
@@ -1830,7 +1738,6 @@ mod tests {
|
||||
|
||||
fn clear_manual_date_taken(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
library_id: i32,
|
||||
rel_path: &str,
|
||||
) -> Result<crate::database::models::ImageExif, DbError> {
|
||||
@@ -1839,7 +1746,6 @@ mod tests {
|
||||
|
||||
fn get_memories_in_window(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_conv_lo: i64,
|
||||
_conv_hi: i64,
|
||||
@@ -1851,7 +1757,6 @@ mod tests {
|
||||
|
||||
fn find_by_content_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_hash: &str,
|
||||
) -> Result<Option<crate::database::models::ImageExif>, DbError> {
|
||||
Ok(None)
|
||||
@@ -1859,40 +1764,29 @@ mod tests {
|
||||
|
||||
fn get_rel_paths_sharing_content(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
rel_path: &str,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
Ok(vec![rel_path.to_string()])
|
||||
}
|
||||
|
||||
fn get_rel_paths_for_library(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
fn get_rel_paths_for_library(&mut self, _library_id: i32) -> Result<Vec<String>, DbError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn find_content_hash_anywhere(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_rel_path: &str,
|
||||
) -> Result<Option<String>, DbError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_rel_paths_by_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_hash: &str,
|
||||
) -> Result<Vec<String>, DbError> {
|
||||
fn get_rel_paths_by_hash(&mut self, _hash: &str) -> Result<Vec<String>, DbError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_rel_paths_for_hashes(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_hashes: &[String],
|
||||
) -> Result<std::collections::HashMap<String, Vec<String>>, DbError> {
|
||||
Ok(std::collections::HashMap::new())
|
||||
@@ -1900,7 +1794,6 @@ mod tests {
|
||||
|
||||
fn list_rel_paths_for_libraries(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_ids: &[i32],
|
||||
_path_prefix: Option<&str>,
|
||||
_include_duplicates: bool,
|
||||
@@ -1910,24 +1803,18 @@ mod tests {
|
||||
|
||||
fn delete_exif_by_library(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
) -> Result<(), DbError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn count_for_library(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
) -> Result<i64, DbError> {
|
||||
fn count_for_library(&mut self, _library_id: i32) -> Result<i64, DbError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn list_rel_paths_for_library_page(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_limit: i64,
|
||||
_offset: i64,
|
||||
@@ -1937,7 +1824,6 @@ mod tests {
|
||||
|
||||
fn get_rows_missing_perceptual_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_limit: i64,
|
||||
) -> Result<Vec<(i32, String)>, DbError> {
|
||||
Ok(Vec::new())
|
||||
@@ -1945,7 +1831,6 @@ mod tests {
|
||||
|
||||
fn backfill_perceptual_hash(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
_phash_64: Option<i64>,
|
||||
@@ -1956,7 +1841,6 @@ mod tests {
|
||||
|
||||
fn list_duplicates_exact(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
_include_resolved: bool,
|
||||
) -> Result<Vec<crate::database::DuplicateRow>, DbError> {
|
||||
@@ -1965,7 +1849,6 @@ mod tests {
|
||||
|
||||
fn list_perceptual_candidates(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
_include_resolved: bool,
|
||||
) -> Result<Vec<crate::database::DuplicateRow>, DbError> {
|
||||
@@ -1974,7 +1857,6 @@ mod tests {
|
||||
|
||||
fn list_image_paths(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: Option<i32>,
|
||||
_include_resolved: bool,
|
||||
) -> Result<Vec<(i32, String)>, DbError> {
|
||||
@@ -1983,7 +1865,6 @@ mod tests {
|
||||
|
||||
fn lookup_duplicate_row(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
) -> Result<Option<crate::database::DuplicateRow>, DbError> {
|
||||
@@ -1992,7 +1873,6 @@ mod tests {
|
||||
|
||||
fn set_duplicate_of(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
_survivor_hash: &str,
|
||||
@@ -2001,18 +1881,12 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear_duplicate_of(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
) -> Result<(), DbError> {
|
||||
fn clear_duplicate_of(&mut self, _library_id: i32, _rel_path: &str) -> Result<(), DbError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn union_perceptual_tags(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_survivor_hash: &str,
|
||||
_demoted_hash: &str,
|
||||
_survivor_rel_path: &str,
|
||||
@@ -2022,7 +1896,6 @@ mod tests {
|
||||
|
||||
fn list_clip_unencoded_candidates(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_limit: i64,
|
||||
) -> Result<Vec<(String, String)>, DbError> {
|
||||
@@ -2031,7 +1904,6 @@ mod tests {
|
||||
|
||||
fn backfill_clip_embedding(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_id: i32,
|
||||
_rel_path: &str,
|
||||
_embedding: &[u8],
|
||||
@@ -2042,7 +1914,6 @@ mod tests {
|
||||
|
||||
fn list_clip_index(
|
||||
&mut self,
|
||||
_context: &opentelemetry::Context,
|
||||
_library_ids: &[i32],
|
||||
_model_version: Option<&str>,
|
||||
) -> Result<Vec<(String, Vec<u8>)>, DbError> {
|
||||
@@ -2062,7 +1933,6 @@ mod tests {
|
||||
|
||||
use crate::database::test::in_memory_db_connection;
|
||||
use crate::tags::SqliteTagDao;
|
||||
use actix_web::test::TestRequest;
|
||||
use actix_web::web::Data;
|
||||
use std::fs;
|
||||
|
||||
@@ -2097,7 +1967,6 @@ mod tests {
|
||||
|
||||
let response: HttpResponse = list_photos(
|
||||
claims,
|
||||
TestRequest::default().to_http_request(),
|
||||
request,
|
||||
Data::new(test_state),
|
||||
Data::new(RealFileSystem::new(test_base.to_str().unwrap().to_string())),
|
||||
@@ -2141,7 +2010,6 @@ mod tests {
|
||||
let temp_dir = env::temp_dir();
|
||||
let response = list_photos(
|
||||
claims,
|
||||
TestRequest::default().to_http_request(),
|
||||
request,
|
||||
Data::new(AppState::test_state()),
|
||||
Data::new(RealFileSystem::new(temp_dir.to_str().unwrap().to_string())),
|
||||
@@ -2168,26 +2036,15 @@ mod tests {
|
||||
let mut tag_dao =
|
||||
SqliteTagDao::new(std::sync::Arc::new(Mutex::new(in_memory_db_connection())));
|
||||
|
||||
let tag1 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag1")
|
||||
.unwrap();
|
||||
let _tag2 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag2")
|
||||
.unwrap();
|
||||
let tag3 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag3")
|
||||
.unwrap();
|
||||
let tag1 = tag_dao.create_tag("tag1").unwrap();
|
||||
let _tag2 = tag_dao.create_tag("tag2").unwrap();
|
||||
let tag3 = tag_dao.create_tag("tag3").unwrap();
|
||||
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao.tag_file("test.jpg", tag1.id).unwrap();
|
||||
let _ = &tag_dao.tag_file("test.jpg", tag3.id).unwrap();
|
||||
|
||||
let response: HttpResponse = list_photos(
|
||||
claims,
|
||||
TestRequest::default().to_http_request(),
|
||||
request,
|
||||
Data::new(AppState::test_state()),
|
||||
Data::new(FakeFileSystem::new(HashMap::new())),
|
||||
@@ -2215,31 +2072,15 @@ mod tests {
|
||||
let mut tag_dao =
|
||||
SqliteTagDao::new(std::sync::Arc::new(Mutex::new(in_memory_db_connection())));
|
||||
|
||||
let tag1 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag1")
|
||||
.unwrap();
|
||||
let _tag2 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag2")
|
||||
.unwrap();
|
||||
let tag3 = tag_dao
|
||||
.create_tag(&opentelemetry::Context::current(), "tag3")
|
||||
.unwrap();
|
||||
let tag1 = tag_dao.create_tag("tag1").unwrap();
|
||||
let _tag2 = tag_dao.create_tag("tag2").unwrap();
|
||||
let tag3 = tag_dao.create_tag("tag3").unwrap();
|
||||
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag1.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao
|
||||
.tag_file(&opentelemetry::Context::current(), "test.jpg", tag3.id)
|
||||
.unwrap();
|
||||
let _ = &tag_dao.tag_file("test.jpg", tag1.id).unwrap();
|
||||
let _ = &tag_dao.tag_file("test.jpg", tag3.id).unwrap();
|
||||
|
||||
// Should get filtered since it doesn't have tag3
|
||||
tag_dao
|
||||
.tag_file(
|
||||
&opentelemetry::Context::current(),
|
||||
"some-other.jpg",
|
||||
tag1.id,
|
||||
)
|
||||
.unwrap();
|
||||
tag_dao.tag_file("some-other.jpg", tag1.id).unwrap();
|
||||
|
||||
let request: Query<FilesRequest> = Query::from_query(&format!(
|
||||
"path=&tag_ids={},{}&tag_filter_mode=All&recursive=true",
|
||||
@@ -2249,7 +2090,6 @@ mod tests {
|
||||
|
||||
let response: HttpResponse = list_photos(
|
||||
claims,
|
||||
TestRequest::default().to_http_request(),
|
||||
request,
|
||||
Data::new(AppState::test_state()),
|
||||
Data::new(FakeFileSystem::new(HashMap::new())),
|
||||
|
||||
Reference in new issue
Block a user