Issue when attempting to manipulate Zip File

Nov 4, 2009 at 6:37 AM

Hello,

First, let me say that this is an absolutely amazing project.  I've been using DotNetZip Library for a while, and it is really very impressive.

My issue though is that I am creating a zip file that contains a number of other files (Pictures, documents, etc.).  I am then attempting to e-mail this file.  This fails.  I can e-mail other attachments without a problem, but can't seem to e-mail the zip.  I was wondering if there were any suggestions for this.  I had been using 1.5 so I updated to 1.8 and am still experiencing the problem.  It appears that when the attachment is very small (A few KB's) I am able to e-mail the file, but if it is larger it fails.  I have tried disposing the zip object and this has no effect.  I was curious if there was something that I might be missing or maybe failed to think about.  I'm open to any suggestions or ideas as well, as I would really like to be able to get this to work.

Thanks for your response, and thanks again for a really amazing utility.

-Matt-

Coordinator
Nov 4, 2009 at 9:24 AM

Hey Matt,

Thanks for the compliments.

I don;t know what to suggest because you haven't described the failure.  What is the failure?  In some cases there can be a filesize limit on attachments.  IS that the problem? 

It could be something else as well.  An exception and a stacktrace might help clarify.

Good luck!

Nov 5, 2009 at 9:45 AM

Thanks for the response Cheeso.  I can certainly provide you with any source code or anything that you would like to see as well.  I am not getting a failure, the mail message is just timing out.  As I created a standalone application that sends the same zip as an attachment and this works successfully, I know that it isn't a filesize limit.  I'm thinking that the zip isn't being disposed perhaps?  The fact that it does work fine for small zip files seems to (At least in my mind) support the dispose possibility as the application would hold onto a larger zip longer?

Unfortunately the exception isn't really helpful, as it is just that the mail message timed out.  I've never compiled a stacktrace before, but I would certainly be willing to do that for you (I just need to know how to do that).

If there is anything else I can do, please let me know.  I've been working on this for over a week now, and have run out of ideas...  The zip is able to be generated fine.  The mail message is able to be sent fine.  But I can't generate the zip then send it.  I have a background worker that is generating the zip file.  In that backgroundWorker_Completed event I am sending the mail message, which is in another backgroundWorker.

Coordinator
Nov 5, 2009 at 11:28 AM

Ok, that makes sense.

a message timeout. You may be onto something with the "zip isn't being disposed" theory.  But, by you raising the possibility, I'm guessing you've taken the care to call Dispose() in  your code, or employ a using clause, and so this isn't a probblem for you, right?

Just to make sure, Can you show me the code that you use to produce the zip file ?

 

Nov 6, 2009 at 6:57 AM

Hi Cheeso.

That's the thing.  I am using a Using statement, and am also calling Dispose(). 

Here's the code that I'm using:

        private void CompileZipbackgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
        {
            string[] tempDirectory = (string[])e.Argument;
            string FilePath = tempDirectory[0].ToString();

            using (ZipFile zip = new ZipFile(FilePath + @"\ZipFile.zip"))
            {
                string current = Directory.GetCurrentDirectory();
                System.IO.Directory.SetCurrentDirectory(FilePath);

                //Add Generated Files to zip
                zip.AddItem("JobFile");
                zip.AddItem("Resources");

                BackgroundWorker worker = sender as BackgroundWorker;
                worker.ReportProgress(20);

                worker.ReportProgress(90);

                //Save the zip
                zip.Save();
                worker.ReportProgress(100);
                zip.Dispose();

                System.IO.Directory.SetCurrentDirectory(current);
                Application.DoEvents();
            }
        }

        private void CompileZipbackgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SubProgressBar1.Value = e.ProgressPercentage;

            HeaderPictureBox3.Image = JobFileInfoSubmiter2.Properties.Resources.Zip_Success_48;
            Sub3.Text = "Completed Successfully!";
            Sub3.ForeColor = Color.Green;
            Sub3.Visible = true;
        }

        private void CompileZipbackgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            string tempFolder = Path.GetTempPath();
            string zipedFile = tempFolder + @"\JobFile.zip";
            if (File.Exists(zipedFile))
            {
                try
                {
                    SubProgressBar1.Style = ProgressBarStyle.Continuous;

                    HeaderPictureBox5.Image = JobFileInfoSubmiter2.Properties.Resources.EMail_Working_48;
                    Header5.Enabled = true;
                    Sub5.Enabled = true;
                    Application.DoEvents();

                    SendMail();

                    HeaderPictureBox5.Image = JobFileInfoSubmiter2.Properties.Resources.EMail_Success_48;
                    Sub5.Text = "Completed Successfully!";
                    Sub5.ForeColor = Color.Green;
                    Sub5.Visible = true;

                    Application.DoEvents();
                }
                catch (Exception ex)
                {
                    HeaderPictureBox5.Image = JobFileInfoSubmiter2.Properties.Resources.EMail_Error_48;
                    Sub5.Text = "Operation Failed!";
                    Sub5.ForeColor = Color.Red;
                    Sub5.Visible = true;

                    ErrorPanel1.Visible = true;
                    ErrorListBox1.Visible = false;
                    ErrorTextBox1.Visible = true;
                    ErrorTextBox1.Text = "Unable to send e-mail." + Environment.NewLine + "" + Environment.NewLine + ex.ToString();
                    Application.DoEvents();

                    return;
                }
            }
        }

