Unzip with progress - Beginner

Jun 15, 2010 at 11:26 PM

Hi,

I'm trying to extract a zip with a single progress bar that displays the overall process (Rather than individual files - my zips contain a single large file)
I've looked through the examples and documentation but cannot find a working example. The Winforms example doesn't seem to work.

I'm just trying to display a single progress bar with the overall extraction progress. (With a label displaying percentage)

Thanks.

 

 

Jun 15, 2010 at 11:35 PM

Here's the example I'm working with, the progress bar does not work:

Imports System
Imports System.IO
Imports Ionic.Zip

Public Class Form1

    Private Delegate Sub ZipProgress(ByVal e As ZipProgressEventArgs)
    Dim _operationCanceled As Boolean
    Dim nFilesCompleted As Integer
    Dim totalEntriesToProcess As Integer
    Dim _appCuKey As Microsoft.Win32.RegistryKey
    Dim _extractThread As System.Threading.Thread
    Dim AppRegyPath As String = "Software\Ionic\VBunZip"
    Dim rvn_ZipFile As String = "zipfile"
    Dim rvn_ExtractDir As String = "extractdir"

    Private Sub btnZipBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnZipBrowse.Click
        Dim openFileDialog1 As New OpenFileDialog
        If (String.IsNullOrEmpty(tbZipToOpen.Text)) Then
            openFileDialog1.InitialDirectory = "c:\"
        Else
            openFileDialog1.InitialDirectory = IIf(File.Exists(Me.tbZipToOpen.Text), Path.GetDirectoryName(Me.tbZipToOpen.Text), Me.tbZipToOpen.Text)
        End If
        openFileDialog1.Filter = "zip files|*.zip|EXE files|*.exe|All Files|*.*"
        openFileDialog1.FilterIndex = 1
        openFileDialog1.RestoreDirectory = True
        If (openFileDialog1.ShowDialog = DialogResult.OK) Then
            Me.tbZipToOpen.Text = openFileDialog1.FileName
            If File.Exists(Me.tbZipToOpen.Text) Then
                Me.btnUnzip_Click(sender, e)
            End If
        End If
    End Sub


    Private Sub btnUnzip_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnUnzip.Click
        If Not String.IsNullOrEmpty(tbZipToOpen.Text) And _
        File.Exists(Me.tbZipToOpen.Text) And _
        Not String.IsNullOrEmpty(tbExtractDir.Text) And _
        Directory.Exists(tbExtractDir.Text) Then
            btnCancel.Enabled = True
            btnUnzip.Enabled = False
            KickoffExtract()
        End If
    End Sub


    Private Sub KickoffExtract()
        If Not String.IsNullOrEmpty(tbExtractDir.Text) Then
            lblStatus.Text = "Extracting..."
            Dim args(2) As Object
            args(0) = tbZipToOpen.Text
            args(1) = tbExtractDir.Text
            _extractThread = New System.Threading.Thread(New System.Threading.ParameterizedThreadStart(AddressOf UnzipFile))
            _extractThread.Start(args)
            Me.Cursor = Cursors.WaitCursor
        End If
    End Sub



    Private Sub btnExtractDirBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnExtractDirBrowse.Click
        Dim dlg As New FolderBrowserDialog
        dlg.Description = "Select a folder to zip up:"
        dlg.ShowNewFolderButton = False
        'dlg.ShowEditBox = True
        dlg.SelectedPath = Me.tbExtractDir.Text
        'dlg.ShowFullPathInEditBox = True
        If (dlg.ShowDialog = DialogResult.OK) Then
            tbExtractDir.Text = dlg.SelectedPath
        End If
    End Sub


    Private Sub UnzipFile(ByVal args As Object())
        Dim extractCancelled As Boolean = False
        Dim zipToRead As String = args(0)
        Dim extractDir As String = args(1)
        Try
            Using zip As ZipFile = ZipFile.Read(zipToRead)
                totalEntriesToProcess = zip.Entries.Count
                SetProgressBarMax(zip.Entries.Count)
                AddHandler zip.ExtractProgress, New EventHandler(Of ExtractProgressEventArgs)(AddressOf Me.zip_ExtractProgress)
                zip.ExtractAll(extractDir, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently)
            End Using
        Catch ex1 As Exception
            MessageBox.Show(String.Format("There's been a problem extracting that zip file.  {0}", ex1.Message), "Error Extracting", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1)
        End Try
        ResetUI()
    End Sub

    Private Sub ResetUI()
        If btnCancel.InvokeRequired Then
            btnCancel.Invoke(New Action(AddressOf ResetUI), New Object() {})
        Else
            btnUnzip.Enabled = True
            btnCancel.Enabled = False
            ProgressBar1.Maximum = 1
            ProgressBar1.Value = 0
            Me.Cursor = Cursors.Arrow
        End If
    End Sub

    Private Sub SetProgressBarMax(ByVal n As Integer)
        If ProgressBar1.InvokeRequired Then
            ProgressBar1.Invoke(New Action(Of Integer)(AddressOf SetProgressBarMax), New Object() {n})
        Else
            ProgressBar1.Maximum = n
            ProgressBar1.Step = 1
        End If
    End Sub

    Private Sub zip_ExtractProgress(ByVal sender As Object, ByVal e As ExtractProgressEventArgs)
        If (e.EventType = Ionic.Zip.ZipProgressEventType.Extracting_AfterExtractEntry) Then
            StepEntryProgress(e)
        ElseIf (e.EventType = ZipProgressEventType.Extracting_BeforeExtractAll) Then
            'StepArchiveProgress(e)
        End If
    End Sub


    Private Sub StepEntryProgress(ByVal e As ZipProgressEventArgs)
        If ProgressBar1.InvokeRequired Then
            ProgressBar1.Invoke(New ZipProgress(AddressOf StepEntryProgress), New Object() {e})
        ElseIf Not _operationCanceled Then
            ProgressBar1.PerformStep()
            System.Threading.Thread.Sleep(100)
            'set a label with status information
            nFilesCompleted = nFilesCompleted + 1
            lblStatus.Text = String.Format("{0} of {1} files...({2})", nFilesCompleted, totalEntriesToProcess, e.CurrentEntry.FileName)
            Me.Update()
        End If
    End Sub



    'Private Sub StepArchiveProgress(ByVal e As ZipProgressEventArgs)
    '    If ProgressBar1.InvokeRequired Then
    '        ProgressBar1.Invoke(New ZipProgress(AddressOf StepArchiveProgress), New Object() {e})
    '    ElseIf Not _operationCanceled Then
    '        _nFilesCompleted = _nFilesCompleted + 1
    '        ProgressBar1.PerformStep()
    '        progressBar2.Value = progressBar2.Maximum = 1
    '        MyBase.Update()
    '    End If
    'End Sub

    Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        _operationCanceled = True
        ProgressBar1.Maximum = 1
        ProgressBar1.Value = 0
    End Sub


    Private Sub SaveFormToRegistry()
        If AppCuKey IsNot Nothing Then
            If Not String.IsNullOrEmpty(tbZipToOpen.Text) Then
                AppCuKey.SetValue(rvn_ZipFile, Me.tbZipToOpen.Text)
            End If
            If Not String.IsNullOrEmpty(tbExtractDir.Text) Then
                AppCuKey.SetValue(rvn_ExtractDir, tbExtractDir.Text)
            End If
        End If
    End Sub

    Private Sub LoadFormFromRegistry()
        If AppCuKey IsNot Nothing Then
            Dim s As String
            s = AppCuKey.GetValue(rvn_ZipFile)
            If Not String.IsNullOrEmpty(s) Then
                Me.tbZipToOpen.Text = s
            End If
            s = AppCuKey.GetValue(rvn_ExtractDir)
            If Not String.IsNullOrEmpty(s) Then
                tbExtractDir.Text = s
            End If
        End If
    End Sub


    Public ReadOnly Property AppCuKey() As Microsoft.Win32.RegistryKey
        Get
            If (_appCuKey Is Nothing) Then
                Me._appCuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(AppRegyPath, True)
                If (Me._appCuKey Is Nothing) Then
                    Me._appCuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(AppRegyPath)
                End If
            End If
            Return _appCuKey
        End Get
    End Property

    Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        SaveFormToRegistry()
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        LoadFormFromRegistry()
    End Sub
