003-knowledge-memory #55
@@ -304,17 +304,6 @@ pub trait ExifDao: Sync + Send {
|
|||||||
context: &opentelemetry::Context,
|
context: &opentelemetry::Context,
|
||||||
) -> Result<Vec<String>, DbError>;
|
) -> Result<Vec<String>, DbError>;
|
||||||
|
|
||||||
/// Get files sorted by date with optional pagination
|
|
||||||
/// Returns (sorted_file_paths, total_count)
|
|
||||||
fn get_files_sorted_by_date(
|
|
||||||
&mut self,
|
|
||||||
context: &opentelemetry::Context,
|
|
||||||
file_paths: &[String],
|
|
||||||
ascending: bool,
|
|
||||||
limit: Option<i64>,
|
|
||||||
offset: i64,
|
|
||||||
) -> Result<(Vec<String>, i64), DbError>;
|
|
||||||
|
|
||||||
/// Get all photos with GPS coordinates
|
/// Get all photos with GPS coordinates
|
||||||
/// Returns Vec<(file_path, latitude, longitude, date_taken)>
|
/// Returns Vec<(file_path, latitude, longitude, date_taken)>
|
||||||
fn get_all_with_gps(
|
fn get_all_with_gps(
|
||||||
@@ -609,66 +598,6 @@ impl ExifDao for SqliteExifDao {
|
|||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_files_sorted_by_date(
|
|
||||||
&mut self,
|
|
||||||
context: &opentelemetry::Context,
|
|
||||||
file_paths: &[String],
|
|
||||||
ascending: bool,
|
|
||||||
limit: Option<i64>,
|
|
||||||
offset: i64,
|
|
||||||
) -> Result<(Vec<String>, i64), DbError> {
|
|
||||||
trace_db_call(context, "query", "get_files_sorted_by_date", |span| {
|
|
||||||
use diesel::dsl::count_star;
|
|
||||||
use opentelemetry::KeyValue;
|
|
||||||
use opentelemetry::trace::Span;
|
|
||||||
use schema::image_exif::dsl::*;
|
|
||||||
|
|
||||||
span.set_attributes(vec![
|
|
||||||
KeyValue::new("file_count", file_paths.len() as i64),
|
|
||||||
KeyValue::new("ascending", ascending.to_string()),
|
|
||||||
KeyValue::new("limit", limit.map(|l| l.to_string()).unwrap_or_default()),
|
|
||||||
KeyValue::new("offset", offset.to_string()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if file_paths.is_empty() {
|
|
||||||
return Ok((Vec::new(), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = &mut *self.connection.lock().unwrap();
|
|
||||||
|
|
||||||
// Get total count of files that have EXIF data
|
|
||||||
let total_count: i64 = image_exif
|
|
||||||
.filter(file_path.eq_any(file_paths))
|
|
||||||
.select(count_star())
|
|
||||||
.first(connection)
|
|
||||||
.map_err(|_| anyhow::anyhow!("Count query error"))?;
|
|
||||||
|
|
||||||
// Build sorted query
|
|
||||||
let mut query = image_exif.filter(file_path.eq_any(file_paths)).into_boxed();
|
|
||||||
|
|
||||||
// Apply sorting
|
|
||||||
// Note: SQLite NULL handling varies - NULLs appear first for ASC, last for DESC by default
|
|
||||||
if ascending {
|
|
||||||
query = query.order(date_taken.asc());
|
|
||||||
} else {
|
|
||||||
query = query.order(date_taken.desc());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply pagination if requested
|
|
||||||
if let Some(limit_val) = limit {
|
|
||||||
query = query.limit(limit_val).offset(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute and extract file paths
|
|
||||||
let results: Vec<String> = query
|
|
||||||
.select(file_path)
|
|
||||||
.load::<String>(connection)
|
|
||||||
.map_err(|_| anyhow::anyhow!("Query error"))?;
|
|
||||||
|
|
||||||
Ok((results, total_count))
|
|
||||||
})
|
|
||||||
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_with_gps(
|
fn get_all_with_gps(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
59
src/files.rs
59
src/files.rs
@@ -61,45 +61,19 @@ fn apply_sorting_with_exif(
|
|||||||
|
|
||||||
match sort_type {
|
match sort_type {
|
||||||
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
||||||
info!("Date sorting requested, using database-level sorting");
|
info!("Date sorting requested, using in-memory sort with EXIF/filename fallback");
|
||||||
|
// Use in-memory sort so files without EXIF dates are included via
|
||||||
// Collect file paths for batch EXIF query
|
// filename extraction and filesystem metadata fallbacks.
|
||||||
let file_paths: Vec<String> = files.iter().map(|f| f.file_name.clone()).collect();
|
let (sorted, _) = in_memory_date_sort(
|
||||||
|
files,
|
||||||
// Try database-level sorting first (most efficient)
|
sort_type,
|
||||||
let ascending = sort_type == SortType::DateTakenAsc;
|
exif_dao,
|
||||||
match exif_dao.get_files_sorted_by_date(
|
|
||||||
span_context,
|
span_context,
|
||||||
&file_paths,
|
base_path,
|
||||||
ascending,
|
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
) {
|
);
|
||||||
Ok((sorted_files, db_total)) => {
|
(sorted, total_count)
|
||||||
info!(
|
|
||||||
"Database-level date sorting succeeded, returned {} files",
|
|
||||||
sorted_files.len()
|
|
||||||
);
|
|
||||||
(sorted_files, db_total)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"Database-level sorting failed: {:?}, falling back to in-memory sort",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
// Fallback to in-memory sorting with date extraction
|
|
||||||
let (sorted, _) = in_memory_date_sort(
|
|
||||||
files,
|
|
||||||
sort_type,
|
|
||||||
exif_dao,
|
|
||||||
span_context,
|
|
||||||
base_path,
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
(sorted, total_count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Use regular sort for non-date sorting
|
// Use regular sort for non-date sorting
|
||||||
@@ -1352,19 +1326,6 @@ mod tests {
|
|||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_files_sorted_by_date(
|
|
||||||
&mut self,
|
|
||||||
_context: &opentelemetry::Context,
|
|
||||||
file_paths: &[String],
|
|
||||||
_ascending: bool,
|
|
||||||
_limit: Option<i64>,
|
|
||||||
_offset: i64,
|
|
||||||
) -> Result<(Vec<String>, i64), DbError> {
|
|
||||||
// For tests, just return all files unsorted
|
|
||||||
let count = file_paths.len() as i64;
|
|
||||||
Ok((file_paths.to_vec(), count))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_with_gps(
|
fn get_all_with_gps(
|
||||||
&mut self,
|
&mut self,
|
||||||
_context: &opentelemetry::Context,
|
_context: &opentelemetry::Context,
|
||||||
|
|||||||
Reference in New Issue
Block a user