using DevExpress.Pdf; using DigitalData.EmailProfiler.Application.Common.Interfaces; using DigitalData.EmailProfiler.Domain.Exceptions; namespace DigitalData.EmailProfiler.Infrastructure.Services; /// /// PDF processing service using DevExpress.Pdf. /// Implements PDF validation and embedded file extraction using streams. /// public class DevExpressPdfProcessingService : IPdfProcessingService { public Task ValidatePdfAsync(Stream pdfStream, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(pdfStream); if (!pdfStream.CanRead) throw new ArgumentException("Stream must be readable.", nameof(pdfStream)); if (!pdfStream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(pdfStream)); if (pdfStream.Position != 0) pdfStream.Position = 0; using var processor = new PdfDocumentProcessor(); processor.LoadDocument(pdfStream); return Task.FromResult(true); } public async Task> ExtractEmbeddedFilesAsync( Stream pdfStream, string outputDirectory, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(pdfStream); ArgumentException.ThrowIfNullOrWhiteSpace(outputDirectory); if (!pdfStream.CanRead) throw new ArgumentException("Stream must be readable.", nameof(pdfStream)); if (!pdfStream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(pdfStream)); if (pdfStream.Position != 0) pdfStream.Position = 0; if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); using var processor = new PdfDocumentProcessor(); processor.LoadDocument(pdfStream); var extractedFiles = new List(); var attachments = processor.Document.FileAttachments; if (attachments == null || !attachments.Any()) return extractedFiles; foreach (var attachment in attachments) { var fileName = attachment.FileName ?? $"attachment_{Guid.NewGuid()}.dat"; var outputPath = Path.Combine(outputDirectory, fileName); var fileData = attachment.Data; if (fileData == null || fileData.Length == 0) continue; await File.WriteAllBytesAsync(outputPath, fileData, cancellationToken); extractedFiles.Add(outputPath); } return extractedFiles; } public Task GetPageCountAsync(Stream pdfStream, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(pdfStream); if (!pdfStream.CanRead) throw new ArgumentException("Stream must be readable.", nameof(pdfStream)); if (!pdfStream.CanSeek) throw new ArgumentException("Stream must be seekable.", nameof(pdfStream)); if (pdfStream.Position != 0) pdfStream.Position = 0; using var processor = new PdfDocumentProcessor(); processor.LoadDocument(pdfStream); return Task.FromResult(processor.Document.Pages.Count); } }