use std::path::PathBuf; use std::sync::{Arc, Mutex}; use clap::Parser; use image_api::cleanup::{ CleanupConfig, DatabaseUpdater, resolve_missing_files, validate_file_types, }; use image_api::database::{SqliteExifDao, SqliteFavoriteDao}; use image_api::tags::SqliteTagDao; #[derive(Parser, Debug)] #[command(name = "cleanup_files")] #[command(about = "File cleanup and fix utility for ImageApi", long_about = None)] struct Args { #[arg(long, help = "Preview changes without making them")] dry_run: bool, #[arg(long, help = "Auto-fix all issues without prompting")] auto_fix: bool, #[arg(long, help = "Skip phase 1 (missing file resolution)")] skip_phase1: bool, #[arg(long, help = "Skip phase 2 (file type validation)")] skip_phase2: bool, } fn main() -> anyhow::Result<()> { // Initialize logging env_logger::init(); // Load environment variables dotenv::dotenv()?; // Parse CLI arguments let args = Args::parse(); // Get base path from environment let base_path = dotenv::var("BASE_PATH")?; let base = PathBuf::from(&base_path); println!("File Cleanup and Fix Utility"); println!("============================"); println!("Base path: {}", base.display()); println!("Dry run: {}", args.dry_run); println!("Auto fix: {}", args.auto_fix); println!(); // Pre-flight checks if !base.exists() { eprintln!("Error: Base path does not exist: {}", base.display()); std::process::exit(1); } if !base.is_dir() { eprintln!("Error: Base path is not a directory: {}", base.display()); std::process::exit(1); } // Create configuration let config = CleanupConfig { base_path: base, dry_run: args.dry_run, auto_fix: args.auto_fix, }; // Create DAOs println!("Connecting to database..."); let tag_dao: Arc> = Arc::new(Mutex::new(SqliteTagDao::default())); let exif_dao: Arc> = Arc::new(Mutex::new(SqliteExifDao::new())); let favorites_dao: Arc> = Arc::new(Mutex::new(SqliteFavoriteDao::new())); // Create database updater let mut db_updater = DatabaseUpdater::new(tag_dao, exif_dao, favorites_dao); println!("āœ“ Database connected\n"); // Track overall statistics let mut total_issues_found = 0; let mut total_issues_fixed = 0; let mut total_errors = Vec::new(); // Phase 1: Missing file resolution if !args.skip_phase1 { match resolve_missing_files(&config, &mut db_updater) { Ok(stats) => { total_issues_found += stats.issues_found; total_issues_fixed += stats.issues_fixed; total_errors.extend(stats.errors); } Err(e) => { eprintln!("Phase 1 failed: {:?}", e); total_errors.push(format!("Phase 1 error: {}", e)); } } } else { println!("Phase 1: Skipped (--skip-phase1)"); } // Phase 2: File type validation if !args.skip_phase2 { match validate_file_types(&config, &mut db_updater) { Ok(stats) => { total_issues_found += stats.issues_found; total_issues_fixed += stats.issues_fixed; total_errors.extend(stats.errors); } Err(e) => { eprintln!("Phase 2 failed: {:?}", e); total_errors.push(format!("Phase 2 error: {}", e)); } } } else { println!("\nPhase 2: Skipped (--skip-phase2)"); } // Final summary println!("\n============================"); println!("Cleanup Complete!"); println!("============================"); println!("Total issues found: {}", total_issues_found); if config.dry_run { println!("Total issues that would be fixed: {}", total_issues_found); } else { println!("Total issues fixed: {}", total_issues_fixed); } if !total_errors.is_empty() { println!("\nErrors encountered:"); for (i, error) in total_errors.iter().enumerate() { println!(" {}. {}", i + 1, error); } println!("\nSome operations failed. Review errors above."); } else { println!("\nāœ“ No errors encountered"); } Ok(()) }