Is there something that I'm missing?  If it is the case that this isn't disposing, I'm not sure why that would be.  I have reworked this a bit, and just recently added the check to make sure that the file exists before sending the mail (Apparently it is returning True, as it does then attempt to send the mail message) but it still fails / times out.

If you see anything that might be wrong, or ideas that you might have, I'm all ears.  It could be something silly on my part.  I really appreciate your help.

Thanks Again,

-Matt-

Coordinator
Nov 6, 2009 at 9:28 AM

Hey Matt, glad to help.

I would re-structure the first method a little.

private void CompileZipbackgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    string[] tempDirectory = (string[])e.Argument;
    string FilePath = tempDirectory[0].ToString();

    string current = Directory.GetCurrentDirectory();

    System.IO.Directory.SetCurrentDirectory(FilePath);

    using (ZipFile zip = new ZipFile())
    {
        //Add Generated Files to zip
        zip.AddItem("JobFile");
        zip.AddItem("Resources");

        worker.ReportProgress(20);
        worker.ReportProgress(90);

        zip.Save(Path.Combine(FilePath, "ZipFile.zip"));
    }
    Application.DoEvents();
    worker.ReportProgress(100);
    System.IO.Directory.SetCurrentDirectory(current);
}


The idea there is to change the current directory outside the scope of the using clause, and to re-structure some of the other stuff. It feels more ordered to me that way.  You do not need to explicitly call Dispose() if you have a using clause.

Now, the other thing is that the method that creates the zip file and the method that sends the mail, have entirely different (and separately hardcoded) ways to determine the name of the zip file.  The first one gets the directory from an argument, and then appends the fixed string "ZipFile.zip" to that string.   The second does this:

    string tempFolder = Path.GetTempPath();
    string zipedFile = tempFolder + @"\JobFile.zip";

In other words, it's "hardcoded" that the directory is Path.GetTempPath() and it's hardcoded to use JobFile.zip for the filename.  Why do the same thing different ways?  For one thing, the filename is different - you use ZipFile.zip in the first method and JobFile.zip in the 2nd.  Maybe this is intentional, maybe not.  The other thing is - why pass an argument for the directory to use, in one method, and then in the 2nd method you just have it hardcoded?  ya see what I mean.  The string for the zip file must be the same bewteen these two methods so they should use the same approach to get it.  Either pass it (in its entirety) as an argument in both cases, or generate it in both cases. IF you generate the name for the zip file, then you need to factor the generation code out into a separate method, and call that method from both places.  This avoids the problem of one place using JobFile.zip and the other ZipFile.zip.   Final thing is I recommend you use Path.Combine(directory, shortFileName) to produce filenames, rather than a simple string concatenation.  It more clearly communicates what you're doing.

Last thing I will say - on the progress reports.  Actually depending on the size of the files you are zipping, much of the tme will be spent within the ZipFile.Save()  method itself.  Doing progress reports before and after the save is sort of ... missing the main event, if you know what I mean.  In order to get finer grained progress reports, let's say to power a progressbar in a Winforms app, you would need to register a SaveProgress event on the ZipFile instance.  As the zipfile.Save() method proceeds, it will invoke your SaveProgress event, and from within that event code, you could do progress updates.  Using an anonymous method makes things pretty simple.  I think it might be something like this:

 

private void CompileZipbackgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    string[] tempDirectory = (string[])e.Argument;
    string FilePath = tempDirectory[0].ToString();

    string current = Directory.GetCurrentDirectory();

    System.IO.Directory.SetCurrentDirectory(FilePath);
    int progress = 10;
    using (ZipFile zip = new ZipFile())
    {
        worker.ReportProgress(progress);
        zip.SaveProgress += (sender1, e1) =>
            {
                if (e1.EventType == ZipProgressEventType.Saving_AfterWriteEntry)
                {
                    progress += 25;
                    worker.ReportProgress(progress);
                }
            };
        
        //Add Generated Files to zip
        zip.AddItem("JobFile");
        zip.AddItem("Resources");

        zip.Save(FilePath + @"\ZipFile.zip");
        
        worker.ReportProgress(90);
    }
    Application.DoEvents();
    worker.ReportProgress(100);
    System.IO.Directory.SetCurrentDirectory(current);
}

 

