feature/exif-endpoint #44
178
src/files.rs
178
src/files.rs
@@ -43,6 +43,56 @@ pub struct FileWithMetadata {
|
|||||||
}
|
}
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Apply sorting to files with EXIF data support for date-based sorting
|
||||||
|
/// Handles both date sorting (with EXIF/filename fallback) and regular sorting
|
||||||
|
fn apply_sorting_with_exif(
|
||||||
|
files: Vec<FileWithTagCount>,
|
||||||
|
sort_type: SortType,
|
||||||
|
exif_dao: &mut Box<dyn ExifDao>,
|
||||||
|
span_context: &opentelemetry::Context,
|
||||||
|
) -> Vec<String> {
|
||||||
|
match sort_type {
|
||||||
|
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
||||||
|
info!("Date sorting requested, fetching EXIF data");
|
||||||
|
|
||||||
|
// Collect file paths for batch EXIF query
|
||||||
|
let file_paths: Vec<String> = files.iter().map(|f| f.file_name.clone()).collect();
|
||||||
|
|
||||||
|
// Batch fetch EXIF data
|
||||||
|
let exif_map: std::collections::HashMap<String, i64> = exif_dao
|
||||||
|
.get_exif_batch(span_context, &file_paths)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Convert to FileWithMetadata with date fallback logic
|
||||||
|
let files_with_metadata: Vec<FileWithMetadata> = files
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| {
|
||||||
|
// Try EXIF date first
|
||||||
|
let date_taken = exif_map.get(&f.file_name).copied().or_else(|| {
|
||||||
|
// Fallback to filename extraction
|
||||||
|
extract_date_from_filename(&f.file_name).map(|dt| dt.timestamp())
|
||||||
|
});
|
||||||
|
|
||||||
|
FileWithMetadata {
|
||||||
|
file_name: f.file_name,
|
||||||
|
tag_count: f.tag_count,
|
||||||
|
date_taken,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
sort_with_metadata(files_with_metadata, sort_type)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Use regular sort for non-date sorting
|
||||||
|
sort(files, sort_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
||||||
_: Claims,
|
_: Claims,
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
@@ -264,51 +314,13 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
.collect::<Vec<FileWithTagCount>>()
|
.collect::<Vec<FileWithTagCount>>()
|
||||||
})
|
})
|
||||||
.map(|files| {
|
.map(|files| {
|
||||||
// Handle sorting - use FileWithMetadata for date sorting to support EXIF dates
|
// Handle sorting - use helper function that supports EXIF date sorting
|
||||||
let sort_type = req.sort.unwrap_or(NameAsc);
|
let sort_type = req.sort.unwrap_or(NameAsc);
|
||||||
match sort_type {
|
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||||
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
let result =
|
||||||
info!("Date sorting requested in tagged/recursive search, fetching EXIF data");
|
apply_sorting_with_exif(files, sort_type, &mut exif_dao_guard, &span_context);
|
||||||
|
drop(exif_dao_guard);
|
||||||
// Collect file paths for batch EXIF query
|
result
|
||||||
let file_paths: Vec<String> =
|
|
||||||
files.iter().map(|f| f.file_name.clone()).collect();
|
|
||||||
|
|
||||||
// Batch fetch EXIF data
|
|
||||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
|
||||||
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
|
||||||
.get_exif_batch(&span_context, &file_paths)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
|
||||||
.collect();
|
|
||||||
drop(exif_dao_guard);
|
|
||||||
|
|
||||||
// Convert to FileWithMetadata with date fallback logic
|
|
||||||
let files_with_metadata: Vec<FileWithMetadata> = files
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
// Try EXIF date first
|
|
||||||
let date_taken = exif_map.get(&f.file_name).copied().or_else(|| {
|
|
||||||
// Fallback to filename extraction
|
|
||||||
extract_date_from_filename(&f.file_name).map(|dt| dt.timestamp())
|
|
||||||
});
|
|
||||||
|
|
||||||
FileWithMetadata {
|
|
||||||
file_name: f.file_name,
|
|
||||||
tag_count: f.tag_count,
|
|
||||||
date_taken,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
sort_with_metadata(files_with_metadata, sort_type)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Use regular sort for non-date sorting
|
|
||||||
sort(files, sort_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.inspect(|files| debug!("Found {:?} files", files.len()))
|
.inspect(|files| debug!("Found {:?} files", files.len()))
|
||||||
.map(|tagged_files: Vec<String>| {
|
.map(|tagged_files: Vec<String>| {
|
||||||
@@ -439,53 +451,13 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
|
|
||||||
info!("After all filters, {} files remain", photos.len());
|
info!("After all filters, {} files remain", photos.len());
|
||||||
|
|
||||||
// Handle sorting - use FileWithMetadata for date sorting to support EXIF dates
|
// Handle sorting - use helper function that supports EXIF date sorting
|
||||||
let response_files = if let Some(sort_type) = req.sort {
|
let response_files = if let Some(sort_type) = req.sort {
|
||||||
match sort_type {
|
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||||
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
let result =
|
||||||
info!("Date sorting requested, fetching EXIF data");
|
apply_sorting_with_exif(photos, sort_type, &mut exif_dao_guard, &span_context);
|
||||||
|
drop(exif_dao_guard);
|
||||||
// Collect file paths for batch EXIF query
|
result
|
||||||
let file_paths: Vec<String> =
|
|
||||||
photos.iter().map(|f| f.file_name.clone()).collect();
|
|
||||||
|
|
||||||
// Batch fetch EXIF data
|
|
||||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
|
||||||
let exif_map: std::collections::HashMap<String, i64> = exif_dao_guard
|
|
||||||
.get_exif_batch(&span_context, &file_paths)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|exif| exif.date_taken.map(|dt| (exif.file_path, dt)))
|
|
||||||
.collect();
|
|
||||||
drop(exif_dao_guard);
|
|
||||||
|
|
||||||
// Convert to FileWithMetadata with date fallback logic
|
|
||||||
let files_with_metadata: Vec<FileWithMetadata> = photos
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
// Try EXIF date first
|
|
||||||
let date_taken =
|
|
||||||
exif_map.get(&f.file_name).copied().or_else(|| {
|
|
||||||
// Fallback to filename extraction
|
|
||||||
extract_date_from_filename(&f.file_name)
|
|
||||||
.map(|dt| dt.timestamp())
|
|
||||||
});
|
|
||||||
|
|
||||||
FileWithMetadata {
|
|
||||||
file_name: f.file_name,
|
|
||||||
tag_count: f.tag_count,
|
|
||||||
date_taken,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
sort_with_metadata(files_with_metadata, sort_type)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Use regular sort for non-date sorting
|
|
||||||
sort(photos, sort_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// No sorting requested
|
// No sorting requested
|
||||||
photos
|
photos
|
||||||
@@ -540,9 +512,9 @@ fn sort(mut files: Vec<FileWithTagCount>, sort_type: SortType) -> Vec<String> {
|
|||||||
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
|
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
|
||||||
}
|
}
|
||||||
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
||||||
// Date sorting not yet implemented for FileWithTagCount
|
// Date sorting not implemented for FileWithTagCount
|
||||||
// Will be implemented when integrating with FileWithMetadata
|
// We shouldn't be hitting this code
|
||||||
// For now, fall back to name sorting
|
warn!("Date sorting not implemented for FileWithTagCount");
|
||||||
files.sort_by(|l, r| l.file_name.cmp(&r.file_name));
|
files.sort_by(|l, r| l.file_name.cmp(&r.file_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,26 +541,22 @@ fn sort_with_metadata(mut files: Vec<FileWithMetadata>, sort_type: SortType) ->
|
|||||||
SortType::TagCountDesc => {
|
SortType::TagCountDesc => {
|
||||||
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
|
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
|
||||||
}
|
}
|
||||||
SortType::DateTakenAsc => {
|
SortType::DateTakenAsc | SortType::DateTakenDesc => {
|
||||||
files.sort_by(|l, r| {
|
files.sort_by(|l, r| {
|
||||||
match (l.date_taken, r.date_taken) {
|
match (l.date_taken, r.date_taken) {
|
||||||
(Some(a), Some(b)) => a.cmp(&b),
|
(Some(a), Some(b)) => {
|
||||||
|
if sort_type == SortType::DateTakenAsc {
|
||||||
|
a.cmp(&b)
|
||||||
|
} else {
|
||||||
|
b.cmp(&a)
|
||||||
|
}
|
||||||
|
}
|
||||||
(Some(_), None) => std::cmp::Ordering::Less, // Dated photos first
|
(Some(_), None) => std::cmp::Ordering::Less, // Dated photos first
|
||||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||||
(None, None) => l.file_name.cmp(&r.file_name), // Fallback to name
|
(None, None) => l.file_name.cmp(&r.file_name), // Fallback to name
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
SortType::DateTakenDesc => {
|
|
||||||
files.sort_by(|l, r| {
|
|
||||||
match (l.date_taken, r.date_taken) {
|
|
||||||
(Some(a), Some(b)) => b.cmp(&a), // Reverse for descending
|
|
||||||
(Some(_), None) => std::cmp::Ordering::Less, // Dated photos first
|
|
||||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
|
||||||
(None, None) => r.file_name.cmp(&l.file_name), // Fallback reversed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
files
|
files
|
||||||
|
|||||||
Reference in New Issue
Block a user