use crate::cleanup::database_updater::DatabaseUpdater; use crate::cleanup::types::{CleanupConfig, CleanupStats}; use anyhow::Result; use log::{error, warn}; use std::path::PathBuf; // All supported image extensions to try const SUPPORTED_EXTENSIONS: &[&str] = &[ "jpg", "jpeg", "png", "webp", "tiff", "tif", "heif", "heic", "avif", "nef", ]; /// Phase 1: Resolve missing files by searching for alternative extensions pub fn resolve_missing_files( config: &CleanupConfig, db_updater: &mut DatabaseUpdater, ) -> Result { let mut stats = CleanupStats::new(); println!("\nPhase 1: Missing File Resolution"); println!("---------------------------------"); // Get all file paths from database println!("Scanning database for file references..."); let all_paths = db_updater.get_all_file_paths()?; println!("Found {} unique file paths\n", all_paths.len()); stats.files_checked = all_paths.len(); println!("Checking file existence..."); let mut missing_count = 0; let mut resolved_count = 0; for path_str in all_paths { let full_path = config.base_path.join(&path_str); // Check if file exists if full_path.exists() { continue; } missing_count += 1; stats.issues_found += 1; // Try to find the file with different extensions match find_file_with_alternative_extension(&config.base_path, &path_str) { Some(new_path_str) => { println!( "✓ {} → found as {} {}", path_str, new_path_str, if config.dry_run { "(dry-run, not updated)" } else { "" } ); if !config.dry_run { // Update database match db_updater.update_file_path(&path_str, &new_path_str) { Ok(_) => { resolved_count += 1; stats.issues_fixed += 1; } Err(e) => { error!("Failed to update database for {}: {:?}", path_str, e); stats.add_error(format!("DB update failed for {}: {}", path_str, e)); } } } else { resolved_count += 1; } } None => { warn!("✗ {} → not found with any extension", path_str); } } } println!("\nResults:"); println!("- Files checked: {}", stats.files_checked); println!("- Missing files: {}", missing_count); println!("- Resolved: {}", resolved_count); println!( "- Still missing: {}", missing_count - if config.dry_run { 0 } else { resolved_count } ); if !stats.errors.is_empty() { println!("- Errors: {}", stats.errors.len()); } Ok(stats) } /// Find a file with an alternative extension /// Returns the relative path with the new extension if found fn find_file_with_alternative_extension( base_path: &PathBuf, relative_path: &str, ) -> Option { let full_path = base_path.join(relative_path); // Get the parent directory and file stem (name without extension) let parent = full_path.parent()?; let stem = full_path.file_stem()?.to_str()?; // Try each supported extension for ext in SUPPORTED_EXTENSIONS { let test_path = parent.join(format!("{}.{}", stem, ext)); if test_path.exists() { // Convert back to relative path if let Ok(rel) = test_path.strip_prefix(base_path) { if let Some(rel_str) = rel.to_str() { return Some(rel_str.to_string()); } } } } None } #[cfg(test)] mod tests { use super::*; use std::fs; use tempfile::TempDir; #[test] fn test_find_file_with_alternative_extension() { let temp_dir = TempDir::new().unwrap(); let base_path = temp_dir.path().to_path_buf(); // Create a test file with .jpeg extension let test_file = base_path.join("test.jpeg"); fs::write(&test_file, b"test").unwrap(); // Try to find it as .jpg let result = find_file_with_alternative_extension(&base_path, "test.jpg"); assert!(result.is_some()); assert_eq!(result.unwrap(), "test.jpeg"); // Try to find non-existent file let result = find_file_with_alternative_extension(&base_path, "nonexistent.jpg"); assert!(result.is_none()); } }