Nov 9, 2009 at 7:28 AM

Hi Cheeso,

Thank you very much for the very detailed post.  I will try to implement the improvements for reporting progress in my applications as that sounds like a good idea.

As for the Dispose(), I didn't think that I needed that with a using statement, but as it was still failing to send the mail, I figured I would at least give that a try.

Also, the difference in name for the zip file was a mistake that I had made in the code that I posted.  I was in the process of renaming the zip file created in my code and copied some new and some old code.  I apologize for that, I can understand the confusion that probably caused.  In my code they are both called JobFile.zip.

I did restructure the code as you suggested and the mailMessage still fails.  At what point is the zip file released after creation?  It appears that when creating a zip file in a backgroundWorker, upon execution of the _completed event the zip is still not released?  I have been reading through the help file and haven't seen any other reference to an issue with disposing the zip file.  I'm fairly certain this is the issue though, as sending a mailMessage with an attachment that exists previously succeeds.  It's only when I create the zip file at the time of sending the mailMessage that this fails.  I tried using my test application (Which sends the zip but doesn't create it) after the one application created the zip file, and until I close the first application that creates the zip file, sending the mail even with the test application fails.

Coordinator
Nov 9, 2009 at 8:01 AM
Edited Nov 11, 2009 at 10:29 AM

The zip file is not closed at the _completed event. 

The zip file is closed when the scope of the using() clause exits - at the end of the close-curly brace. That is when Dispose() is called, and that is when the Close is guaranteed to be called on the zip file.

The test you ran with the test application, which does not succeed until you close the first app, suggests that there is something in your code holding open the file in question. 

I have some other ideas.

First, in the BackgroundWorker_Completed event - do you check for exceptions in the bg worker?  Normally even in the case of exceptions the file should be closed, but it may be that it is not.   The completed event needs to check for e.Error, as showed in the doc by Microsoft:

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if ((e.Cancelled == true))
    {
        this.tbProgress.Text = "Canceled!";
    }

    else if (!(e.Error == null))
    {
        this.tbProgress.Text = ("Error: " + e.Error.Message);
    }

    else
    {
        this.tbProgress.Text = "Done!";
    }
}

 

If an unhandled exception is not the problem, it might be easiest to do a full code review, and track the problem down that way.  Or, simplify your application and get the very simplest case working, then add things until you figure out where the problem is.

If you don't like that, you might want to try ProcMon, which is a free utility from microsoft that lets you monitor access to various OS resources, including files, the registry, and so on.  You can set it up so that it monitors only particular, specified files.  It will tell you when the files are open and when they are closed. 

All this may be the wrong track anyway. Getting back to the original problem, your code is timing out.  What makes you conclude that a timeout in your code implies an unclosed file in other code?  Is the time out occurring in the statement that opens a file?  Normally File.Open() does not have a timeout.  It succeeds, or does not succeed, immediately.  Where's the timeout occurring?  what statement? 

I still feel like I don't understand the base problem we're trying to solve.  A timeout.  Can you be more specific?  Can you get real clear on exactly what the problem is?  

 

Nov 11, 2009 at 9:25 AM

Hi Cheeso,

Thanks for the response.  I will try that tomorrow to try to get this working. 

