using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using ImageCatalog_2.Models; using Microsoft.Extensions.Logging; namespace ImageCatalog_2.Services; public class AiExtractionService : IAiExtractionService { private readonly ILogger _logger; public AiExtractionService(ILogger logger) { _logger = logger; } public async Task RunAsync( AiExtractionRequest request, CancellationToken token, Func onResult, Func onProgress) { var searchOption = request.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var imageFiles = Directory.EnumerateFiles(request.SearchRoot, "*.*", searchOption) .Where(f => f.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) .ToList(); if (imageFiles.Count == 0) { return; } var extractedResults = new List(); Type? aiProcessorType = null; object? aiProcessor = null; try { var assembly = AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name?.Equals("AIFotoONLUS.Core", StringComparison.OrdinalIgnoreCase) == true); if (assembly != null) { aiProcessorType = assembly.GetType("AIFotoONLUS.Core.AiProcessor"); if (aiProcessorType != null) { aiProcessor = Activator.CreateInstance(aiProcessorType); } } } catch (Exception ex) { _logger.LogDebug(ex, "AIFotoONLUS.Core not available or failed to load via reflection"); } var processed = 0; var total = imageFiles.Count; foreach (var file in imageFiles) { token.ThrowIfCancellationRequested(); var extracted = string.Empty; if (aiProcessorType is not null && aiProcessor is not null) { try { var method = aiProcessorType.GetMethod("ExtractNumbersFromImage") ?? aiProcessorType.GetMethod("ExtractTextFromImage"); if (method is not null) { var value = method.Invoke(aiProcessor, new object[] { file }); if (value != null) { extracted = value.ToString() ?? string.Empty; } } } catch (Exception ex) { _logger.LogWarning(ex, "Error invoking AI processor for {File}", file); } } if (!string.IsNullOrWhiteSpace(extracted)) { var result = new AiResultItem { Path = file, Text = extracted }; extractedResults.Add(result); await onResult(result).ConfigureAwait(false); } processed++; var percent = total > 0 ? (processed * 100.0 / total) : 100.0; await onProgress(percent).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(request.CsvOutputPath)) { try { var dir = Path.GetDirectoryName(request.CsvOutputPath) ?? string.Empty; if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using var sw = new StreamWriter(request.CsvOutputPath, false, Encoding.UTF8); sw.WriteLine("Path,Text"); foreach (var r in extractedResults) { var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\""); sw.WriteLine($"\"{r.Path}\",\"{safeText}\""); } } catch (Exception ex) { _logger.LogError(ex, "Failed to write CSV to {CsvOutputPath}", request.CsvOutputPath); } } } }