diff --git a/src/bin/migrate_exif.rs b/src/bin/migrate_exif.rs index 163e7ee..87684e7 100644 --- a/src/bin/migrate_exif.rs +++ b/src/bin/migrate_exif.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use chrono::Utc; +use clap::Parser; use rayon::prelude::*; use walkdir::WalkDir; @@ -9,16 +10,30 @@ use image_api::database::models::InsertImageExif; use image_api::database::{ExifDao, SqliteExifDao}; use image_api::exif; +#[derive(Parser, Debug)] +#[command(name = "migrate_exif")] +#[command(about = "Extract and store EXIF data from images", long_about = None)] +struct Args { + #[arg(long, help = "Skip files that already have EXIF data in database")] + skip_existing: bool, +} + fn main() -> anyhow::Result<()> { env_logger::init(); dotenv::dotenv()?; + let args = Args::parse(); let base_path = dotenv::var("BASE_PATH")?; let base = PathBuf::from(&base_path); println!("EXIF Migration Tool"); println!("==================="); println!("Base path: {}", base.display()); + if args.skip_existing { + println!("Mode: Skip existing (incremental)"); + } else { + println!("Mode: Upsert (insert new, update existing)"); + } println!(); // Collect all image files that support EXIF @@ -59,6 +74,19 @@ fn main() -> anyhow::Result<()> { } }; + // Check if EXIF data already exists + let existing = if let Ok(mut dao_lock) = dao.lock() { + dao_lock.get_exif(&relative_path).ok().flatten() + } else { + eprintln!("✗ {} - Failed to acquire database lock", relative_path); + return Err(anyhow::anyhow!("Lock error")); + }; + + // Skip if exists and skip_existing flag is set + if args.skip_existing && existing.is_some() { + return Ok(("skip".to_string(), relative_path)); + } + match exif::extract_exif_from_path(path) { Ok(exif_data) => { let timestamp = Utc::now().timestamp(); @@ -78,16 +106,31 @@ fn main() -> anyhow::Result<()> { shutter_speed: exif_data.shutter_speed, iso: exif_data.iso, date_taken: exif_data.date_taken, - created_time: timestamp, + created_time: existing + .as_ref() + .map(|e| e.created_time) + .unwrap_or(timestamp), last_modified: timestamp, }; - // Store in database + // Store or update in database if let Ok(mut dao_lock) = dao.lock() { - match dao_lock.store_exif(insert_exif) { - Ok(_) => { - println!("✓ {}", relative_path); - Ok(relative_path) + let result = if existing.is_some() { + // Update existing record + dao_lock.update_exif(insert_exif).map(|_| "update") + } else { + // Insert new record + dao_lock.store_exif(insert_exif).map(|_| "insert") + }; + + match result { + Ok(action) => { + if action == "update" { + println!("↻ {} (updated)", relative_path); + } else { + println!("✓ {} (inserted)", relative_path); + } + Ok((action.to_string(), relative_path)) } Err(e) => { eprintln!("✗ {} - Database error: {:?}", relative_path, e); @@ -107,23 +150,42 @@ fn main() -> anyhow::Result<()> { }) .collect(); - let success_count = results.iter().filter(|r| r.is_ok()).count(); - let error_count = results.len() - success_count; + // Count results + let mut success_count = 0; + let mut inserted_count = 0; + let mut updated_count = 0; + let mut skipped_count = 0; + + for result in &results { + if let Ok((action, _)) = result { + success_count += 1; + match action.as_str() { + "insert" => inserted_count += 1, + "update" => updated_count += 1, + "skip" => skipped_count += 1, + _ => {} + } + } + } + + let error_count = results.len() - success_count - skipped_count; println!(); println!("==================="); println!("Migration complete!"); - println!( - "Successfully extracted EXIF from {}/{} images", - success_count, - image_files.len() - ); + println!("Total images processed: {}", image_files.len()); + if inserted_count > 0 { + println!(" New EXIF records inserted: {}", inserted_count); + } + if updated_count > 0 { + println!(" Existing records updated: {}", updated_count); + } + if skipped_count > 0 { + println!(" Skipped (already exists): {}", skipped_count); + } if error_count > 0 { - println!( - "{} images had no EXIF data or encountered errors", - error_count - ); + println!(" Errors (no EXIF data or failures): {}", error_count); } Ok(())