Looking for C# example - Unzip with Progress

Oct 20, 2010 at 4:52 PM

Hi everyone,

I'm trying to find a C# example of Unzipping a file with progress.  I'm not sure which of the ExtractExistingFileAction (Overwrite Silently, or InvokeExtractProgressEvent) I should be using.
I've seen several posts where other developers are using the Library with VB.NET however it's really confusing trying to convert the code to what I would need in C#.  Could someone point me in the right direction please? 

Thanks,
Jim

Coordinator
Oct 24, 2010 at 1:26 PM

Hey Jim,

That's one example I apparently haven't written.  There is an "unzip with progressbar" written in VB.  There is a "create zip with progressbar" written in C#.  But I don't think there is a simple "unzip with progressbar" in C#.  I should write one. I'll put it on my list of things to do.

To answer your questions, ExtractExistingFileAction is a property that specifies what DotNetZip will do when you extract a file that already exists in the filesystem.  This isn't appropriate for use when driving a progress bar.  It's really something for exceptional cases, and it allows your application to handle that exceptional case, flexibly.

What you want is to set the ExtractProgress event.  There are examples in the documentation showing how to use the ExtractProgress event from within C#, and a CONSOLE app.  This is obviously not what you asked for, but it gets you pretty close to what you want.

You could use the same approach as in the console examples, but rather than writing to the console during the progress event, simply update the Value of your progressbar.  That will work but is simplistic.  Here's why: normally in a Winforms app, you want to keep the user interface active, even as you are performing IO, like extracting a compressed zip file.  Some zip files are really big and extracting can take a minute, ten minutes, an hour.  In these cases, you want your application to remain responsive to button clicks, so the user could, for example, cancel the operation if he gets tired of waiting.

Doing DotNetZip in WinForms programming, and one is tempted to put the zipfile extraction logic in the Button_Click event.  But if you do this, the UI will freeze for the entire len gth of time it takes to extract the zip file.  Bad manners.  So what you need to do is use a BackgroundWorker, to perform the possibly lengthy job of extracting the zipfile.  In the Button_Click event, kickoff the BackgroundWorker (if it is not already running, of course!).  In the BG worker code, is where you should do the extraction of the zipfile.

Why am I telling you all this?  Well, if you use an  ExtractProgress event in your extraction code that runs on the context of the background worker, you must not try to update the UI (like a progress bar) from that background thread.  Huh?  In Winforms, only one thread is permitted to update the UI, and when you use a BG worker, that worker logic does not run on the UI thread.  (This is sort of the point, after all.  We want the UI to remain responsive, so we avoid doing IO work on the UI thread).  So what you need to do is call the background worker's ReportProgress method, inside the ExtractProgress event.  Get it?  And update the progressbar in the ReportProgress method.  That insures that the progress  bar gets updated only on the UI thread, as required.

See this article for a writeup of how to do this: http://p3net.mvps.org/Topics/WinForms/BackgroundWorker.aspx  This article doesn't use DotNetZip, but it covers progress bars in a winforms app, and a background worker.

In the meantime I'll work on adding a functional example to DotNetZip that does just what you want.

 

Oct 26, 2010 at 1:15 PM

Thanks for the quick reply Cheeso!  :)   I thought I had something working just before I got your reply but now I think I've gotten off on a tangent and between what I thought I had working and the code from that discussion on the background worker, I'm throughly confused now.
Here is the code I'm currently working with which I am sure I don't need everything in it:

private BackgroundWorker _zipBGWorker;
private delegate void ZipProgressEventHandler(ExtractProgressEventArgs e);
private delegate void ExtractEntryProgressEventHandler(ExtractProgressEventArgs e);

private void DoExtract(string ZipToExtract)
{
 object[] args = new object[3];
 args[0] = ZipToExtract;
 args[1] = ExtractPath;
 _zipBGWorker = new BackgroundWorker();
 _zipBGWorker.WorkerSupportsCancellation = false;
 _zipBGWorker.WorkerReportsProgress = true;
 _zipBGWorker.DoWork += new DoWorkEventHandler(_zipBGWorker_DoWork);
 _zipBGWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_zipBGWorker_RunWorkerCompleted);
 _zipBGWorker.ProgressChanged += new ProgressChangedEventHandler(_zipBGWorker_ProgressChanged);
 _zipBGWorker.RunWorkerAsync(args);
 this.Cursor = Cursors.WaitCursor;
}

void _zipBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
 progressBar1.Value = e.ProgressPercentage;
}

void _zipBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
 this.Close();
}

private void _zipBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
 //bool extractCancelled = false;

 object[] args = (object[])e.Argument;
 var list = args.Cast<string>().ToList();
 string ZipToUnpack = list[0];
 string extractDir = list[1];

 try
 {
  using (ZipFile zip = ZipFile.Read(ZipToUnpack))
  {
   SetProgressBarMax(zip.Entries.Count);
   zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(zip_ExtractProgress);
   zip.ExtractAll(extractDir, ExtractExistingFileAction.OverwriteSilently);
  }
 }
 catch (Exception ex)
 {
  MessageBox.Show(string.Format("There's been a problem extracting that zip file.  {0}", ex.Message),
      "Error Extracting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
 }
}

void zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
{
 progressBar1.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
}

private void SetProgressBarMax(int n)
{
 if (progressBar1.InvokeRequired)
 {
  progressBar1.Invoke(new Action<int>(SetProgressBarMax), new object[] { n });
 }
 else
 {
  progressBar1.Maximum = n;
  progressBar1.Step = 1;
 }
}

