Click here to Skip to main content
15,886,753 members
Articles / Programming Languages / Visual Basic 10

SharpZipLib or DotNetZip... Which Should You Use?

Rate me:
Please Sign up or sign in to vote.
4.54/5 (8 votes)
5 Aug 2012CPOL9 min read 102.6K   3.3K   48  
A comparison of these two free zip libraries, complete with a multi-threaded VB.NET class wrapper for each
Imports System.IO
Imports System.IO.Compression
Imports System.Threading
Imports ICSharpCode.SharpZipLib.Checksums
Imports ICSharpCode.SharpZipLib.Core
Imports ICSharpCode.SharpZipLib.Zip
Imports System.Text
Imports System.Runtime.InteropServices
Imports ICSharpCode.SharpZipLib.Zip.Compression.Streams
Imports Ionic.Zip

' To use this class, you must include a reference to ICSharpCode.SharpZipLib.dll

Public Class clsCompareLibs


    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '''''''''''''''''''''''''''''''''''''''''''''''' Fast File system tools ''''''''''''''''''''''''''''''''''''''''''''''''''''
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

    Public Class FastFileSystemTools

        Public Const FILE_ATTRIBUTE_DIRECTORY As Integer = &H10

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto)> _
        Public Shared Function FindFirstFile(ByVal lpFileName As String, ByRef lpFindFileData As WIN32_FIND_DATA) As IntPtr
        End Function

        <DllImport("kernel32.dll")> _
        Public Shared Function FindClose(ByVal hFindFile As IntPtr) As Boolean
        End Function

        <DllImport("kernel32.dll", CharSet:=CharSet.Auto)> _
        Public Shared Function FindNextFile(ByVal hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean
        End Function

        'Public Declare Function FindNextFile Lib "kernel32" Alias "FindNextFileA" (ByVal hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean

        ' The CharSet must match the CharSet of the corresponding PInvoke signature
        ' Get file size like this: 
        ' Dim FileSize As Long = ((nFileSizeHigh << 32&) Or nFileSizeLow)
        ' .dwFileAttributes = 16 is a directory. > 16 is a file.
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
        Public Structure WIN32_FIND_DATA
            Public dwFileAttributes As UInteger
            Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
            Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
            Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
            Public nFileSizeHigh As UInteger
            Public nFileSizeLow As UInteger
            Public dwReserved0 As UInteger
            Public dwReserved1 As UInteger
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> Public cFileName As String
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=14)> Public cAlternateFileName As String
        End Structure

        Public Class ShortFileInfo
            Public name As String
            Public length As Int64
            Public attributes As UInteger
            Public xtraData As Int32

            Public Sub New(ByVal fileName As String, ByVal fileSize As Int64, ByVal attribs As UInteger)
                name = fileName
                length = fileSize
                attributes = attribs
            End Sub

            Public Sub New(ByVal fileName As String)
                name = fileName
                length = FastFileSize(fileName, attributes)
            End Sub
        End Class

        Public Shared Function FastFileSize(ByVal filePath As String, Optional ByRef attribs As UInteger = 0) As Int64
            Dim attributes As New WIN32_FIND_DATA

            Try
                Dim handle As IntPtr = FindFirstFile(filePath, attributes)
                attribs = attributes.dwFileAttributes
                FindClose(handle)
                Return ((attributes.nFileSizeHigh << 32&) Or attributes.nFileSizeLow)
            Catch ex As Exception
                Return -1
            End Try
        End Function

        Public Function GetFoldersInFolder(ByVal path As String) As List(Of String)

            Dim attributes As New WIN32_FIND_DATA
            Dim pathList As New List(Of String)
            Dim handle As IntPtr
            Dim searchPath As String
            Dim builder As New StringBuilder(path)

            If path.Substring(path.Length - 1, 1) <> "\" Then
                builder.Append("\")
            End If

            path = builder.ToString()

            builder.Append("*")
            searchPath = builder.ToString()

            handle = FindFirstFile(searchPath, attributes)
            If CBool(attributes.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) Then
                ' It's a folder...
                builder.Remove(0, builder.Length)
                builder.Append(path)
                builder.Append(attributes.cFileName)
                pathList.Add(builder.ToString())
            Else
                ' It's a file...
            End If

            While FindNextFile(handle, attributes)
                If CBool(attributes.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) Then
                    ' Its a folder...
                    builder.Remove(0, builder.Length)
                    builder.Append(path)
                    builder.Append(attributes.cFileName)
                    pathList.Add(builder.ToString())
                Else
                    ' It's a file...
                End If
            End While

            FindClose(handle)

            Return pathList
        End Function

        Public Function GetFilesInFolder(ByVal path As String, _
                                         Optional ByVal recourseSubdirectories As Boolean = False, _
                                         Optional ByRef folderSize As Int64 = 0) As List(Of ShortFileInfo)

            Dim attributes As New WIN32_FIND_DATA
            Dim filesList As New List(Of ShortFileInfo)
            Dim foldersList As New List(Of String)
            Dim tmp As New List(Of ShortFileInfo)
            Dim handle As IntPtr
            Dim searchPath As String
            Dim builder As New StringBuilder(path)
            Dim fileSize As Int64 = 0

            If path.Substring(path.Length - 1, 1) <> "\" Then
                builder.Append("\")
            End If

            path = builder.ToString()

            builder.Append("*")
            searchPath = builder.ToString()

            handle = FindFirstFile(searchPath, attributes)
            If CBool(attributes.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) Then
                ' It's a folder...
                If attributes.cFileName <> "." AndAlso attributes.cFileName <> ".." Then
                    foldersList.Add(attributes.cFileName)
                End If
            Else
                ' Add if it's a file...
                builder.Remove(0, builder.Length)
                builder.Append(path)
                builder.Append(attributes.cFileName)
                fileSize = ((attributes.nFileSizeHigh << 32&) Or attributes.nFileSizeLow)
                folderSize += fileSize
                filesList.Add(New ShortFileInfo(builder.ToString(), fileSize, _
                             attributes.dwFileAttributes))
            End If

            While FindNextFile(handle, attributes)
                If CBool(attributes.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) Then
                    ' Its a folder...
                    If attributes.cFileName <> "." AndAlso attributes.cFileName <> ".." Then
                        foldersList.Add(attributes.cFileName)
                    End If
                Else
                    ' Add if it's a file...
                    builder.Remove(0, builder.Length)
                    builder.Append(path)
                    builder.Append(attributes.cFileName)
                    fileSize = ((attributes.nFileSizeHigh << 32&) Or attributes.nFileSizeLow)
                    folderSize += fileSize
                    filesList.Add(New ShortFileInfo(builder.ToString(), fileSize, _
                                 attributes.dwFileAttributes))
                End If
            End While

            FindClose(handle)

            If recourseSubdirectories Then
                If foldersList.Count > 0 Then
                    For Each folder As String In foldersList
                        builder.Remove(0, builder.Length)
                        builder.Append(path)
                        builder.Append(folder)
                        tmp = GetFilesInFolder(builder.ToString(), True, folderSize)
                        If tmp.Count > 0 Then filesList.AddRange(tmp)
                    Next
                End If
            End If

            Return filesList
        End Function

    End Class

    Public Class ZipData
        Public fileList As List(Of ShortEntry)
        Public totalBytes As Int64
        Public totalBytesCopied As Int64
        Public currentFileLength As Int64
        Public currentFileBytesCopied As Int64
        Public currentFileName As String
        Public cancel As Boolean
        Public complete As Boolean
        Public operationTitle As String
        Public errorMessage As String
    End Class

    Public Delegate Sub ResultCallback(ByRef ZipData)

    Private zipfilePath As String
    Private errorMessage As String
    Private accessFlags As ZipAccessFlags
    Private theZipInputStream As ICSharpCode.SharpZipLib.Zip.ZipInputStream
    Private theZipOutputStream As ICSharpCode.SharpZipLib.Zip.ZipOutputStream
    Private ionicZip As Ionic.zip.ZipOutputStream
    Private buff() As Byte
    Private updateInterval As Int32
    Private callBack As ResultCallback
    Private running As Boolean
    Private updateSyncObject As Object
    Private fileList As List(Of ShortEntry)
    Private totalBytes As Int64
    Private totalBytesCopied As Int64
    Private currentFileLength As Int64
    Private currentFileBytesCopied As Int64
    Private currentFileName As String
    Private operationTitle As String
    Private cancelOperation As Boolean
    Private useDotNetZip As Boolean
    Private compressionLevel As Integer
    Private useZip64 As Boolean
    Private password As String

    Private inputStream As FileStream
    Private outPutStream As FileStream

    Public Enum ZipAccessFlags
        Read
        Write
        Create ' create and write.
    End Enum

    Private Structure AddFileListComm
        Public fileList As List(Of String)
        Public recourseSubdirectories As Boolean
        Public setIsRunningFlag As Boolean
    End Structure

    Public Structure ShortEntry
        Public name As String
        Public size As Int64
        Public index As Integer
    End Structure

    Private Structure MultiExtractComm
        Public entries As List(Of ShortEntry)
        Public targetFiolder As String
        Public setIsRunningFlag As Boolean
        Public justNames As Boolean
    End Structure

    Public ReadOnly Property GetErrorMessage() As String
        Get
            Return errorMessage
        End Get
    End Property

    Public Sub Cancel()
        cancelOperation = True
    End Sub

    Public ReadOnly Property IsRunning()
        Get
            Return running
        End Get
    End Property

    Public Sub New(ByVal _zipFilePath As String, _accessflags As ZipAccessFlags, _
                        Optional bufferSize As Integer = (1024 * 256), _
                        Optional ByVal _useDotNetZip As Boolean = False, _
                        Optional ByVal _compressionLevel_0_to_9 As Integer = 0, _
                        Optional ByVal _useZip64 As Boolean = False, _
                        Optional ByVal _password As String = "", _
                        Optional ByVal _updateIntervalMilliseconds As Int32 = 100, _
                        Optional ByRef _callback As ResultCallback = Nothing)

        If _compressionLevel_0_to_9 < 0 then _compressionLevel_0_to_9 = 0
        If _compressionLevel_0_to_9 > 9 then _compressionLevel_0_to_9 = 9

        accessFlags         = _accessflags
        zipfilePath         = _zipFilePath
        callBack            = _callback
        updateInterval      = _updateIntervalMilliseconds
        running             = False
        updateSyncObject    = New Object
        cancelOperation     = False
        useDotNetZip        = _useDotNetZip
        compressionLevel    = _compressionLevel_0_to_9
        password            = _password
        useZip64            = _useZip64

        ReDim buff(bufferSize)
        Dim mode As FileMode = FileMode.Open
        Dim options As FileOptions = FileOptions.SequentialScan

        'If unbuffered Then options = FileOptions.WriteThrough Or FileOptions.SequentialScan

        ' No error handling - we want any exceptions thrown.
        If accessFlags = ZipAccessFlags.Read Then
            inputStream = New FileStream(zipfilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.SequentialScan)
            theZipInputStream = New ICSharpCode.SharpZipLib.Zip.ZipInputStream(inputStream)
            theZipInputStream.IsStreamOwner = True
            Exit Sub
        End If

        If accessFlags = ZipAccessFlags.Create Then
            mode = FileMode.Create
            accessFlags = ZipAccessFlags.Write
        End If

        If accessFlags = ZipAccessFlags.Write Then
            outPutStream = New FileStream(zipfilePath, mode, FileAccess.Write, FileShare.None, 4096, options)
            Exit Sub
        End If

    End Sub

    Private Sub Monitor()

        Dim updateData As New ZipData
        updateData.cancel = False

        Do While running
            Thread.Sleep(updateInterval)

            With updateData
                SyncLock updateSyncObject
                    .totalBytes = totalBytes
                    .totalBytesCopied = totalBytesCopied
                    .currentFileLength = currentFileLength
                    .currentFileBytesCopied = currentFileBytesCopied
                    .fileList = fileList
                    .currentFileName = currentFileName
                    .operationTitle = operationTitle
                    .errorMessage = errorMessage
                    .complete = False
                    .cancel = cancelOperation
                End SyncLock
            End With

            If callBack IsNot Nothing Then callBack(updateData)
            If Not cancelOperation AndAlso updateData.cancel Then cancelOperation = True
        Loop

        With updateData
            SyncLock updateSyncObject
                .totalBytes = totalBytes
                .totalBytesCopied = totalBytesCopied
                .currentFileLength = currentFileLength
                .currentFileBytesCopied = currentFileBytesCopied
                .fileList = fileList
                .currentFileName = currentFileName
                .operationTitle = operationTitle
                .errorMessage = errorMessage
                .complete = True
                .cancel = cancelOperation
            End SyncLock
        End With

        If callBack IsNot Nothing Then callBack(updateData)
    End Sub

    Public Sub Close()
        If accessFlags = ZipAccessFlags.Read Then
            Try
                theZipInputStream.Close()
            Catch ex As Exception
            End Try
        End If

        If accessFlags = ZipAccessFlags.Write Then
            Try
                'theZipOutputStream.Finish()
            Catch ex As Exception
            End Try
            Try
                theZipOutputStream.Close()
            Catch ex As Exception
            End Try
        End If

        buff = Nothing

        GC.Collect()
        GC.GetTotalMemory(True)
    End Sub

    Public Function GetZipEntries() As Boolean
        If accessFlags = ZipAccessFlags.Write Then Return False
        If running Then
            errorMessage = "An operation is already running."
            Return False
        End If

        running = True
        Dim getZipEntriesbgWorker As New Thread(AddressOf GetZipEntriesBackgroundWorker) With { _
            .IsBackground = True
        }
        getZipEntriesbgWorker.Start()

        Return True
    End Function

    Private Sub GetZipEntriesBackgroundWorker()

        Dim index As Integer        = 0
        Dim callBackData As New ZipData With { _
            .fileList               = New List(Of ShortEntry),
            .currentFileName        = "",
            .totalBytes             = -1,
            .totalBytesCopied       = -1,
            .currentFileLength      = -1,
            .currentFileBytesCopied = -1,
            .operationTitle         = "Listing zip entries..."
        }

        callBack(callBackData) 


        Try 
            If useDotNetZip Then
                Using zip As ionic.zip.ZipFile = New ionic.zip.ZipFile(inputStream.Name)
                    For Each entry as ionic.zip.ZipEntry In zip.Entries
                        callBackData.fileList.Add(New ShortEntry With { _
                            .name       = entry.FileName,
                            .size       = entry.UncompressedSize,
                            .index      = index
                        })
                        index           += 1
                    Next
                End Using
            Else
                Using zip As New ICSharpCode.SharpZipLib.Zip.ZipFile(inputStream)
                    Dim entry As ICSharpCode.SharpZipLib.Zip.ZipEntry
                    For count As Integer = 0 To zip.Count - 1
                        entry           = zip.EntryByIndex(count)
                        callBackData.fileList.Add(New ShortEntry With { _
                            .name       = entry.Name,
                            .size       = entry.Size,
                            .index      = count
                        })
                    Next
                End Using
            End If
        Catch ex As Exception
            errorMessage = "Could not get entry list. The error returned is: " & ex.Message
            callBackData.errorMessage = errorMessage
        End Try

        callBackData.operationTitle     = "Complete."
        If callBackData.fileList.Count < 1 AndAlso errorMessage = "" Then 
            callBackData.fileList.Add(New ShortEntry With { _
                        .name       = "This is an empty zip file.",
                        .size       = 0,
                        .index      = -1
                    })
        End If

        callBack(callBackData)
        running = False
    End Sub

    Public Function Extract(ByVal zippedEntryName As String, _
                            ByVal targetFolder As String) As Boolean
        If accessFlags = ZipAccessFlags.Write Then Return False

        Dim entry As New List(Of ShortEntry)
        entry.Add(New ShortEntry With { _
             .name = zippedEntryName
        })

        running = True
        Dim extractFileBgWorker As New Thread(AddressOf ExtractFileBackgroundWorker)
        extractFileBgWorker.Start(New MultiExtractComm With { _
            .entries = entry,
            .targetFiolder = targetFolder,
            .setIsRunningFlag = True,
            .justNames = True
        })

        operationTitle = "Extracting"

        Dim monitorThread As New Thread(AddressOf Monitor) With { _
            .IsBackground = True,
            .Name = "Zip Monitor"
        }
        monitorThread.Start()

        Return True
    End Function

    Public Function Extract(ByVal zippedEntryNames As list(Of String), _
                       ByVal targetFolder As String)
        If accessFlags = ZipAccessFlags.Write Then Return False

        Dim entries As New List(Of ShortEntry)
        For Each name As String In zippedEntryNames
            entries.Add(New ShortEntry With { _
                .name = name
            })
        Next

        running = True
        Dim extractFilesBgWorker As New Thread(AddressOf ExtractFileBackgroundWorker)
        extractFilesBgWorker.Start(New MultiExtractComm With { _
            .entries = entries,
            .targetFiolder = targetFolder,
            .setIsRunningFlag = True,
            .justNames = True
        })

        operationTitle = "Extracting"

        Dim monitorThread As New Thread(AddressOf Monitor) With { _
            .IsBackground = True,
            .Name = "Zip Monitor"
        }
        monitorThread.Start()

        Return True
    End Function

    Public Function Extract(ByVal zippedEntryNames As List(Of ShortEntry), _
                       ByVal targetFolder As String)
        If accessFlags = ZipAccessFlags.Write Then Return False

        running = True
        Dim extractFilesBgWorker As New Thread(AddressOf ExtractFileBackgroundWorker)
        extractFilesBgWorker.Start(New MultiExtractComm With { _
            .entries = zippedEntryNames,
            .targetFiolder = targetFolder,
            .setIsRunningFlag = True
        })

        operationTitle = "Extracting"

        Dim monitorThread As New Thread(AddressOf Monitor) With { _
            .IsBackground = True,
            .Name = "Zip Monitor"
        }
        monitorThread.Start()

        Return True
    End Function

    Private Sub ExtractFileBackgroundWorker(ByVal _comm As Object)
        Dim comm As MultiExtractComm = DirectCast(_comm, MultiExtractComm)

        Dim bytesRead As Long
        Dim targetPath As String
        Dim zippedEntries As New List(Of ShortEntry)
        Dim tmpName As String
        Dim tmpSize As Int64
        Dim entryCollection As New List(Of Integer)
        Dim index As Integer = 0

        zippedEntries.AddRange(comm.entries)
        targetPath = comm.targetFiolder

        totalBytesCopied = 0
        currentFileBytesCopied = 0
        totalBytes = 0

        currentFileName = ""
        operationTitle = "Calculating total job size..."

        If useDotNetZip Then

            Dim Overwrite As ExtractExistingFileAction = ExtractExistingFileAction.OverwriteSilently
            Dim ZipToUnpack As String = inputStream.Name
            Dim UnpackDirectory As String = comm.targetFiolder
            Using zip As ionic.zip.ZipFile = ionic.zip.ZipFile.Read(ZipToUnpack)
                zip.BufferSize = 1024 * 128
                AddHandler zip.ExtractProgress, AddressOf ExtractFileProgressCallback

                Dim entriesInTheFile As New List(Of ionic.zip.ZipEntry)
                entriesInTheFile.AddRange(zip.Entries)

                If comm.justNames Then
                    ' They only passed us names, as string. We need to 
                    ' create a collection of all the items to be extracted,
                    ' and add up the file sizes.
                    For count As Integer = 0 to zip.Entries.Count -1
                        With entriesInTheFile.Item(count)
                            For Each fileToExtract As ShortEntry In zippedEntries
                                If fileToExtract.name = .FileName Then
                                    'entryCollection.Add(count)
                                    fileToExtract.size   = .UncompressedSize
                                    fileToExtract.index  = count
                                    totalBytes           += .UncompressedSize
                                End If
                            Next
                        End With
                    Next
                Else
                    ' They gave us ShortEntries - we have the indexes of 
                    ' all the files to be restored, and the file sizes.
                    For Each entry As ShortEntry In zippedEntries
                        entryCollection.Add(entry.index)
                        totalBytes += entry.size
                    Next
                End If

                operationTitle = "Extracting with DotNetZip"

                ' here, we extract every entry, but we could extract conditionally,
                ' based on entry name, size, date, checkbox status, etc.   
                For Each entry As ionic.zip.ZipEntry In zip
                    entry.Password = password
                    If entry.FileName = zippedEntries.Item(0).name then
                        Try 
                            entry.Extract(UnpackDirectory, Overwrite)
                        Catch ex As Exception
                            errorMessage = ex.Message
                            Exit For
                        End Try
                        
                        zippedEntries.RemoveAt(0)
                    End If

                    If zippedEntries.Count = 0 then Exit For
                    If cancelOperation Then Exit For
                Next
            End Using

            If Not cancelOperation Then _
                currentFileBytesCopied  = currentFileLength

            If comm.setIsRunningFlag Then running = False
        Else
            ' #ZipLib code...
            Dim zip As New ICSharpCode.SharpZipLib.Zip.ZipFile(inputStream)
            Dim longEntry As ICSharpCode.SharpZipLib.Zip.ZipEntry
            If useZip64 Then zip.UseZip64 = True

            If comm.justNames Then
                ' They only passed us names, as string. We need to 
                ' create a collection of all the items to be extracted,
                ' and add up the file sizes.
                For count = 0 To zip.Count - 1
                    With zip.EntryByIndex(count)
                        For Each entry As ShortEntry In zippedEntries
                            If entry.name = .Name Then
                                entryCollection.Add(count)
                                totalBytes += .Size
                            End If
                        Next
                    End With
                Next
            Else
                ' They gave us ShortEntries - we have the indexes of 
                ' all the files to be restored, and the file sizes.
                For Each entry As ShortEntry In zippedEntries
                    entryCollection.Add(entry.index)
                    totalBytes += entry.size
                Next
            End If

            operationTitle = "Extracting with #ZipLib"

            If entryCollection.Count < 1 Then
                operationTitle = "Complete."
                If comm.setIsRunningFlag Then running = False
                Exit Sub
            End If

            For count = 0 To entryCollection.Count - 1
                If entryCollection.Item(count) < 0 Then Continue For
                longEntry = zip.EntryByIndex(entryCollection.Item(count))

                With longEntry
                    tmpName = .Name
                    tmpSize = .Size
                End With

                Try
                    targetPath = comm.targetFiolder & "\" & tmpName ' theZipEntry.Name
                    targetPath = targetPath.Replace("/", "\")

                    currentFileBytesCopied = 0
                    currentFileLength = tmpSize
                    currentFileName = tmpName

                    If CreateFolders(targetPath) = False Then Throw New  _
                                    Exception("Could not create folders for " & targetPath)
                    Try
                        If File.Exists(targetPath) Then
                            File.SetAttributes(targetPath, IO.FileAttributes.Normal)
                            File.Delete(targetPath)
                        End If
                    Catch ex As Exception
                        Throw New Exception(ex.Message)
                    End Try

                    zip.Password = password

                    Using writer As FileStream = New FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.SequentialScan)
                        If currentFileLength > 0 Then
                            Using localInputStream As Stream = zip.GetInputStream(longEntry)
                                bytesRead = 1
                                Do Until (bytesRead <= 0)
                                    bytesRead = localInputStream.Read(buff, 0, buff.Length)
                                    writer.Write(buff, 0, bytesRead)

                                    currentFileBytesCopied += bytesRead
                                    totalBytesCopied += bytesRead

                                    If cancelOperation Then Exit For
                                Loop
                            End Using
                        End If
                    End Using

                Catch ex As Exception
                    ' Catch these "Unexpected EOF" errors, and continue
                    If ex.message = "Unexpected EOF" Then 
                        ' Do nothing here...
                    Else
                        errorMessage = "Error: Could not create " & targetPath & ": " & ex.Message
                        If comm.setIsRunningFlag Then running = False
                        Exit Sub
                    End If
                End Try
            Next
        End If
        
        If comm.setIsRunningFlag Then running = False
    End Sub

    Public Function Add(ByVal fileOrFolderPath As String, _
                        Optional ByVal recourseSubDirectories As Boolean = True) As Boolean
        If accessFlags = ZipAccessFlags.Read Then Return False
        Dim thisFolder As New List(Of String)

        thisFolder.Add(fileOrFolderPath)
        If Directory.Exists(fileOrFolderPath) Or File.Exists(fileOrFolderPath) Then
            running = True
            Dim addFolderBgWorker As New Thread(AddressOf AddFileListBackgroundWorker)
            addFolderBgWorker.Start(New AddFileListComm With { _
                .fileList = thisFolder,
                .recourseSubdirectories = recourseSubDirectories,
                .setIsRunningFlag = True
            })
        Else
            errorMessage = "" & fileOrFolderPath & " can not be found."
        End If

        operationTitle = "Zipping"

        Dim monitorThread As New Thread(AddressOf Monitor) With { _
            .IsBackground = True,
            .Name = "Zip Monitor"
        }
        monitorThread.Start()

        Return True
    End Function

    Public Function Add(ByVal filesAndFolders As List(Of String), _
                        Optional ByVal recourseSubDirectories As Boolean = True) As Boolean
        If accessFlags = ZipAccessFlags.Read Then Return False

        running = True
        Dim addFolderBgWorker As New Thread(AddressOf AddFileListBackgroundWorker)
        addFolderBgWorker.Start(New AddFileListComm With { _
            .fileList = filesAndFolders,
            .recourseSubdirectories = recourseSubDirectories,
            .setIsRunningFlag = True
        })

        operationTitle = "Zipping"

        Dim monitorThread As New Thread(AddressOf Monitor) With { _
            .IsBackground = True,
            .Name = "Zip Monitor"
        }
        monitorThread.Start()

        Return True
    End Function

    Private Function AddFileListBackgroundWorker(ByVal _comm As Object) As Boolean
        Dim comm As AddFileListComm = DirectCast(_comm, AddFileListComm)

        Dim tools           As New FastFileSystemTools
        Dim files           As List(Of FastFileSystemTools.ShortFileInfo)
        Dim allFiles        As New List(Of FastFileSystemTools.ShortFileInfo)
        Dim folderSize      As Int64 = 0
        Dim bytesRead       As Integer 
        Dim unNeededPortion As Int32 = 0

        For Each item As String In comm.fileList
            If Directory.Exists(item) Then
                files = tools.GetFilesInFolder(item, comm.recourseSubdirectories, folderSize)

                unNeededPortion = item.Trim.Length - GetLastFolderName(item).Length
                For Each file In files
                    file.xtraData = unNeededPortion
                Next

                allFiles.AddRange(files)
                totalBytes += folderSize
            ElseIf File.Exists(item) Then
                allFiles.Add(New FastFileSystemTools.ShortFileInfo(item))
                With allFiles.Item(allFiles.Count - 1)
                    totalBytes += .length
                    .xtraData = .name.Length - Path.GetFileName(.name).Length
                End With
            End If
        Next

        If useDotNetZip Then
            operationTitle                              += " with DotNetZip"
            Dim bufferPairs As Integer                  = (4 * System.Environment.ProcessorCount)
            outPutStream.Close
            Using output As Ionic.Zip.ZipOutputStream = New Ionic.Zip.ZipOutputStream(outPutStream.Name)
                output.Encryption                       = Ionic.Zip.EncryptionAlgorithm.None
                output.CompressionLevel                 = compressionLevel
                If compressionLevel < 1 then _
                    output.CompressionMethod            = Ionic.zip.CompressionMethod.None
                
                output.ParallelDeflateMaxBufferPairs    = bufferPairs
                If useZip64 then output.EnableZip64     = Ionic.zip.Zip64Option.AsNecessary
                Dim entry As Ionic.zip.ZipEntry

                For Each inputFile As FastFileSystemTools.ShortFileInfo In allFiles
                    entry = output.PutNextEntry(inputFile.name.Remove(0, inputFile.xtraData))
                    currentFileBytesCopied              = 0
                    currentFileLength                   = inputFile.length
                    currentFileName                     = inputFile.name

                    If password <> "" Then
                        entry.Password                  = password
                        entry.CompressionLevel          = compressionLevel
                    End If

                    If inputFile.length > 0 Then
                        If inputFile.length > (1024 * 1024) Then
                            output.ParallelDeflateThreshold = 0
                        Else
                            output.ParallelDeflateThreshold = -1
                        End If

                        Using input As FileStream       = File.Open(inputFile.name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
                            bytesRead = 1
                            Do While (bytesRead > 0)
                                bytesRead = input.Read(buff, 0, buff.Length)
                                output.Write(buff, 0, bytesRead)

                                currentFileBytesCopied  += bytesRead
                                totalBytesCopied        += bytesRead
                                If cancelOperation Then Exit For
                            Loop
                        End Using
                    End If
                Next
            End Using
        Else
            operationTitle += " with #ZipLib"
            Using output As ICSharpCode.SharpZipLib.Zip.ZipOutputStream = New ICSharpCode.SharpZipLib.Zip.ZipOutputStream(outPutStream, buff.Length)
                output.Password         = password
                output.IsStreamOwner    = True
                output.SetLevel(compressionLevel)
                If useZip64 then output.UseZip64 = ICSharpCode.SharpZipLib.Zip.UseZip64.On
                    
                Dim entry As ICSharpCode.SharpZipLib.zip.ZipEntry

                For Each inputFile As FastFileSystemTools.ShortFileInfo In allFiles
                    entry = New ICSharpCode.SharpZipLib.Zip.ZipEntry(inputFile.name.Remove(0, _
                                                    inputFile.xtraData).Replace("\", "/")) With { _
                        .Size       = inputFile.length,
                        .DateTime   = Now
                    }
                    output.PutNextEntry(entry)

                    currentFileBytesCopied  = 0
                    currentFileLength       = inputFile.length
                    currentFileName         = inputFile.name

                    Using source as FileStream = New FileStream(inputFile.name, FileMode.Open, FileAccess.Read, _
                                                        FileShare.Read, buff.Length, FileOptions.SequentialScan)
                        bytesRead           = 1
                        Do Until (bytesRead <= 0)

                            If cancelOperation Then
                                entry.Size = currentFileBytesCopied
                                output.CloseEntry
                                running = False
                                Return True
                            End If

                            bytesRead = source.Read(buff, 0, buff.Length)
                            output.Write(buff, 0, bytesRead)

                            currentFileBytesCopied  += bytesRead
                            totalBytesCopied        += bytesRead
                        Loop
                    End Using

                Next
            End Using
        End If

        running = False
        Return True
    End Function

    Private Function GetLastFolderName(ByVal folderPath As String) As String

        If folderPath.Substring(folderPath.Length - 1, 1) = "\" Then folderPath = folderPath.Remove(folderPath.Length - 1, 1)

        For position As Int32 = (folderPath.Length - 1) To 0 Step -1
            If folderPath.Substring(position, 1) = "\" Then
                Return folderPath.Remove(0, position)
            End If
        Next

        Return ""
    End Function

    Private Function CreateFolders(ByVal filePath As String) As Boolean
        If Directory.Exists(Path.GetDirectoryName(filePath)) Then Return True

        Try
            Directory.CreateDirectory(Path.GetDirectoryName(filePath))
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

    Private Sub ExtractFileProgressCallback(ByVal sender As Object, ByVal e As ExtractProgressEventArgs)
        
        Static lastBytesTransfered As Int64 = 0

        e.Cancel = cancelOperation
        currentFileName         = Path.GetFileName(e.CurrentEntry.FileName)
        
        If e.BytesTransferred = 0 then lastBytesTransfered = 0

        currentFileBytesCopied  = e.BytesTransferred
        currentFileLength       = e.CurrentEntry.UncompressedSize
        totalBytesCopied        += e.BytesTransferred - lastBytesTransfered
        lastBytesTransfered     = e.BytesTransferred
    End Sub
End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President Doxtader Industries LLC
United States United States
I've been in IT for the last 25 years in one capacity or another - always as either a network engineer or a developer... or both. At the moment I have an IT consultancy in Long Island, NY offering software development and network engineer services.

Comments and Discussions