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);
}
}
}
UPDATE
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
}
I know this is an old question, but this is my approach to solving in iText7, and it is quite different then the accepted answer. Since I could not use using statements, I took a different approach when closing out the document. This may seem like over kill, but it works very well.
First I closed the Document:
Document.Close();
Nothing out of the ordinary here.. after doing this however, I close / dispose the Reader and Writer instances. After closing them out, I'll set the writer, reader, and document in that order to null. The GC should take care of clearing these up, but for my usage the object that was holding these instance is still being used, so to free up some memory I'm doing this additional step.
step 2
Writer.Close();
Writer.Dispose();
Writer = null;
Step 3
Reader.SetCloseStream(true);
Reader.Close();
Reader = null;
Step 4
Document = null;
I would suggest you wrap each step in a try catch; depending on how your code is running, you could see issues doing this all at once.
I believe the most important part here is the actions taken on the reader. For some reason, the reader does not seem to close the stream when calling .Close() by default.
***While running in production I have still noticed that one file (so far anyways) still held a lock when trying to delete right after closing. I added a catch that waits a few seconds before trying again. That seems to do the trick on those more "stubborn" files.
Related
I'm trying to access a file with my WPF project and I get an exception saying it couldn't access the file because another process is using it.
I don't see any reason because the only process which used it was syncronized and it should close the file after it used it. I tried the "client.Dispose();" below, but it didn't help.
Any advice may be a help! Thanks.
I'm trying to access "currentQr" file in local url. Here's my code:
private void BtnScanQR(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
currentQr= System.IO.Path.GetFullPath(openFileDialog.FileName).Replace(#"\\", #"\");
if (!bL.IsQRExist(currentQr))
{
/////// some code
}
}
}
It calls "bL.IsQRExist(currentQr)" which calls "dal" :
public bool IsQRExist(string currentQr)
{
return Dal.IsQRExist(currentQr);
}
Here's my Dal.IsQRExist function, Which calls directly to "DecodeQR" function:
public bool IsQRExist(string currentQr)
{
Product p = Tools.ConvertQRToProduct(currentQr);
if (GetProductID(p) == -1)
return false;
return true; }
And in "Tools.DecodeQR" there's the Actual access to the file:
public static string DecodeQR(string downloadUrl) //'unzip' qrCode from url and interperts its meaning
{
string imageUrl = downloadUrl;
// Install-Package ZXing.Net -Version 0.16.5
var client = new WebClient();
var stream = client.OpenRead(imageUrl);
if (stream == null) return "";
var bitmap = new Bitmap(stream);
IBarcodeReader reader = new BarcodeReader();
var result = reader.Decode(bitmap);
client.Dispose();
return result.Text;
}
After this only access to the file, I'm trying to access the file again in another proccess but it says that another proccess (must be this one i descibed here, cause when i cancled this it didn't throw the exception).
So how can I make sure the file isn't being accessed anymore after this code above?
And I also thinks maybe is there a way to close all accesses to a file, whether they were made in this code or any other code.
Thanks
Since Bitmap is an IDisposable, you have to make sure that you properly dispose of it after usage, which is usually done by creating it in a using statement.
In addition, when you load a Bitmap from a local file, you would not have to deal with Streams at all.
This should be sufficient:
public static string DecodeQR(string imageFilePath)
{
using (var bitmap = new Bitmap(imageFilePath))
{
return new BarcodeReader().Decode(bitmap).Text;
}
}
I think the file handle, that isn't closed, is held by the variable stream which receives the file opened by WebClient.OpenRead. You will need to dispose the stream resource as well.
With a using block it's disposed automatically when the block is exited - where using guarantees to invoke Dispose even in case of exceptions thrown inside the using block. Which is quite good, when you want to open that same file later again.
Also I think you don't need a WebClient if you deal with a local file. Just opening that file as a FileStream seems more straight forward.
using System.IO;
...
public static string DecodeQR(string localImageFile)
{
using (var stream = new FileStream(localImageFile,
FileMode.Open, FileMode.Read))
{
if (stream == null) return "";
var bitmap = new Bitmap(stream);
// Install-Package ZXing.Net -Version 0.16.5
var reader = new BarcodeReader();
var result = reader.Decode(bitmap);
return result.Text;
}
}
I'm using the two functions to read and write huge files (write to multiple files). I want to keep the file operation in the functions because the lines may be read/write from other sources.
Update:
C# doesn't really have coroutine. Is it a good use case for Reactive extensions?
foreach (var line in ReadFrom("filename"))
{
try
{
.... // Some actions based on the line
var l = .....
WriteTo("generatedFile1", l);
}
catch (Exception e)
{
var l = ..... // get some data from line, e and other objects etc.
WriteTo("generatedFile2", l);
}
}
The following function open the file once until all the lines are read and then close and release the resource.
private static IEnumerable<string> ReadFrom(string file)
{
string line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
yield return line;
}
}
However, the following function, which write the lines instead of read lines, open and close the file for each line it writes. Is it possible to implement it in a way so it only open the file once and continue to write to the file until EOF is sent?
private static void WriteTo(string file, string line)
{
if (!File.Exists(file)) // Remove and recreate the file if existing
using (var tw = File.CreateText(file))
{
tw.WriteLine(line);
}
else
using (var tw = new StreamWriter(file, true))
{
tw.WriteLine(line);
}
}
Just use File.WriteAllLines. It will write all of the lines in a sequence to a file, and it won't open/close the file for each line.
You can remove the entire second method, and replace the call with var writer = new StreamWriter(file, true), as that constructor creates the file if it does not exist.
You can then use writer.WriteLine() until you're done writing, and Dispose() it afterwards.
In trying to answer this question, I was surprised to discover that attempting to create a new file when that file already exists does not throw a unique exception type, it just throws a generic IOException.
I am therefore left wondering how to determine if the IOException is the result of an existing file, or some other IO error.
The exception has an HResult, but this property is protected, and thus unavailable to me.
The only other way I can see is to pattern match the message string which feels awful.
example:
try
{
using (var stream = new FileStream("C:\\Test.txt", FileMode.CreateNew))
using (var writer = new StreamWriter(stream))
{
//write file
}
}
catch (IOException e)
{
//how do I know this is because a file exists?
}
try
{
using (var stream = new FileStream("C:\\Test.txt", FileMode.CreateNew))
using (var writer = new StreamWriter(stream))
{
//write file
}
}
catch (IOException e)
{
var exists = File.Exists(#"C:\Text.text"); // =)
}
Won't work for temp files etc which might have been deleted again.
Here are my exception best practices: https://coderr.io/exception-handling
Edit: there is another Hresult that is used when file already exists: 0x800700B7 (-2147024713) "Cannot create a file when that file already exists". Updated the code sample.
When you try to create a new file and it already exists IOException will have Hresult = 0x80070050 (-2147024816).
So you code could look like this:
try
{
using (var stream = new FileStream("C:\\Test.txt", FileMode.CreateNew))
using (var writer = new StreamWriter(stream))
{
//write file
}
}
catch (IOException e)
{
if (e.HResult == -2147024816 ||
e.HResult == -2147024713)
{
// File already exists.
}
}
You can place this condition in your catch statement for IOException: if(ex.Message.Contains("already exists")) { ... }. It is a hack, but it will work for all cases that a file exists, even temporary files and such.
To modify #jgauffin, in C# 6, you can use the File.Exists inside of the when clause to avoid entering the catch block and thus behaving more like an actual dedicated exception:
try
{
using (var stream = new FileStream("C:\\Test.txt", FileMode.CreateNew))
using (var writer = new StreamWriter(stream))
{
//write file
}
}
catch (IOException e) when (File.Exists(#"C:\Text.text"))
{
//...
}
It's not 100% foolproof (there are other reasons for an IOException), but you can at least exclude all derived exception types:
try
{
...
}
catch(IOException e)
{
if (e is UnauthorizedAccessException) throw;
if (e is DirectoryNotFoundException) throw;
if (e is PathTooLongException) throw;
// etc for other exceptions derived from IOException
... assume file exists
}
or the equivalent:
try
{
...
}
catch(UnauthorizedAccessException)
{
throw;
}
catch(DirectoryNotFoundException)
{
throw;
}
catch(PathTooLongException)
{
throw;
}
catch(IOException e)
{
... assume file exists
}
As for the linked question, I'd just check for existence, prompt the user to overwrite, then use OpenOrCreate to overwrite if it exists. I think most apps work this way even if there is a theoretical risk of overwriting a file that's created just at the wrong moment.
In C# 6 and later:
const int WARN_WIN32_FILE_EXISTS = unchecked((int)0x80070050);
try
{
...
}
catch (IOException e) when (e.HResult == WARN_WIN32_FILE_EXISTS)
{
...
}
... or just when (e.HResult == -2147024816), if you're going for "quick and impenetrable". ;-)
(FWIW, the Windows-centric error code has been faithfully copied by Mono and also works on Mac/Linux.)
You can't. Unfortunatly IOExceptions are not further specified for some reason beyond my comprehension in the .NET framework.
But in case of creating a new file it is common practice to check if the file exists first. Like so:
try
{
if (File.Exists("C:\\Test.txt"))
{
//write file
using (var stream = new FileStream("C:\\Test.txt", FileMode.CreateNew))
using (var writer = new StreamWriter(stream))
{
//The actual writing of file
}
}
}
catch (IOException ex)
{
//how do I know this is because a file exists?
Debug.Print(ex.Message);
}
Perhaps not the answer you were looking for. But, c'est ca.
You should use
FileMode.Create
instead of
FileMode.CreateNew
It will override a file if its already exists.
As you can understand from title, i modified Settings.cs file.Added some properties, some code to constractor (public Settings()) and overrided Save() function.But it is a patial class so i know what kind of consequences you have to face when you modify IDE generated files especially .designers.It is not a designer but an IDE generated internal sealed partial and i want to know is it 100% safe or not?
internal sealed partial class Settings
{
public System.Data.SqlClient.SqlConnection AppDbConnection
{
get
{
if (_AppDbConnection == null)
{
try
{
_AppDbConnection = new System.Data.SqlClient.SqlConnection(_ConnectionString);
_AppDbConnection.Open();
}
catch (System.Exception ex) { throw ex; }
}
return _AppDbConnection;
}
}
private System.Data.SqlClient.SqlConnection _AppDbConnection;
private string _ConnectionString;
public override void Save()
{
System.IO.FileInfo fi = new System.IO.FileInfo(System.Windows.Forms.Application.StartupPath + "\\Settings.dat");
System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
System.IO.StreamWriter sw = new System.IO.StreamWriter(fs, System.Text.Encoding.GetEncoding("iso-8859-9"));
try { sw.Write(Helper.BinarySerializer.ToBinary(_SettingsBase)); }
catch (System.Exception ex) { throw ex; }
finally { sw.Close(); fs.Close(); }
base.Save();
}
public Settings()
{
try
{
System.IO.FileInfo fi = new System.IO.FileInfo(System.Windows.Forms.Application.StartupPath + "\\Settings.dat");
if (fi.Exists)
{
System.IO.FileStream fs = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
System.IO.StreamReader sr = new System.IO.StreamReader(fs, System.Text.Encoding.GetEncoding("iso-8859-9"));
string data = sr.ReadToEnd();
if (data != "")
{
_SettingsBase = (AppCode.SettingsBase)Helper.BinarySerializer.BinaryTo(data);
_ConnectionString = Helper.Crypto.DecryptString(_SettingsBase.ConnectionString, "");
}
If your additions are in a separate file that was not created by some code generation tool, then the IDE won't overwrite your changes (just don't name it something ending with *.designer.cs, just in case).
So that would be at least safe from the IDE's code generation.
Do not edit the files generated by visual studio (Those file usually have a comment at the top warning you about this).
Autogenerated files like this are one of the main reasons to declare classes as partial, so that you can extend it in another file without fear that your changes are overwritten.
Note: Any sensitive data in that connection string won't be safe though
to know is it 100% safe or not?
100% unsafe. Designer change = file thrown away.
Make another file, partial, add your code there. ANY CHANGE TO CODE IN THIS FILE IS NOT SAFE AND WILL BE LOST.
Accident waiting to happen.
I have a use-case where I'm required to read in some information from an XML file and act on it accordingly. The problem is, this XML file is technically allowed to be empty or full of whitespace and this means "there's no info, do nothing", any other error should fail hard.
I'm currently thinking about something along the lines of:
public void Load (string fileName)
{
XElement xml;
try {
xml = XElement.Load (fileName);
}
catch (XmlException e) {
// Check if the file contains only whitespace here
// if not, re-throw the exception
}
if (xml != null) {
// Do this only if there wasn't an exception
doStuff (xml);
}
// Run this irrespective if there was any xml or not
tidyUp ();
}
Does this pattern seem ok? If so, how do people recommend implementing the check for if the file contained only whitespace inside the catch block? Google only throws up checks for if a string is whitespace...
Cheers muchly,
Graham
Well, the easiest way is probably to make sure it isn't whitespace in the first place, by reading the entire file into a string first (I'm assuming it isn't too huge):
public void Load (string fileName)
{
var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
var reader = new StreamReader(stream, Encoding.UTF8, true);
var xmlString = reader.ReadToEnd();
if (!string.IsNullOrWhiteSpace(xmlString)) { // Use (xmlString.Trim().Length == 0) for .NET < 4
var xml = XElement.Parse(xmlString); // Exceptions will bubble up
doStuff(xml);
}
tidyUp();
}