End Class


Coordinator
Jun 16, 2010 at 1:14 AM

I'd suggest putting a breakpoint in the ExtractProgress event, to verify that it is actually being called.

If it IS being called, then you'll need to debug why the code is not working as you expect.

 

 

Jun 16, 2010 at 11:53 AM
Edited Jun 16, 2010 at 11:57 AM

Hi Cheeso, I've simplified an example to try and get this progress working. What value should I use to set the progress bars maximum value?

Imports Ionic.Zip

Public Class Form1

    Dim zipfileName As String = "C:\Test\test.zip"
    Dim extractDirectory As String = "C:\Test\"

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    End Sub

    Private Sub Unzip()
        Dim args(2) As String
        args(0) = Me.zipfileName
        args(1) = Me.extractDirectory
        Dim worker As System.Threading.Thread
        worker = New System.Threading.Thread(New System.Threading.ParameterizedThreadStart(AddressOf UnzipFile))
        worker.Start(args)
    End Sub

    Private Sub UnzipFile(ByVal args As String())
        Try
            Using zip As ZipFile = ZipFile.Read(args(0))
                Me.ProgressBar1.Maximum = ????
                Dim entry As ZipEntry
                For Each entry In zip
                    UpdateUi(entry.FileName)
                    entry.Extract(args(1), ExtractExistingFileAction.OverwriteSilently)
                    '' Sleep a little because it's really fast
                    System.Threading.Thread.Sleep(20)
                Next
                UpdateUi(String.Format("Finished unzipping {0} entries", zip.Entries.Count))
            End Using
        Catch ex1 As Exception
            Me.label1.Text = ("Exception: " & ex1.ToString)
        End Try
    End Sub

    Private Sub UpdateUi(ByVal filename As String)
        If Me.InvokeRequired Then
            '' invoke on the proper thread 
            Me.Invoke(New Action(Of String)(AddressOf UpdateUi), New Object() {filename})
        Else
            Me.label1.Text = filename
            Me.ProgressBar1.PerformStep()
            MyBase.Update()
        End If
    End Sub



    Private Sub BtnUnzip_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnUnzip.Click
        Unzip()
    End Sub
