Add EXIF search infrastructure (Phase 1 & 2)

Implements foundation for EXIF-based photo search capabilities:

- Add geo.rs module with GPS distance calculations (Haversine + bounding box)
- Extend FilesRequest with EXIF search parameters (camera, GPS, date, media type)
- Add MediaType enum and DateTakenAsc/DateTakenDesc sort options
- Create date_taken index migration for efficient date queries
- Implement ExifDao methods: get_exif_batch, query_by_exif, get_camera_makes
- Add FileWithMetadata struct for date-aware sorting
- Implement date sorting with filename extraction fallback
- Make extract_date_from_filename public for reuse

Next: Integrate EXIF filtering into list_photos() and enhance get_all_tags()
This commit is contained in:
Cameron
2025-12-18 09:34:07 -05:00
parent 52e1ced2a2
commit eb8e08b9ff
8 changed files with 314 additions and 1 deletions

View File

@@ -28,6 +28,14 @@ use crate::video::actors::StreamActor;
use path_absolutize::*;
use rand::prelude::SliceRandom;
use rand::thread_rng;
/// File metadata for sorting and filtering
/// Includes tag count and optional date for date-based sorting
pub struct FileWithMetadata {
pub file_name: String,
pub tag_count: i64,
pub date_taken: Option<i64>, // Unix timestamp from EXIF or filename extraction
}
use serde::Deserialize;
pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
@@ -264,6 +272,56 @@ fn sort(mut files: Vec<FileWithTagCount>, sort_type: SortType) -> Vec<String> {
SortType::TagCountDesc => {
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
}
SortType::DateTakenAsc | SortType::DateTakenDesc => {
// Date sorting not yet implemented for FileWithTagCount
// Will be implemented when integrating with FileWithMetadata
// For now, fall back to name sorting
files.sort_by(|l, r| l.file_name.cmp(&r.file_name));
}
}
files
.iter()
.map(|f| f.file_name.clone())
.collect::<Vec<String>>()
}
/// Sort files with metadata support (including date sorting)
fn sort_with_metadata(mut files: Vec<FileWithMetadata>, sort_type: SortType) -> Vec<String> {
match sort_type {
SortType::Shuffle => files.shuffle(&mut thread_rng()),
SortType::NameAsc => {
files.sort_by(|l, r| l.file_name.cmp(&r.file_name));
}
SortType::NameDesc => {
files.sort_by(|l, r| r.file_name.cmp(&l.file_name));
}
SortType::TagCountAsc => {
files.sort_by(|l, r| l.tag_count.cmp(&r.tag_count));
}
SortType::TagCountDesc => {
files.sort_by(|l, r| r.tag_count.cmp(&l.tag_count));
}
SortType::DateTakenAsc => {
files.sort_by(|l, r| {
match (l.date_taken, r.date_taken) {
(Some(a), Some(b)) => a.cmp(&b),
(Some(_), None) => std::cmp::Ordering::Less, // Dated photos first
(None, Some(_)) => std::cmp::Ordering::Greater,
(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