feature/exif-endpoint #44
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user