End Class

Jun 16, 2010 at 1:17 PM

It looks like the WinForms-Unzip is broken? (From the Dev kit example)

Theres one progress bar which indicates the progress through the number of files in the zip in steps.
Theres some commented out code that appears to be for the actual progress through each file, I've uncommented this, added a second progress bar to get an understanding of what I want.

Do you have any examples of progress for the whole extraction event rather than stepped individual file extraction? (Similar to what the second progress bar is meant to do but for the whole extraction)

Thanks.

Coordinator
Jun 16, 2010 at 5:37 PM

> It looks like the WinForms-Unzip is broken? (From the Dev kit example)

In what way is it broken?  Does it simply not work?  Does it not compile?  If you provide a clear description I'll file a workitem, then investigate it and fix as appropriate.

> Do you have any examples of progress for the whole extraction event rather than stepped individual file extraction? (Similar to what the second progress bar is meant to do but for the whole extraction)

Hmm,  by "the whole extraction event", I guess you want the progress through the aggregate size of the extracted files, is that right?   Here's the thing - there's no general way for the library to know how many entries you are extracting, and therefore there's no way for the library to know the aggregate size of the intended extracted entries. In the specific case of an ExtractAll(), theoretically the library would be able to calculate the aggregate extracted size, before extraction occurred.  It could then present, in the extraction progress event, the "total size" and the "size extracted so far", via properties in the custom EventArgs.  Theoretically the library could do this, but I don't believe it does so, today.  I'm not able to look at the code, so I can't recall if that is true.

