Add tracing to EXIF DAO methods
This commit is contained in:
@@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use opentelemetry;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -63,6 +64,9 @@ fn main() -> anyhow::Result<()> {
|
|||||||
let results: Vec<_> = image_files
|
let results: Vec<_> = image_files
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|path| {
|
.map(|path| {
|
||||||
|
// Create context for this processing iteration
|
||||||
|
let context = opentelemetry::Context::new();
|
||||||
|
|
||||||
let relative_path = match path.strip_prefix(&base) {
|
let relative_path = match path.strip_prefix(&base) {
|
||||||
Ok(p) => p.to_str().unwrap().to_string(),
|
Ok(p) => p.to_str().unwrap().to_string(),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -76,7 +80,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Check if EXIF data already exists
|
// Check if EXIF data already exists
|
||||||
let existing = if let Ok(mut dao_lock) = dao.lock() {
|
let existing = if let Ok(mut dao_lock) = dao.lock() {
|
||||||
dao_lock.get_exif(&relative_path).ok().flatten()
|
dao_lock.get_exif(&context, &relative_path).ok().flatten()
|
||||||
} else {
|
} else {
|
||||||
eprintln!("✗ {} - Failed to acquire database lock", relative_path);
|
eprintln!("✗ {} - Failed to acquire database lock", relative_path);
|
||||||
return Err(anyhow::anyhow!("Lock error"));
|
return Err(anyhow::anyhow!("Lock error"));
|
||||||
@@ -117,10 +121,12 @@ fn main() -> anyhow::Result<()> {
|
|||||||
if let Ok(mut dao_lock) = dao.lock() {
|
if let Ok(mut dao_lock) = dao.lock() {
|
||||||
let result = if existing.is_some() {
|
let result = if existing.is_some() {
|
||||||
// Update existing record
|
// Update existing record
|
||||||
dao_lock.update_exif(insert_exif).map(|_| "update")
|
dao_lock
|
||||||
|
.update_exif(&context, insert_exif)
|
||||||
|
.map(|_| "update")
|
||||||
} else {
|
} else {
|
||||||
// Insert new record
|
// Insert new record
|
||||||
dao_lock.store_exif(insert_exif).map(|_| "insert")
|
dao_lock.store_exif(&context, insert_exif).map(|_| "insert")
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl DatabaseUpdater {
|
|||||||
|
|
||||||
// Update image_exif table
|
// Update image_exif table
|
||||||
if let Ok(mut dao) = self.exif_dao.lock() {
|
if let Ok(mut dao) = self.exif_dao.lock() {
|
||||||
match dao.update_file_path(old_path, new_path) {
|
match dao.update_file_path(&context, old_path, new_path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Updated image_exif: {} -> {}", old_path, new_path);
|
info!("Updated image_exif: {} -> {}", old_path, new_path);
|
||||||
success_count += 1;
|
success_count += 1;
|
||||||
@@ -120,7 +120,7 @@ impl DatabaseUpdater {
|
|||||||
|
|
||||||
// Get from image_exif
|
// Get from image_exif
|
||||||
if let Ok(mut dao) = self.exif_dao.lock() {
|
if let Ok(mut dao) = self.exif_dao.lock() {
|
||||||
match dao.get_all_file_paths() {
|
match dao.get_all_file_paths(&context) {
|
||||||
Ok(paths) => {
|
Ok(paths) => {
|
||||||
info!("Found {} paths in image_exif", paths.len());
|
info!("Found {} paths in image_exif", paths.len());
|
||||||
all_paths.extend(paths);
|
all_paths.extend(paths);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use crate::database::models::{
|
use crate::database::models::{
|
||||||
Favorite, ImageExif, InsertFavorite, InsertImageExif, InsertUser, User,
|
Favorite, ImageExif, InsertFavorite, InsertImageExif, InsertUser, User,
|
||||||
};
|
};
|
||||||
|
use crate::otel::trace_db_call;
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
@@ -221,18 +222,42 @@ impl FavoriteDao for SqliteFavoriteDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait ExifDao: Sync + Send {
|
pub trait ExifDao: Sync + Send {
|
||||||
fn store_exif(&mut self, exif_data: InsertImageExif) -> Result<ImageExif, DbError>;
|
fn store_exif(
|
||||||
fn get_exif(&mut self, file_path: &str) -> Result<Option<ImageExif>, DbError>;
|
&mut self,
|
||||||
fn update_exif(&mut self, exif_data: InsertImageExif) -> Result<ImageExif, DbError>;
|
context: &opentelemetry::Context,
|
||||||
fn delete_exif(&mut self, file_path: &str) -> Result<(), DbError>;
|
exif_data: InsertImageExif,
|
||||||
fn get_all_with_date_taken(&mut self) -> Result<Vec<(String, i64)>, DbError>;
|
) -> Result<ImageExif, DbError>;
|
||||||
|
fn get_exif(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<Option<ImageExif>, DbError>;
|
||||||
|
fn update_exif(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
exif_data: InsertImageExif,
|
||||||
|
) -> Result<ImageExif, DbError>;
|
||||||
|
fn delete_exif(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<(), DbError>;
|
||||||
|
fn get_all_with_date_taken(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<(String, i64)>, DbError>;
|
||||||
|
|
||||||
/// Batch load EXIF data for multiple file paths (single query)
|
/// Batch load EXIF data for multiple file paths (single query)
|
||||||
fn get_exif_batch(&mut self, file_paths: &[String]) -> Result<Vec<ImageExif>, DbError>;
|
fn get_exif_batch(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
file_paths: &[String],
|
||||||
|
) -> Result<Vec<ImageExif>, DbError>;
|
||||||
|
|
||||||
/// Query files by EXIF criteria with optional filters
|
/// Query files by EXIF criteria with optional filters
|
||||||
fn query_by_exif(
|
fn query_by_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
camera_make: Option<&str>,
|
camera_make: Option<&str>,
|
||||||
camera_model: Option<&str>,
|
camera_model: Option<&str>,
|
||||||
lens_model: Option<&str>,
|
lens_model: Option<&str>,
|
||||||
@@ -242,13 +267,24 @@ pub trait ExifDao: Sync + Send {
|
|||||||
) -> Result<Vec<ImageExif>, DbError>;
|
) -> Result<Vec<ImageExif>, DbError>;
|
||||||
|
|
||||||
/// Get distinct camera makes with counts
|
/// Get distinct camera makes with counts
|
||||||
fn get_camera_makes(&mut self) -> Result<Vec<(String, i64)>, DbError>;
|
fn get_camera_makes(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<(String, i64)>, DbError>;
|
||||||
|
|
||||||
/// Update file path in EXIF database
|
/// Update file path in EXIF database
|
||||||
fn update_file_path(&mut self, old_path: &str, new_path: &str) -> Result<(), DbError>;
|
fn update_file_path(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
old_path: &str,
|
||||||
|
new_path: &str,
|
||||||
|
) -> Result<(), DbError>;
|
||||||
|
|
||||||
/// Get all file paths from EXIF database
|
/// Get all file paths from EXIF database
|
||||||
fn get_all_file_paths(&mut self) -> Result<Vec<String>, DbError>;
|
fn get_all_file_paths(
|
||||||
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<String>, DbError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqliteExifDao {
|
pub struct SqliteExifDao {
|
||||||
@@ -270,113 +306,151 @@ impl SqliteExifDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExifDao for SqliteExifDao {
|
impl ExifDao for SqliteExifDao {
|
||||||
fn store_exif(&mut self, exif_data: InsertImageExif) -> Result<ImageExif, DbError> {
|
fn store_exif(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
exif_data: InsertImageExif,
|
||||||
|
) -> Result<ImageExif, DbError> {
|
||||||
|
trace_db_call(context, "insert", "store_exif", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
diesel::insert_into(image_exif)
|
diesel::insert_into(image_exif)
|
||||||
.values(&exif_data)
|
.values(&exif_data)
|
||||||
.execute(connection.deref_mut())
|
.execute(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::InsertError))?;
|
.map_err(|_| anyhow::anyhow!("Insert error"))?;
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.filter(file_path.eq(&exif_data.file_path))
|
.filter(file_path.eq(&exif_data.file_path))
|
||||||
.first::<ImageExif>(connection.deref_mut())
|
.first::<ImageExif>(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::InsertError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_exif(&mut self, path: &str) -> Result<Option<ImageExif>, DbError> {
|
fn get_exif(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
path: &str,
|
||||||
|
) -> Result<Option<ImageExif>, DbError> {
|
||||||
|
trace_db_call(context, "query", "get_exif", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
match image_exif
|
match image_exif
|
||||||
.filter(file_path.eq(path))
|
.filter(file_path.eq(path))
|
||||||
.first::<ImageExif>(connection.deref_mut())
|
.first::<ImageExif>(connection.deref_mut())
|
||||||
{
|
{
|
||||||
Ok(exif) => Ok(Some(exif)),
|
Ok(exif) => Ok(Some(exif)),
|
||||||
Err(diesel::result::Error::NotFound) => Ok(None),
|
Err(diesel::result::Error::NotFound) => Ok(None),
|
||||||
Err(_) => Err(DbError::new(DbErrorKind::QueryError)),
|
Err(_) => Err(anyhow::anyhow!("Query error")),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_exif(&mut self, exif_data: InsertImageExif) -> Result<ImageExif, DbError> {
|
fn update_exif(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
exif_data: InsertImageExif,
|
||||||
|
) -> Result<ImageExif, DbError> {
|
||||||
|
trace_db_call(context, "update", "update_exif", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
diesel::update(image_exif.filter(file_path.eq(&exif_data.file_path)))
|
diesel::update(image_exif.filter(file_path.eq(&exif_data.file_path)))
|
||||||
.set((
|
.set((
|
||||||
camera_make.eq(&exif_data.camera_make),
|
camera_make.eq(&exif_data.camera_make),
|
||||||
camera_model.eq(&exif_data.camera_model),
|
camera_model.eq(&exif_data.camera_model),
|
||||||
lens_model.eq(&exif_data.lens_model),
|
lens_model.eq(&exif_data.lens_model),
|
||||||
width.eq(&exif_data.width),
|
width.eq(&exif_data.width),
|
||||||
height.eq(&exif_data.height),
|
height.eq(&exif_data.height),
|
||||||
orientation.eq(&exif_data.orientation),
|
orientation.eq(&exif_data.orientation),
|
||||||
gps_latitude.eq(&exif_data.gps_latitude),
|
gps_latitude.eq(&exif_data.gps_latitude),
|
||||||
gps_longitude.eq(&exif_data.gps_longitude),
|
gps_longitude.eq(&exif_data.gps_longitude),
|
||||||
gps_altitude.eq(&exif_data.gps_altitude),
|
gps_altitude.eq(&exif_data.gps_altitude),
|
||||||
focal_length.eq(&exif_data.focal_length),
|
focal_length.eq(&exif_data.focal_length),
|
||||||
aperture.eq(&exif_data.aperture),
|
aperture.eq(&exif_data.aperture),
|
||||||
shutter_speed.eq(&exif_data.shutter_speed),
|
shutter_speed.eq(&exif_data.shutter_speed),
|
||||||
iso.eq(&exif_data.iso),
|
iso.eq(&exif_data.iso),
|
||||||
date_taken.eq(&exif_data.date_taken),
|
date_taken.eq(&exif_data.date_taken),
|
||||||
last_modified.eq(&exif_data.last_modified),
|
last_modified.eq(&exif_data.last_modified),
|
||||||
))
|
))
|
||||||
.execute(connection.deref_mut())
|
.execute(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::InsertError))?;
|
.map_err(|_| anyhow::anyhow!("Update error"))?;
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.filter(file_path.eq(&exif_data.file_path))
|
.filter(file_path.eq(&exif_data.file_path))
|
||||||
.first::<ImageExif>(connection.deref_mut())
|
.first::<ImageExif>(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::UpdateError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_exif(&mut self, path: &str) -> Result<(), DbError> {
|
fn delete_exif(&mut self, context: &opentelemetry::Context, path: &str) -> Result<(), DbError> {
|
||||||
use schema::image_exif::dsl::*;
|
trace_db_call(context, "delete", "delete_exif", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
diesel::delete(image_exif.filter(file_path.eq(path)))
|
diesel::delete(image_exif.filter(file_path.eq(path)))
|
||||||
.execute(self.connection.lock().unwrap().deref_mut())
|
.execute(self.connection.lock().unwrap().deref_mut())
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Delete error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_with_date_taken(&mut self) -> Result<Vec<(String, i64)>, DbError> {
|
fn get_all_with_date_taken(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<(String, i64)>, DbError> {
|
||||||
|
trace_db_call(context, "query", "get_all_with_date_taken", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.select((file_path, date_taken))
|
.select((file_path, date_taken))
|
||||||
.filter(date_taken.is_not_null())
|
.filter(date_taken.is_not_null())
|
||||||
.load::<(String, Option<i64>)>(connection.deref_mut())
|
.load::<(String, Option<i64>)>(connection.deref_mut())
|
||||||
.map(|records| {
|
.map(|records| {
|
||||||
records
|
records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(path, dt)| dt.map(|ts| (path, ts)))
|
.filter_map(|(path, dt)| dt.map(|ts| (path, ts)))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_exif_batch(&mut self, file_paths: &[String]) -> Result<Vec<ImageExif>, DbError> {
|
fn get_exif_batch(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
file_paths: &[String],
|
||||||
|
) -> Result<Vec<ImageExif>, DbError> {
|
||||||
|
trace_db_call(context, "query", "get_exif_batch", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
if file_paths.is_empty() {
|
if file_paths.is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.filter(file_path.eq_any(file_paths))
|
.filter(file_path.eq_any(file_paths))
|
||||||
.load::<ImageExif>(connection.deref_mut())
|
.load::<ImageExif>(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_by_exif(
|
fn query_by_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
camera_make_filter: Option<&str>,
|
camera_make_filter: Option<&str>,
|
||||||
camera_model_filter: Option<&str>,
|
camera_model_filter: Option<&str>,
|
||||||
lens_model_filter: Option<&str>,
|
lens_model_filter: Option<&str>,
|
||||||
@@ -384,88 +458,111 @@ impl ExifDao for SqliteExifDao {
|
|||||||
date_from: Option<i64>,
|
date_from: Option<i64>,
|
||||||
date_to: Option<i64>,
|
date_to: Option<i64>,
|
||||||
) -> Result<Vec<ImageExif>, DbError> {
|
) -> Result<Vec<ImageExif>, DbError> {
|
||||||
use schema::image_exif::dsl::*;
|
trace_db_call(context, "query", "query_by_exif", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
let mut query = image_exif.into_boxed();
|
let mut query = image_exif.into_boxed();
|
||||||
|
|
||||||
// Camera filters (case-insensitive partial match)
|
// Camera filters (case-insensitive partial match)
|
||||||
if let Some(make) = camera_make_filter {
|
if let Some(make) = camera_make_filter {
|
||||||
query = query.filter(camera_make.like(format!("%{}%", make)));
|
query = query.filter(camera_make.like(format!("%{}%", make)));
|
||||||
}
|
}
|
||||||
if let Some(model) = camera_model_filter {
|
if let Some(model) = camera_model_filter {
|
||||||
query = query.filter(camera_model.like(format!("%{}%", model)));
|
query = query.filter(camera_model.like(format!("%{}%", model)));
|
||||||
}
|
}
|
||||||
if let Some(lens) = lens_model_filter {
|
if let Some(lens) = lens_model_filter {
|
||||||
query = query.filter(lens_model.like(format!("%{}%", lens)));
|
query = query.filter(lens_model.like(format!("%{}%", lens)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPS bounding box
|
// GPS bounding box
|
||||||
if let Some((min_lat, max_lat, min_lon, max_lon)) = gps_bounds {
|
if let Some((min_lat, max_lat, min_lon, max_lon)) = gps_bounds {
|
||||||
query = query
|
query = query
|
||||||
.filter(gps_latitude.between(min_lat, max_lat))
|
.filter(gps_latitude.between(min_lat, max_lat))
|
||||||
.filter(gps_longitude.between(min_lon, max_lon))
|
.filter(gps_longitude.between(min_lon, max_lon))
|
||||||
.filter(gps_latitude.is_not_null())
|
.filter(gps_latitude.is_not_null())
|
||||||
.filter(gps_longitude.is_not_null());
|
.filter(gps_longitude.is_not_null());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Date range
|
// Date range
|
||||||
if let Some(from) = date_from {
|
if let Some(from) = date_from {
|
||||||
query = query.filter(date_taken.ge(from));
|
query = query.filter(date_taken.ge(from));
|
||||||
}
|
}
|
||||||
if let Some(to) = date_to {
|
if let Some(to) = date_to {
|
||||||
query = query.filter(date_taken.le(to));
|
query = query.filter(date_taken.le(to));
|
||||||
}
|
}
|
||||||
if date_from.is_some() || date_to.is_some() {
|
if date_from.is_some() || date_to.is_some() {
|
||||||
query = query.filter(date_taken.is_not_null());
|
query = query.filter(date_taken.is_not_null());
|
||||||
}
|
}
|
||||||
|
|
||||||
query
|
query
|
||||||
.load::<ImageExif>(connection.deref_mut())
|
.load::<ImageExif>(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_camera_makes(&mut self) -> Result<Vec<(String, i64)>, DbError> {
|
fn get_camera_makes(
|
||||||
use diesel::dsl::count;
|
&mut self,
|
||||||
use schema::image_exif::dsl::*;
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<(String, i64)>, DbError> {
|
||||||
|
trace_db_call(context, "query", "get_camera_makes", |_span| {
|
||||||
|
use diesel::dsl::count;
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.filter(camera_make.is_not_null())
|
.filter(camera_make.is_not_null())
|
||||||
.group_by(camera_make)
|
.group_by(camera_make)
|
||||||
.select((camera_make, count(id)))
|
.select((camera_make, count(id)))
|
||||||
.order(count(id).desc())
|
.order(count(id).desc())
|
||||||
.load::<(Option<String>, i64)>(connection.deref_mut())
|
.load::<(Option<String>, i64)>(connection.deref_mut())
|
||||||
.map(|records| {
|
.map(|records| {
|
||||||
records
|
records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(make, cnt)| make.map(|m| (m, cnt)))
|
.filter_map(|(make, cnt)| make.map(|m| (m, cnt)))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_file_path(&mut self, old_path: &str, new_path: &str) -> Result<(), DbError> {
|
fn update_file_path(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
old_path: &str,
|
||||||
|
new_path: &str,
|
||||||
|
) -> Result<(), DbError> {
|
||||||
|
trace_db_call(context, "update", "update_file_path", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
diesel::update(image_exif.filter(file_path.eq(old_path)))
|
diesel::update(image_exif.filter(file_path.eq(old_path)))
|
||||||
.set(file_path.eq(new_path))
|
.set(file_path.eq(new_path))
|
||||||
.execute(connection.deref_mut())
|
.execute(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::UpdateError))?;
|
.map_err(|_| anyhow::anyhow!("Update error"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::UpdateError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_file_paths(&mut self) -> Result<Vec<String>, DbError> {
|
fn get_all_file_paths(
|
||||||
use schema::image_exif::dsl::*;
|
&mut self,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<String>, DbError> {
|
||||||
|
trace_db_call(context, "query", "get_all_file_paths", |_span| {
|
||||||
|
use schema::image_exif::dsl::*;
|
||||||
|
|
||||||
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
let mut connection = self.connection.lock().expect("Unable to get ExifDao");
|
||||||
|
|
||||||
image_exif
|
image_exif
|
||||||
.select(file_path)
|
.select(file_path)
|
||||||
.load(connection.deref_mut())
|
.load(connection.deref_mut())
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| anyhow::anyhow!("Query error"))
|
||||||
|
})
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/files.rs
34
src/files.rs
@@ -143,6 +143,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||||
let exif_results = exif_dao_guard
|
let exif_results = exif_dao_guard
|
||||||
.query_by_exif(
|
.query_by_exif(
|
||||||
|
&span_context,
|
||||||
req.camera_make.as_deref(),
|
req.camera_make.as_deref(),
|
||||||
req.camera_model.as_deref(),
|
req.camera_model.as_deref(),
|
||||||
req.lens_model.as_deref(),
|
req.lens_model.as_deref(),
|
||||||
@@ -276,7 +277,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
// Batch fetch EXIF data
|
// Batch fetch EXIF data
|
||||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||||
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
||||||
.get_exif_batch(&file_paths)
|
.get_exif_batch(&span_context, &file_paths)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
||||||
@@ -451,7 +452,7 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
// Batch fetch EXIF data
|
// Batch fetch EXIF data
|
||||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||||
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
||||||
.get_exif_batch(&file_paths)
|
.get_exif_batch(&span_context, &file_paths)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
||||||
@@ -896,6 +897,7 @@ mod tests {
|
|||||||
impl crate::database::ExifDao for MockExifDao {
|
impl crate::database::ExifDao for MockExifDao {
|
||||||
fn store_exif(
|
fn store_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
data: crate::database::models::InsertImageExif,
|
data: crate::database::models::InsertImageExif,
|
||||||
) -> Result<crate::database::models::ImageExif, crate::database::DbError> {
|
) -> Result<crate::database::models::ImageExif, crate::database::DbError> {
|
||||||
// Return a dummy ImageExif for tests
|
// Return a dummy ImageExif for tests
|
||||||
@@ -923,6 +925,7 @@ mod tests {
|
|||||||
|
|
||||||
fn get_exif(
|
fn get_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
_: &str,
|
_: &str,
|
||||||
) -> Result<Option<crate::database::models::ImageExif>, crate::database::DbError> {
|
) -> Result<Option<crate::database::models::ImageExif>, crate::database::DbError> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -930,6 +933,7 @@ mod tests {
|
|||||||
|
|
||||||
fn update_exif(
|
fn update_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
data: crate::database::models::InsertImageExif,
|
data: crate::database::models::InsertImageExif,
|
||||||
) -> Result<crate::database::models::ImageExif, crate::database::DbError> {
|
) -> Result<crate::database::models::ImageExif, crate::database::DbError> {
|
||||||
// Return a dummy ImageExif for tests
|
// Return a dummy ImageExif for tests
|
||||||
@@ -955,18 +959,24 @@ mod tests {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_exif(&mut self, _: &str) -> Result<(), crate::database::DbError> {
|
fn delete_exif(
|
||||||
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
|
_: &str,
|
||||||
|
) -> Result<(), crate::database::DbError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_with_date_taken(
|
fn get_all_with_date_taken(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
) -> Result<Vec<(String, i64)>, crate::database::DbError> {
|
) -> Result<Vec<(String, i64)>, crate::database::DbError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_exif_batch(
|
fn get_exif_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
_: &[String],
|
_: &[String],
|
||||||
) -> Result<Vec<crate::database::models::ImageExif>, crate::database::DbError> {
|
) -> Result<Vec<crate::database::models::ImageExif>, crate::database::DbError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
@@ -974,6 +984,7 @@ mod tests {
|
|||||||
|
|
||||||
fn query_by_exif(
|
fn query_by_exif(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
_: Option<&str>,
|
_: Option<&str>,
|
||||||
_: Option<&str>,
|
_: Option<&str>,
|
||||||
_: Option<&str>,
|
_: Option<&str>,
|
||||||
@@ -984,15 +995,26 @@ mod tests {
|
|||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_camera_makes(&mut self) -> Result<Vec<(String, i64)>, crate::database::DbError> {
|
fn get_camera_makes(
|
||||||
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<(String, i64)>, crate::database::DbError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_file_path(&mut self, old_path: &str, new_path: &str) -> Result<(), DbError> {
|
fn update_file_path(
|
||||||
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
|
_old_path: &str,
|
||||||
|
_new_path: &str,
|
||||||
|
) -> Result<(), DbError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_file_paths(&mut self) -> Result<Vec<String>, DbError> {
|
fn get_all_file_paths(
|
||||||
|
&mut self,
|
||||||
|
_context: &opentelemetry::Context,
|
||||||
|
) -> Result<Vec<String>, DbError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -158,6 +158,7 @@ async fn get_file_metadata(
|
|||||||
let tracer = global_tracer();
|
let tracer = global_tracer();
|
||||||
let context = extract_context_from_request(&request);
|
let context = extract_context_from_request(&request);
|
||||||
let mut span = tracer.start_with_context("get_file_metadata", &context);
|
let mut span = tracer.start_with_context("get_file_metadata", &context);
|
||||||
|
let span_context = opentelemetry::Context::current();
|
||||||
|
|
||||||
let full_path = is_valid_full_path(&app_state.base_path, &path.path, false);
|
let full_path = is_valid_full_path(&app_state.base_path, &path.path, false);
|
||||||
|
|
||||||
@@ -171,7 +172,7 @@ async fn get_file_metadata(
|
|||||||
|
|
||||||
// Query EXIF data if available
|
// Query EXIF data if available
|
||||||
if let Ok(mut dao) = exif_dao.lock()
|
if let Ok(mut dao) = exif_dao.lock()
|
||||||
&& let Ok(Some(exif)) = dao.get_exif(&path.path)
|
&& let Ok(Some(exif)) = dao.get_exif(&span_context, &path.path)
|
||||||
{
|
{
|
||||||
response.exif = Some(exif.into());
|
response.exif = Some(exif.into());
|
||||||
}
|
}
|
||||||
@@ -205,6 +206,7 @@ async fn upload_image(
|
|||||||
let tracer = global_tracer();
|
let tracer = global_tracer();
|
||||||
let context = extract_context_from_request(&request);
|
let context = extract_context_from_request(&request);
|
||||||
let mut span = tracer.start_with_context("upload_image", &context);
|
let mut span = tracer.start_with_context("upload_image", &context);
|
||||||
|
let span_context = opentelemetry::Context::current();
|
||||||
|
|
||||||
let mut file_content: BytesMut = BytesMut::new();
|
let mut file_content: BytesMut = BytesMut::new();
|
||||||
let mut file_name: Option<String> = None;
|
let mut file_name: Option<String> = None;
|
||||||
@@ -305,7 +307,7 @@ async fn upload_image(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut dao) = exif_dao.lock() {
|
if let Ok(mut dao) = exif_dao.lock() {
|
||||||
if let Err(e) = dao.store_exif(insert_exif) {
|
if let Err(e) = dao.store_exif(&span_context, insert_exif) {
|
||||||
error!("Failed to store EXIF data for {}: {:?}", relative_path, e);
|
error!("Failed to store EXIF data for {}: {:?}", relative_path, e);
|
||||||
} else {
|
} else {
|
||||||
debug!("EXIF data stored for {}", relative_path);
|
debug!("EXIF data stored for {}", relative_path);
|
||||||
@@ -877,6 +879,7 @@ fn process_new_files(
|
|||||||
exif_dao: Arc<Mutex<Box<dyn ExifDao>>>,
|
exif_dao: Arc<Mutex<Box<dyn ExifDao>>>,
|
||||||
modified_since: Option<SystemTime>,
|
modified_since: Option<SystemTime>,
|
||||||
) {
|
) {
|
||||||
|
let context = opentelemetry::Context::new();
|
||||||
let thumbs = dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
|
let thumbs = dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
|
||||||
let thumbnail_directory = Path::new(&thumbs);
|
let thumbnail_directory = Path::new(&thumbs);
|
||||||
|
|
||||||
@@ -922,7 +925,7 @@ fn process_new_files(
|
|||||||
|
|
||||||
let existing_exif_paths: HashMap<String, bool> = {
|
let existing_exif_paths: HashMap<String, bool> = {
|
||||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||||
match dao.get_exif_batch(&file_paths) {
|
match dao.get_exif_batch(&context, &file_paths) {
|
||||||
Ok(exif_records) => exif_records
|
Ok(exif_records) => exif_records
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|record| (record.file_path, true))
|
.map(|record| (record.file_path, true))
|
||||||
@@ -995,7 +998,7 @@ fn process_new_files(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||||
if let Err(e) = dao.store_exif(insert_exif) {
|
if let Err(e) = dao.store_exif(&context, insert_exif) {
|
||||||
error!("Failed to store EXIF data for {}: {:?}", relative_path, e);
|
error!("Failed to store EXIF data for {}: {:?}", relative_path, e);
|
||||||
} else {
|
} else {
|
||||||
debug!("EXIF data stored for {}", relative_path);
|
debug!("EXIF data stored for {}", relative_path);
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ pub fn extract_date_from_filename(filename: &str) -> Option<DateTime<FixedOffset
|
|||||||
/// Collect memories from EXIF database
|
/// Collect memories from EXIF database
|
||||||
fn collect_exif_memories(
|
fn collect_exif_memories(
|
||||||
exif_dao: &Data<Mutex<Box<dyn ExifDao>>>,
|
exif_dao: &Data<Mutex<Box<dyn ExifDao>>>,
|
||||||
|
context: &opentelemetry::Context,
|
||||||
base_path: &str,
|
base_path: &str,
|
||||||
now: NaiveDate,
|
now: NaiveDate,
|
||||||
span_mode: MemoriesSpan,
|
span_mode: MemoriesSpan,
|
||||||
@@ -344,7 +345,7 @@ fn collect_exif_memories(
|
|||||||
) -> Vec<(MemoryItem, NaiveDate)> {
|
) -> Vec<(MemoryItem, NaiveDate)> {
|
||||||
// Query database for all files with date_taken
|
// Query database for all files with date_taken
|
||||||
let exif_records = match exif_dao.lock() {
|
let exif_records = match exif_dao.lock() {
|
||||||
Ok(mut dao) => match dao.get_all_with_date_taken() {
|
Ok(mut dao) => match dao.get_all_with_date_taken(context) {
|
||||||
Ok(records) => records,
|
Ok(records) => records,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to query EXIF database: {:?}", e);
|
warn!("Failed to query EXIF database: {:?}", e);
|
||||||
@@ -471,8 +472,9 @@ pub async fn list_memories(
|
|||||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let tracer = global_tracer();
|
let tracer = global_tracer();
|
||||||
let context = extract_context_from_request(&request);
|
let parent_context = extract_context_from_request(&request);
|
||||||
let mut span = tracer.start_with_context("list_memories", &context);
|
let mut span = tracer.start_with_context("list_memories", &parent_context);
|
||||||
|
let span_context = opentelemetry::Context::current();
|
||||||
|
|
||||||
let span_mode = q.span.unwrap_or(MemoriesSpan::Day);
|
let span_mode = q.span.unwrap_or(MemoriesSpan::Day);
|
||||||
let years_back: u32 = 15;
|
let years_back: u32 = 15;
|
||||||
@@ -506,6 +508,7 @@ pub async fn list_memories(
|
|||||||
// Phase 1: Query EXIF database
|
// Phase 1: Query EXIF database
|
||||||
let exif_memories = collect_exif_memories(
|
let exif_memories = collect_exif_memories(
|
||||||
&exif_dao,
|
&exif_dao,
|
||||||
|
&span_context,
|
||||||
&app_state.base_path,
|
&app_state.base_path,
|
||||||
now,
|
now,
|
||||||
span_mode,
|
span_mode,
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ async fn get_all_tags<D: TagDao>(
|
|||||||
let camera_makes = exif_dao
|
let camera_makes = exif_dao
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Unable to get ExifDao")
|
.expect("Unable to get ExifDao")
|
||||||
.get_camera_makes()
|
.get_camera_makes(&span_context)
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
log::warn!("Failed to get camera makes: {:?}", e);
|
log::warn!("Failed to get camera makes: {:?}", e);
|
||||||
Vec::new()
|
Vec::new()
|
||||||
@@ -591,7 +591,7 @@ impl TagDao for SqliteTagDao {
|
|||||||
&mut self,
|
&mut self,
|
||||||
old_name: &str,
|
old_name: &str,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
context: &opentelemetry::Context,
|
_context: &opentelemetry::Context,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
use crate::database::schema::tagged_photo::dsl::*;
|
use crate::database::schema::tagged_photo::dsl::*;
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ impl TagDao for SqliteTagDao {
|
|||||||
|
|
||||||
fn get_all_photo_names(
|
fn get_all_photo_names(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &opentelemetry::Context,
|
_context: &opentelemetry::Context,
|
||||||
) -> anyhow::Result<Vec<String>> {
|
) -> anyhow::Result<Vec<String>> {
|
||||||
use crate::database::schema::tagged_photo::dsl::*;
|
use crate::database::schema::tagged_photo::dsl::*;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user