I did though want to answer your question with regards to why I think this is an issue with the file not disposing.  My send mail code (Which I'll post below) works if the file is not attached.  It works if the zip file is small.  It is only when the file is of a reasonable size (Generally around 1 - 2 MB's) that it fails.  It is not occurring when opening the file, it is occurring when the mail is being sent.  Looking in the debugger it appears to attach the mail message fine.  I did take a very brief run through with Process Monitor (Which looks like a really useful tool.), and I noticed that explorer.exe is creating and closing the zip file a lot after my application has done so.  I will look further into this application tomorrow and see if I can make any sense of what's occurring.  My code (Which you now have all of basically) is only creating the zip file, then attempting to e-mail it.  It isn't opening the zip file, it's not displaying any information relative to the zip file, etc.  So aside from the creation of the zip file, I'm not sure what else could be holding on to the file.

        void SendMail()
        {
            try
            {
                string ccState = "";
                if (CCCheckBox1.Checked == true)
                {
                    ccState = "true";
                }
                else
                {
                    ccState = "false";
                }

                string urgentState = null;
                if (UrgentCheckBox1.Checked == true)
                {
                    urgentState = "true";
                }
                else
                {
                    urgentState = "false";
                }

                string attachState = null;
                if (SubmitStepTB1.Text == "Attach")
                {
                    attachState = "true";
                }
                else
                {
                    attachState = "false";
                }

                string[] SendMail = { CustLastNameTB1.Text, CustFirstNameTB1.Text, UserCB1.Text, UserEMailTB1.Text, AssemblyVersion, JobFileNotesTB1.Text, ccState, urgentState, attachState };
                SendMailbackgroundWorker1.RunWorkerAsync(SendMail);
            }
            catch
            {
                    MessageBox.Show("The Mail Message was unable to be sent (1).", "Error", MessageBoxButtons.OK);
            }
        }

        private void SendMailbackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                string[] SendMail = (string[])e.Argument;
                string CustLastName = SendMail[0].ToString();
                string CustFirstName = SendMail[1].ToString();
                string User = SendMail[2].ToString();
                string UserEMail = SendMail[3].ToString();
                string SoftVersion = SendMail[4].ToString();
                string JobFileNotes = SendMail[5].ToString();
                string CCState = SendMail[6].ToString();
                string UrgentState = SendMail[7].ToString();
                string attachState = SendMail[8].ToString();

                //Send E-Mail on click of Button
                MailMessage theMailMessage = new MailMessage("xxxx", "xxxx");

                BackgroundWorker worker = sender as BackgroundWorker;
                worker.ReportProgress(25);

                //Generate the message body
                string messageBody = "New Job File Information Added For:  " + CustLastName + ", " + CustFirstName;
                messageBody += Environment.NewLine + "";
                messageBody += Environment.NewLine + "From:  " + User;
                messageBody += Environment.NewLine + "E-Mail:  " + UserEMail + "  (CC'd =  " + CCState + ")";
                messageBody += Environment.NewLine + "";
                messageBody += Environment.NewLine + "Software Version:  " + SoftVersion;
                messageBody += Environment.NewLine + "";
                messageBody += Environment.NewLine + "Notes:  " + JobFileNotes;

                worker.ReportProgress(50);

                //Set the property of the message body and subject body
                theMailMessage.Body = messageBody;
                theMailMessage.Subject = "New File for " + CustLastName + ", " + CustFirstName;

                //Set the CC Property
                if (CCState == "true")
                {
                    MailAddress copy = new MailAddress(UserEMail);
                    theMailMessage.CC.Add(copy);
                }

                //Set the Urgent Property
                if (UrgentState == "true")
                {
                    theMailMessage.Priority = MailPriority.High;
                }

                //Set the Attachment Property
                if (attachState == "true")
                {
                    string tempFolder = Path.GetTempPath();
                    theMailMessage.Attachments.Add(new Attachment(tempFolder + @"\JobFile.zip"));
                }

                worker.ReportProgress(75);

                //E-Mail Credentials and Sending
                SmtpClient theClient = new SmtpClient("smtp.1and1.com");
                System.Net.NetworkCredential theCredential = new
                    
                System.Net.NetworkCredential("xxxx", "xxxx");
                theClient.Credentials = theCredential;
                theClient.Send(theMailMessage);  //This is where it times out.

                worker.ReportProgress(100);
            }
            catch
            {
                MessageBox.Show("The Mail Message was unable to be sent (2).", "Error", MessageBoxButtons.OK);
            }
        }

        private void SendMailbackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SubProgressBar1.Value = e.ProgressPercentage;
        }

        private void SendMailbackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }

Coordinator
Nov 11, 2009 at 10:28 AM
Edited Nov 11, 2009 at 10:36 AM

Ok, excellent.  we'll figure it out.  That ProcMon is a nice diagostic tool.  CreateFile means "open file" - in Win32 speak.  It can also mean "create file" but in your case, you know the file exists, so you know CreateFile in ProcMon, on your zip file, is "open file".

If your app is able to attach the message successfully, then it sure looks like it is closed from dotNetZip's perspective.   I don't know exactly what's involved in  attaching a message, but it sounds like you do.  If that bit of logic is able to open the .zip file, then dotnetzip is done with it, does not have it locked.  you are looking for a different culprit.

If Explorer is opening the file, It could be Windows Defender, or some other anti-virus program, scanning the zip.  I don't know how the quarantine works, but in some cases the AV scan will lock the file, preventing it from being read or opened or deleted, while the scan is happening.

Not sure if that would interfere with sending the mail, though.

You might be able to test that by putting a sleep in your app, which waits 10 seconds or so (however long it takes for the Explorer open/close activity to die down in ProcMon) after generating the zip, but before sending the mail.   If you sleep 10 seconds, and then send the mail, does it work properly?

Another test for fun might be to create a zip via the zipit.exe tool that comes with the DotNetZip runtime download.  From your program you could invoke a System.Diagnostics.Process.Start() on that, and generate the zip file that way.  When the process returns, yuou know for sure DotNetZip doesn't have it locked.  Then try to send THAT file.  If you get the same problem then you know it's not a lock being held.  (Alternatively, create another large file, not using ZIP at all.  Maybe just a random stream of bytes for 10mb.  If the same "lock" occurs, then it would implicate something else locking the file).

For now I'll let you figure this out since it does not appear to be a DotNetZip issue.

good luck.