In any case that would work only for the ExtractAll() case.  If an application extracts anything other than "every entry" , then the library cannot know how many entries will be extracted, nor the aggregate size of those extracted entries. 

But, thyour application could pretty simply drive the kind of progress bar you want.  To do it, compute the total expected size of the entries to be extracted (by summing ZipEntry.UncompressedSize across all those entries), before beginning the extraction. Then keep a running total of the "extracted so far" size within the ExtractProgress event, and update your progress bar appropriately.   

 

Jun 16, 2010 at 6:03 PM
Hi Cheeso, The example has a single progress bar which does nothing. The label (lblStatus) updates with the current extraction item and total ok. Thanks.
Coordinator
Jun 16, 2010 at 6:32 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Jun 16, 2010 at 8:21 PM
Edited Jun 16, 2010 at 10:41 PM

I see, that solution could work.
For me it's unlikely a zip would contain more than one file (Will contain a single zipped up sql .bak backup)

So I could get away with just:

Me.ProgressBar1.Value = 100 * e.BytesTransferred / e.TotalBytesToTransfer

Although I would be interested in implementing a solution that can handle multiple files.
I think I've managed to get the sum of ZipEntry.UncompressedSize, how do I pass this to the event handler?:

Imports Ionic.Zip
Imports System.Threading
Imports System.ComponentModel

Public Class Form1
    Dim TotalSize As Long ' Bits
    Dim ZipUncompressedSize As Long
    Dim ZipUncompressedSize2 As Long
    Private _zipBGWorker As System.ComponentModel.BackgroundWorker
    Private Delegate Sub ZipProgress(ByVal e As ExtractProgressEventArgs)
    Private Delegate Sub ExtractEntryProgress(ByVal e As ExtractProgressEventArgs)
    Private Sub BtnUnzip_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnUnzip.Click
        MyExtract()
    End Sub

    Private Sub MyExtract()
        Dim args(2) As String
        args(0) = "C:\Test\Test2.zip"
        args(1) = "C:\Test\ZipTestExtract\"
        _zipBGWorker = New System.ComponentModel.BackgroundWorker()
        _zipBGWorker.WorkerSupportsCancellation = False
        _zipBGWorker.WorkerReportsProgress = False
        AddHandler Me._zipBGWorker.DoWork, New DoWorkEventHandler(AddressOf Me.UnzipFile)
        _zipBGWorker.RunWorkerAsync(args)

    End Sub

    Private Sub UnzipFile(ByVal sender As Object, ByVal e As DoWorkEventArgs)
        Dim extractCancelled As Boolean = False
        Dim args() As String = e.Argument
        Dim ZipToUnpack As String = args(0)
        Dim extractDir As String = args(1)
        
        Using zip As ZipFile = ZipFile.Read(ZipToUnpack)
            AddHandler zip.ExtractProgress, New EventHandler(Of ExtractProgressEventArgs)(AddressOf Me.zip_ExtractProgress)
            For Each backup In zip
                TotalSize = backup.UncompressedSize + TotalSize
            Next
            For Each backup In zip
                backup.Extract(extractDir, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently)
             Next
        End Using
    End Sub

    Private Sub zip_ExtractProgress(ByVal sender As Object, ByVal e As ExtractProgressEventArgs)

        If (e.EventType = ZipProgressEventType.Extracting_AfterExtractEntry) Then
            ZipUncompressedSize2 = ZipUncompressedSize + ZipUncompressedSize2
        ElseIf (e.EventType = ZipProgressEventType.Extracting_EntryBytesWritten) Then
            StepEntryProgress(e)

            ZipUncompressedSize = e.BytesTransferred + ZipUncompressedSize2

        End If
    End Sub

    Private Sub StepEntryProgress(ByVal e As ExtractProgressEventArgs)


        If Me.ProgressBar1.InvokeRequired Then
            Me.ProgressBar1.Invoke(New ExtractEntryProgress(AddressOf Me.StepEntryProgress), New Object() {e})
        Else

            Me.ProgressBar1.Maximum = 100
            Me.ProgressBar1.Value = 100 * ZipUncompressedSize / TotalSize
            Me.LblStatus.Text = CStr(100 * (ZipUncompressedSize / TotalSize).ToString("#,##0.00")) & "% Complete"

        End If
    End Sub


  