private void StepEntryProgress(ExtractProgressEventArgs e)
{

 if (progressBar1.InvokeRequired)
 {
  progressBar1.Invoke(new ExtractEntryProgressEventHandler(this.StepEntryProgress), new object[] { e });
 }
 else
 {
  progressBar1.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
 }
}

Oct 26, 2010 at 6:24 PM
I finally got something working but I feel like I'm missing something or I'm doing more than I need to. Any thoughts?  :)
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Ionic.Zip;
using System.Threading;
using Microsoft.Win32;
using System.IO;
using test.saic.library;
using CTSDBExtractionApp.StringExtentions;


namespace CTSDB
{
    public partial class FormProgress : Form
    {

        private BackgroundWorker _zipBGWorker;
        private delegate void ZipProgressEventHandler(ExtractProgressEventArgs e);
        private delegate void ExtractEntryProgressEventHandler(ExtractProgressEventArgs e);

        public string ExtractPath { get; set; }

        // Disable User Closing Form
        private bool AllowClose = false;

        public FormProgress()
        {
            InitializeComponent();
            SetTitle();
        }

        private void SetTitle()
        {
            this.Text = "CTS Database Decompression";
        }

        private void GetMSSQLPath()
        {
            string sKey = "Software\\Microsoft\\Microsoft SQL Server\\CTSDB\\Setup";
            RegistryKey rk = Registry.LocalMachine;

            try
            {
                RegistryKey sk1 = rk.OpenSubKey(sKey);

                string sVal = (string)sk1.GetValue("SQLPath");
                if (sVal == null)
                {
                    MessageBox.Show("Registry value " + sKey + " not set.",
                                    "CTS Database Extraction", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }

                int iVal;
                bool bConverted = Int32.TryParse(sVal.Right(1), out iVal);
                if (bConverted && iVal == 0)
                {
                    ExtractPath = sVal.Left(sVal.Length - 1) + "\\Data";
                }
                else
                {
                    ExtractPath = sVal + "\\Data";
                }

            }
            catch (Exception e)
            {
                MessageBox.Show("Unable to open key " + sKey + ": " + e.Message,
                                "CTS Database Extraction", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            GetMSSQLPath();
            DoExtract("Cdrsqlbs.zip");
        }

        private void DoExtract(string ZipToExtract)
        {
            object[] args = new object[3];
            args[0] = ZipToExtract;
            args[1] = ExtractPath;
            _zipBGWorker = new BackgroundWorker();
            _zipBGWorker.WorkerSupportsCancellation = true;
            _zipBGWorker.WorkerReportsProgress = false;
            _zipBGWorker.DoWork += new DoWorkEventHandler(_zipBGWorker_DoWork);
            _zipBGWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_zipBGWorker_RunWorkerCompleted);
            _zipBGWorker.RunWorkerAsync(args);
            this.Cursor = Cursors.WaitCursor;
        }

        void _zipBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Close();
        }

        private void _zipBGWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            object[] args = (object[])e.Argument;
            var list = args.Cast<string>().ToList();
            string ZipToUnpack = list[0];
            string extractDir = list[1];

            try
            {
                using (ZipFile zip = ZipFile.Read(ZipToUnpack))
                {
                    SetProgressBarMax(zip.Entries.Count);
                    zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(zip_ExtractProgress);
                    zip.ExtractAll(extractDir, ExtractExistingFileAction.OverwriteSilently);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format("There's been a problem extracting that zip file.  {0}", ex.Message), 
                       "Error Extracting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
            }
        }

        void zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
        {
            if (e.EventType == ZipProgressEventType.Extracting_EntryBytesWritten)
            {
                StepEntryProgress(e);
            }
        }

        private void SetProgressBarMax(int n)
        {
            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new Action<int>(SetProgressBarMax), new object[] { n });
            }
            else
            {
                progressBar1.Maximum = n;
                progressBar1.Step = 1;
            }
        }

        private void StepEntryProgress(ExtractProgressEventArgs e)
        {

            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new ExtractEntryProgressEventHandler(this.StepEntryProgress), new object[] { e });
            }
            else
            {
                progressBar1.Maximum = 100;
                progressBar1.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing && AllowClose)
            {
                e.Cancel = true;
            }
        }

        public void ForceClose()
        {
            AllowClose = true;
            this.Close();
            AllowClose = false;
        }

     }
}
Coordinator
Oct 27, 2010 at 1:23 AM

It looks about right to me.

There are lots of extra pushups required if you want to do asynch work in a Winforms app.

 

Jun 29, 2011 at 10:08 PM

Hello Cheeso,

Guess a little late on this post :)

Is there a way show extract progress in webforms ?

Thanks.

Coordinator
Jul 5, 2011 at 1:52 AM
Edited Jul 5, 2011 at 1:52 AM

Extract progress in webforms - meaning extract on a server and update the browser when the server-side extract is done?

The way I would do it is to poll from browser to server periodically;  The server needs to update a server-wide variable "PercentExtracted" or something like that.  The polling request would then retrieve that number, something between 0 and 100, and display a jQuery progressbar of the appropriate length .This would work only with a long-running extraction, something where the extract would take much much longer than 5 or 6 HTTP requests.  So... like a minute or more. 

If the extraction on the server side takes less than a minute, which will be the case for most zipfiles less than 100mb, then a simple spinning "wait" cursor should be sufficient.  That's my opinion. I'm not a web UI expert, but I do think it's possible to over-engineer things.

And yes, resurrecting a very old post to ask a completely unrelated question is bad manners. Don't do that.  open a new thread.

Nov 8, 2011 at 4:53 PM
Edited Nov 8, 2011 at 4:54 PM

JDM6763 example makes my progress bar jump back and forth when I extract the files, anyone know why? (any help or tip is appricieated)