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);
}
}