I have a windows service which merges PDFs together on the fly and then moves them to another location. I don't have control over what someone wants merged, for the most part. It has happened that every so often a corrupted PDF gets processed and therefore creating the new PdfDocument throws a PdfException "Trailer not found". I am catching the exception and closing the document but it appears after closing the PDF itself is still locked somehow. I need to delete the directory but in trying to do that it throws an IOException and crashes the service.
I have verified that calling the PdfDocument constructor is what locks the pdf and that immediately after closing the file remains locked.
Any ideas? Is there something iText can do to help with is or do I need to come up with some sort of work around where I check for corrupted PDFs up front?
ProcessDirectory
private void ProcessDirectory(string directoryPath)
{
EventLogManager.WriteInformation("ProcessDirectory");
// DON'T TOUCH THE BACKUPS, ERRORS AND WORK DIRECTORIES. Just in case they were made or renamed after the fact for some reason
if (directoryPath != this._errorsPath && directoryPath != this._backupsPath && directoryPath != this._workPath)
{
string pdfJsonPath = System.IO.Path.Combine(directoryPath, "pdf.json");
if (File.Exists(pdfJsonPath))
{
string workPath = System.IO.Path.Combine(this._workPath, System.IO.Path.GetFileName(directoryPath));
try
{
CopyToDirectory(directoryPath, workPath);
PdfMerge pdfMerge = null;
string jsonPath = System.IO.Path.Combine(workPath, "pdf.json");
using (StreamReader r = Helpers.GetStreamReader(jsonPath))
{
string json = r.ReadToEnd();
pdfMerge = JsonConvert.DeserializeObject<PdfMerge>(json);
}
FillFormFields(workPath, pdfMerge);
if (pdfMerge.Pdfs.Any(p => !String.IsNullOrWhiteSpace(p.OverlayFilename)))
{
ApplyOverlays(workPath, pdfMerge);
}
MergePdfs(workPath, pdfMerge);
//NumberPages(workPath, pdfMerge);
FinishPdf(workPath, pdfMerge);
// Move original to backups directory
if (DoSaveBackups)
{
string backupsPath = System.IO.Path.Combine(this._backupsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, backupsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
catch (Exception ex)
{
EventLogManager.WriteError(ex);
if (DoSaveErrors)
{
// Move original to errors directory
string errorsPath = System.IO.Path.Combine(this._errorsPath, String.Format("{0}_{1}", System.IO.Path.GetFileName(directoryPath), DateTime.Now.ToString("yyyyMMddHHmmss")));
Directory.Move(directoryPath, errorsPath);
}
else
{
Directory.Delete(directoryPath, true);
}
}
// Delete work directory
// THIS IS WHERE THE IOEXCEPTION OCCURS AND THE SERVICE CRASHES
Directory.Delete(workPath, true);
}
else
{
EventLogManager.WriteInformation(String.Format("No pdf.json file. {0} skipped.", directoryPath));
}
}
}
FillFormFields
private void FillFormFields(string directoryPath, PdfMerge pdfMerge)
{
if (pdfMerge != null && pdfMerge.Pdfs != null)
{
string formPath = String.Empty;
string newFilePath;
PdfDocument document = null;
PdfAcroForm form;
PdfFormField pdfFormField;
foreach (var pdf in pdfMerge.Pdfs)
{
try
{
formPath = System.IO.Path.Combine(directoryPath, pdf.Filename);
newFilePath = System.IO.Path.Combine(
directoryPath,
String.Format("{0}{1}", String.Format("{0}{1}", System.IO.Path.GetFileNameWithoutExtension(pdf.Filename), "_Revised"), System.IO.Path.GetExtension(pdf.Filename)));
// THIS IS WHERE THE PDFEXCEPTOIN OCCURS
document = new PdfDocument(Helpers.GetPdfReader(formPath), new PdfWriter(newFilePath));
form = PdfAcroForm.GetAcroForm(document, true);
if (pdf.Fields != null && pdf.Fields.Count > 0)
{
foreach (var field in pdf.Fields)
{
if (field.Value != null)
{
pdfFormField = form.GetField(field.Name);
if (pdfFormField != null)
{
form.GetField(field.Name).SetValue(field.Value);
}
else
{
EventLogManager.WriteWarning(String.Format("Field '{0}' does not exist in '{1}'", field.Name, pdf.Filename));
}
}
}
}
form.FlattenFields();
}
catch (Exception ex)
{
throw new Exception(String.Format("An exception occurred filling form fields for {0}", pdf.Filename), ex);
}
finally
{
if (document != null)
{
document.Close();
}
}
// Now rename the new one back to the old name
File.Delete(formPath);
File.Move(newFilePath, formPath);
}
}
}
It seems in order to everything to dispose properly you have to declare separate PdfReader and PdfWriter objects into using statements and pass those into the PdfDocument. Like this:
using (reader = Helpers.GetPdfReader(formPath))
{
using (writer = new PdfWriter(newFilePath))
{
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}
}
}
I'm not sure why this is other than that iText isn't disposing of the individual PdfReader and PdfWriter when disposing of the PdfDocument, which I assumed it would.
Find out which of the itext7 classes implement IDisposable (from the documentation, or the Visual Studio Object Browser etc), and make sure you use them within using blocks, the same way you already have using blocks for StreamReader.
Edit: @sourkrause's solution can be shortened to:
using (reader = Helpers.GetPdfReader(formPath))
using (writer = new PdfWriter(newFilePath))
using (document = new PdfDocument(reader, writer))
{
// The rest of the code here
}