End Class



 

Thank you for your help.
Coordinator
Jun 16, 2010 at 9:47 PM

if you are concerned about extracting a single entry, then you don't need any of what I described. the EventArgs contains a reference to the ZipEntry, from which you can get UncompressedSize.

In the general case, the easiest way to "pass" the aggregate uncompressed size to the event handler would be to use an instance variable on your Form or Window to store that quantity.

 

Jun 16, 2010 at 10:39 PM

Hi Cheeso,

For the moment that's fine but I can see this ability being useful in the future.
I've noticed the total bytes from BytesTransferred is slightly wrong,

Windows & ZipEntry.UncompressedSize give 305150464 (Total of 3 files in zip)
BytesTransfeered gives 361510400.

I've updated the above code with what I'm currently using to get these figures.

Thanks.

 

 

 

Coordinator
Jun 16, 2010 at 10:57 PM

if you have a smaller archive that exhibits the discrepancy, I'd like to have a look at it.

 

Jun 16, 2010 at 10:58 PM
Sure, I will create an example now.
Jun 17, 2010 at 12:56 AM
Edited Jun 17, 2010 at 12:58 AM

I've tried with several zips containing different file types but cannot reproduce this...


I'm having problems keeping a runnig total of BytesTransferred:
http://www.daniweb.com/forums/thread290601.html

Values after extracting 3 files:
TotalSize = (UncompressedSize - Global) 305150464 (Which is correct for the total size of all files)
TransferTotal (Global Long) = 2447309139968 (I expected it to total TotalSize?)

Values after extracting first file:
BytesToTransfer = 56378880 for the first file (Which is correct)
TransferTotal (Global) = 97109969920 in relation to the above value... Not sure how it's getting this figure... Should be the same?


I have "TranferTotal = e.BytesTransferred + TranferTotal" on event "ZipProgressEventType.Extracting_EntryBytesWritten"
To increment TransferTotal between extracted files.

Thanks.

Coordinator
Jun 17, 2010 at 2:33 PM

Right, it sounds like you're having some problems with the logic in your code.

ExtractProgressEventArgs.BytesTransferred  is the running total of bytes transferred for the current entry being extracted.  Suppose you have an entry that is 10,000 bytes.  On successive invocations of the ExtractProgress event, e.BytesTransferrred might progress through 1000, 2000, 3000, and so on, up to 10,0000.

You are accumulating this quantity, which is not appropriate.  It is already an accumulated value. 
Therefore you'll need to use different arithmetic to accomplish what you want.

Jun 17, 2010 at 4:09 PM

Ahh, I see.

I've added "TransferTotal = TransferTotal + backup.UncompressedSize" to the extaction loop which works great.
Is there an extraction completion event I can use to update labels etc? For extracting I only see AfterExtractAll?

Thanks.

Coordinator
Jun 17, 2010 at 5:46 PM

Ah - when your app extracts entries in a loop, there's no way for the library to know when your app is done extracting.  So... the way to update the labels when the extraction is done is to do so upon exit from your application's loop.

ExtractAll is a special case - obviously at the end of that method it's possible to invoke an event, and AfterExtractAll is that event.  

I think it would be worthwhile to provide a similar event after ExtractSelectedItems() but... today I don't believe that there is such an event available.