'  
'   Rebex Sample Code License
' 
'   Copyright 2025, REBEX CR s.r.o.
'   All rights reserved.
'   https://www.rebex.net/
' 
'   Permission to use, copy, modify, and/or distribute this software for any
'   purpose with or without fee is hereby granted.
' 
'   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
'   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
'   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
'   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
'   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
'   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
'   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
'   OTHER DEALINGS IN THE SOFTWARE.
' 

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports System.Windows.Forms.ComponentModel
Imports System.IO

Imports Rebex.IO
Imports Rebex.IO.Compression

''' <summary>
''' Main WinFormZip application form.
''' </summary>
Partial Public Class MainForm
    Inherits Form

    ' ZIP archive
    Private _archive As ZipArchive

    ' options dialog
    Private _optionsDialog As OptionsDialog

    ' progress dialog
    Private _progressDialog As ProgressDialog

    ' problem dialog
    Private _problemDialog As ProblemDetectedDialog

    ''' <summary>
    ''' Initializes the form and load a ZIP file if specified.
    ''' </summary>
    Public Sub New()
        CheckForIllegalCrossThreadCalls = True

        InitializeComponent()

        ' create new instance of the options dialog
        _optionsDialog = New OptionsDialog()

        ' assign a sorter to the ListView
        listViewRight.ListViewItemSorter = New ListViewColumnSorter()

        ' select a default button
        btnOpenArchive.[Select]()

        ' enables or disables form controls as needed
        UpdateButtonsEnabled()
    End Sub

#Region "Properties"

    ''' <summary>
    ''' Gets a value indicating whether the opened archive is writable (mostly used for enabling of UI controls).
    ''' </summary>
    Public ReadOnly Property IsArchiveWriteable() As Boolean
        Get
            Return (_archive IsNot Nothing AndAlso Not _archive.IsReadOnly)
        End Get
    End Property

    ''' <summary>
    ''' Gets a path to a directory to which ZIP files were extracted recently (set to an archive's directory by default).
    ''' </summary>
    <DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
    Public Property LastUsedExtractDirectory() As String
        Get
            If String.IsNullOrEmpty(_lastUsedExtractDirectory) AndAlso _archive IsNot Nothing Then
                Return Path.GetDirectoryName(_archive.FilePath)
            Else
                Return _lastUsedExtractDirectory
            End If
        End Get
        Set(value As String)
            _lastUsedExtractDirectory = value
        End Set
    End Property
    Private _lastUsedExtractDirectory As String

#End Region

#Region "Button events"

    ''' <summary>
    ''' 'New archive' click event - create a new ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnNewArchive_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnNewArchive.Click
        ' ask for new zip file name
        saveFileDialog.Title = "Create a new ZIP archive"
        saveFileDialog.DefaultExt = ".zip"
        saveFileDialog.Filter = "ZIP files (*.zip)|*.zip"

        If saveFileDialog.ShowDialog(Me) = DialogResult.OK Then
            ' create new archive
            CreateNewArchive(saveFileDialog.FileName)
        End If
    End Sub

    ''' <summary>
    ''' 'Open archive' click event - open an existing ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnOpenArchive_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnOpenArchive.Click
        ' ask the zipped file to open
        openFileDialog.Title = "Open a ZIP archive"
        openFileDialog.Filter = "ZIP files (*.zip)|*.zip"
        openFileDialog.Multiselect = False

        If openFileDialog.ShowDialog(Me) = DialogResult.OK Then
            ' open existing archive
            OpenArchive(openFileDialog.FileName)
        End If
    End Sub

    ''' <summary>
    ''' 'Add files' click event - add files to the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnAddFiles_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnAddFiles.Click
        ' ask the zipped file to open
        openFileDialog.Title = "Add files to the ZIP archive"
        openFileDialog.Filter = "All files (*.*)|*.*"
        openFileDialog.Multiselect = True

        If openFileDialog.ShowDialog(Me) = DialogResult.OK Then
            ' Add files to Archive
            Dim target As String = Utils.RootPath
            If treeViewLeft.SelectedNode IsNot Nothing Then
                Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
                target = item.Path
            End If

            AddFiles(GetFileSet(openFileDialog.FileNames), target)
        End If
    End Sub

    ''' <summary>
    ''' 'Add directory' click event - add a directory to the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnAddDirectory_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnAddDirectory.Click
        folderBrowserDialog.Description = "Add directory to the ZIP archive"
        folderBrowserDialog.SelectedPath = LastUsedExtractDirectory
        folderBrowserDialog.ShowNewFolderButton = False

        If folderBrowserDialog.ShowDialog(Me) = DialogResult.OK Then
            ' Add directory to archive
            Dim target = Utils.RootPath
            If treeViewLeft.SelectedNode IsNot Nothing Then
                Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
                target = item.Path
            End If
            AddFiles(GetFileSet(folderBrowserDialog.SelectedPath), target)
        End If
    End Sub

    ''' <summary>
    ''' 'Extract' click event - extract selected files.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnExtract_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnExtract.Click
        ExtractSelectedMenuItem_Click(sender, e)
    End Sub

    ''' <summary>
    ''' 'Extract all' click event - extract the whole ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnExtractAll_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnExtractAll.Click
        ExtractFiles(New FileSet(Utils.RootPath, "*"))
    End Sub

    ''' <summary>
    ''' Handles an event to open an Options dialog.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnOptions_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnOptions.Click
        _optionsDialog.ShowDialog(Me, _archive)
    End Sub

#End Region

#Region "TreeView related events"

    ''' <summary>
    ''' TreeView mouse-up event - show context menu.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub treeViewLeft_MouseUp(ByVal sender As Object, ByVal e As MouseEventArgs) Handles treeViewLeft.MouseUp
        If e.Button = MouseButtons.Right Then
            ' get the clicked node
            Dim clickedNode As TreeNode = treeViewLeft.GetNodeAt(e.X, e.Y)
            If clickedNode IsNot Nothing Then
                ' select the clicked node
                treeViewLeft.SelectedNode = clickedNode

                Dim item As ZipItem = CType(clickedNode.Tag, ZipItem)

                ' enable/disable context menu items
                MoveDirectoryMenuItem.Enabled = IsArchiveWriteable AndAlso Not item.IsRootDirectory
                ExtractDirectoryMenuItem.Enabled = (_archive IsNot Nothing)
                DeleteDirectoryMenuItem.Enabled = IsArchiveWriteable
                CreateDirectoryMenuItem.Enabled = IsArchiveWriteable

                ' show context menu
                contextMenuTreeView.Show(treeViewLeft, e.Location)
            End If
        End If
    End Sub

    ''' <summary>
    ''' TreeView key-down event - handle a 'Delete' key to delete a directory within the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub treeViewLeft_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles treeViewLeft.KeyDown
        If e.KeyCode = Keys.Delete Then
            ' behave just like 'Delete directory' context menu item
            DeleteDirectoryMenuItem_Click(sender, e)
        End If
    End Sub

    ''' <summary>
    ''' 'Extract directory' menu item click - extract a directory from the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ExtractDirectoryMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ExtractDirectoryMenuItem.Click
        If treeViewLeft.SelectedNode IsNot Nothing Then
            Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
            ExtractFiles(GetFileSet(item.Path))
        End If
    End Sub

    ''' <summary>
    ''' 'Delete directory' menu item click - delete a directory from the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub DeleteDirectoryMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles DeleteDirectoryMenuItem.Click
        If treeViewLeft.SelectedNode IsNot Nothing Then
            Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)

            Dim deleteQuestionResult As DialogResult = MessageBox.Show(Me, _
                String.Format("Are you sure you want to delete directory '{0}'?", item.Name), _
                "Delete confirmation", _
                MessageBoxButtons.YesNoCancel, _
                MessageBoxIcon.Question, _
                MessageBoxDefaultButton.Button2)

            If deleteQuestionResult = DialogResult.Yes Then
                DeleteFiles(GetFileSet(item.Path))
            End If
        End If
    End Sub

    ''' <summary>
    ''' 'Move directory' menu item click - move a directory within the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub MoveDirectoryMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles MoveDirectoryMenuItem.Click
        If treeViewLeft.SelectedNode IsNot Nothing Then
            Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
            MoveDirectory(item.Path)
        End If
    End Sub

    ''' <summary>
    ''' 'Create directory' menu item click - create a new directory in the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub CreateDirectoryMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles CreateDirectoryMenuItem.Click
        If treeViewLeft.SelectedNode IsNot Nothing Then
            Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
            CreateArchiveDirectory(item.Path)
        Else
            CreateArchiveDirectory(Utils.RootPath)
        End If
    End Sub

    ''' <summary>
    ''' TreeView after-select event - fill ListView with currently selected directory content.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub treeViewLeft_AfterSelect(ByVal sender As Object, ByVal e As TreeViewEventArgs) Handles treeViewLeft.AfterSelect
        Utils.FillListView(_archive, listViewRight, e.Node)
    End Sub

#End Region

#Region "ListView related events"

    ''' <summary>
    ''' 'Extract files' menu item click - extracts selected file(s) from the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ExtractSelectedMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ExtractSelectedMenuItem.Click
        If listViewRight.SelectedItems.Count > 0 Then
            Dim filesToExtract As New List(Of String)
            For i As Integer = 0 To listViewRight.SelectedItems.Count - 1
                Dim item As ZipItem = CType(listViewRight.SelectedItems(i).Tag, ZipItem)
                filesToExtract.Add(item.Path)
            Next
            ExtractFiles(GetFileSet(filesToExtract))
        End If
    End Sub

    ''' <summary>
    ''' 'Rename file' menu item click - renames the selected file within the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub RenameSelectedMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles RenameSelectedMenuItem.Click
        If listViewRight.SelectedItems.Count > 0 Then
            Dim item As ZipItem = CType(listViewRight.SelectedItems(0).Tag, ZipItem)
            RenameFile(item.Path)
        End If
    End Sub

    ''' <summary>
    ''' 'Delete files' menu item click - delete selected file(s) from the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub DeleteSelectedMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles DeleteSelectedMenuItem.Click
        If listViewRight.SelectedItems.Count > 0 Then
            Dim deleteQuestionResult As DialogResult = MessageBox.Show(Me, String.Format("Are you sure you want to delete selected {0}?", IIf(listViewRight.SelectedItems.Count = 1, "file", "files")), "Delete confirmation", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2)

            If deleteQuestionResult = DialogResult.Yes Then
                Dim filesToDelete As New List(Of String)
                For i As Integer = 0 To listViewRight.SelectedItems.Count - 1
                    Dim item As ZipItem = CType(listViewRight.SelectedItems(i).Tag, ZipItem)
                    filesToDelete.Add(item.Path)
                Next
                DeleteFiles(GetFileSet(filesToDelete))
            End If
        End If
    End Sub

    ''' <summary>
    ''' ListView column click - sorts the ListView.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub listViewRight_ColumnClick(ByVal sender As Object, ByVal e As ColumnClickEventArgs) Handles listViewRight.ColumnClick
        ' update sorter column
        Dim sorter As ListViewColumnSorter = CType(listViewRight.ListViewItemSorter, ListViewColumnSorter)
        sorter.SetSortingColumn(e.Column)

        ' perform the sort using new sort column
        listViewRight.Sort()
    End Sub

    ''' <summary>
    ''' ListView key-down event - delete selected file(s) from the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub listViewRight_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles listViewRight.KeyDown
        If e.KeyCode = Keys.Delete Then
            ' behave just like 'Delete files' context menu item
            DeleteSelectedMenuItem_Click(sender, e)
        End If
    End Sub

    ''' <summary>
    ''' ListView drag-enter event - enables drag'n'drop functionality.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub listViewRight_DragEnter(ByVal sender As Object, ByVal e As DragEventArgs) Handles listViewRight.DragEnter
        If (e.AllowedEffect And DragDropEffects.Copy) <> 0 AndAlso IsArchiveWriteable Then
            Dim paths As String() = TryCast(e.Data.GetData(DataFormats.FileDrop), String())
            If paths IsNot Nothing AndAlso paths.Length > 0 Then
                e.Effect = DragDropEffects.Copy
            End If
        End If
    End Sub

    ''' <summary>
    ''' ListView drag-drop event - finish the drag'n'drop process.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub listViewRight_DragDrop(ByVal sender As Object, ByVal e As DragEventArgs) Handles listViewRight.DragDrop
        Dim paths As String() = TryCast(e.Data.GetData(DataFormats.FileDrop), String())
        If paths IsNot Nothing AndAlso paths.Length > 0 Then
            Dim target = Utils.RootPath
            If treeViewLeft.SelectedNode IsNot Nothing Then
                Dim item As ZipItem = CType(treeViewLeft.SelectedNode.Tag, ZipItem)
                target = item.Path
            End If
            AddFiles(GetFileSet(paths), target)
        End If
    End Sub

    ''' <summary>
    ''' ListView ContextMenu opening event - display the correct menu items.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub contextMenuListView_Opening(ByVal sender As Object, ByVal e As CancelEventArgs) Handles contextMenuListView.Opening
        ' enable/disable context menu items
        ExtractSelectedMenuItem.Enabled = (_archive IsNot Nothing AndAlso listViewRight.SelectedItems.Count > 0)
        RenameSelectedMenuItem.Enabled = (IsArchiveWriteable AndAlso listViewRight.SelectedItems.Count = 1)
        DeleteSelectedMenuItem.Enabled = (IsArchiveWriteable AndAlso listViewRight.SelectedItems.Count > 0)
    End Sub

    ''' <summary>
    ''' ListView selected index changed event - enable or disable the Extract button.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub listViewRight_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles listViewRight.SelectedIndexChanged
        btnExtract.Enabled = (_archive IsNot Nothing AndAlso listViewRight.SelectedItems.Count > 0)
    End Sub

#End Region

#Region "Form events"

    ''' <summary>
    ''' MainForm closing event - close the ZIP archive.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub MainForm_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles MyBase.FormClosing
        CloseArchive()
    End Sub

#End Region

#Region "Archive global events"

    ''' <summary>
    ''' ZipArchive ProgressChanged event - updates progress indicators.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ProgressChanged(ByVal sender As Object, ByVal e As ZipProgressChangedEventArgs)
        If _progressDialog IsNot Nothing Then
            _progressDialog.ProgressChanged(e)
        End If
    End Sub

    ''' <summary>
    ''' ZipArchive ShrinkProgress event - updates shrink progress indicator.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ShrinkProgress(ByVal sender As Object, ByVal e As ZipShrinkProgressEventArgs)
        If e.ItemsProcessed = e.ItemsTotal Then
            statusLabel.Text = "Shrinking finished"
            statusProgress.Visible = False
            statusStrip.Refresh()
        Else
            statusLabel.Text = "Shrinking..."
            statusProgress.Visible = True
            If statusProgress.Value <> e.ProgressPercentage Then
                statusProgress.Value = e.ProgressPercentage
            End If
        End If
    End Sub

    ''' <summary>
    ''' ZipArchive ProblemDetected event - asks the user what to do when a problem occurs.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ProblemDetected(ByVal sender As Object, ByVal e As ZipProblemDetectedEventArgs)
        If _problemDialog Is Nothing Then
            _problemDialog = New ProblemDetectedDialog()
        End If

        _problemDialog.ShowDialog(Me, e)
    End Sub

    ''' <summary>
    ''' ZipArchive PasswordRequired event - asks the user for password when needed.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub PasswordRequired(ByVal sender As Object, ByVal e As ZipPasswordRequiredEventArgs)
        Dim comment As String
        If e.Reason = ArchivePasswordReason.PasswordRequired Then
            comment = "Please enter a password."
        Else
            comment = "Entered password is incorrect or file is corrupted."
        End If

        Dim dialog As New PasswordDialog(e.ArchiveItemPath, comment)

        Select Case dialog.ShowDialog(Me)
            Case DialogResult.OK
                e.Password = dialog.Password
                Exit Select
            Case DialogResult.Ignore
                e.Action = ArchivePasswordActions.SkipFile
                Exit Select
            Case Else
                e.Action = ArchivePasswordActions.Cancel
                Exit Select
        End Select
    End Sub

#End Region

#Region "Extract files"

    ''' <summary>
    ''' Extracts specified files/directories from the archive.
    ''' </summary>
    ''' <param name="filesToExtract">Collection of files or directories to extract.</param>
    Private Sub ExtractFiles(ByVal filesToExtract As FileSet)
        folderBrowserDialog.Description = String.Format("Select directory to extract")
        folderBrowserDialog.SelectedPath = LastUsedExtractDirectory
        folderBrowserDialog.ShowNewFolderButton = True

        If folderBrowserDialog.ShowDialog(Me) <> DialogResult.OK Then
            Return
        End If

        LastUsedExtractDirectory = folderBrowserDialog.SelectedPath

        SetEnabled(False)
        statusLabel.Text = "Extracting files..."
        CreateProgressDialog("Extracting files")

        ' start asynchronous Extract operation
        _archive.ExtractAsync(filesToExtract, folderBrowserDialog.SelectedPath, 0, 0)
    End Sub

    ''' <summary>
    ''' ZipArchive ExtractCompleted event - called when the asynchronous ExtractAsync method finishes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub ExtractCompleted(ByVal sender As Object, ByVal e As ZipExtractCompletedEventArgs)
        If e.Cancelled Then
            ShowMessage("Operation was cancelled.")
        ElseIf e.[Error] IsNot Nothing Then
            If TypeOf e.[Error] Is ZipException Then
                ShowMessage(e.[Error].Message)
            Else
                ShowMessage(String.Format("Error occured: {0}", e.[Error]))
            End If
        Else
            statusLabel.Text = "Done"
            ShowMessage(String.Format("Extracted {0} {1}.", e.Result.FilesAffected, IIf(e.Result.FilesAffected = 1, "file", "files")))
        End If

        FinishJob()
    End Sub

#End Region

#Region "Compress files"

    ''' <summary>
    ''' Adds specified files/directories to the archive.
    ''' </summary>
    ''' <param name="filesToAdd">Collection of files or directories to add.</param>
    ''' <param name="targetDirectory">Path to a directory where to add specified files or directories.</param>
    Private Sub AddFiles(ByVal filesToAdd As FileSet, ByVal targetDirectory As String)
        SetEnabled(False)
        statusLabel.Text = "Compressing files..."
        CreateProgressDialog("Compressing files")

        ' start asynchronous Add operation
        _archive.AddAsync(filesToAdd, targetDirectory, 0, 0)
    End Sub

    ''' <summary>
    ''' ZipArchive AddCompleted event - called when the asynchronous AddAsync method finishes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub AddCompleted(ByVal sender As Object, ByVal e As ZipAddCompletedEventArgs)
        ' refresh the TreeView content (ListView is refreshed as well)
        FillTreeView()

        If e.Cancelled Then
            ShowMessage("Operation was cancelled.")
        ElseIf e.[Error] IsNot Nothing Then
            If TypeOf e.[Error] Is ZipException Then
                ShowMessage(e.[Error].Message)
            Else
                ShowMessage(String.Format("Error occured: {0}", e.[Error]))
            End If
        Else
            statusLabel.Text = "Done"
            ShowMessage(String.Format("Added {0} {1}.", e.Result.FilesAffected, IIf(e.Result.FilesAffected = 1, "file", "files")))
        End If

        FinishJob()
    End Sub

#End Region

#Region "Delete files"

    ''' <summary>
    ''' Deletes specified files/directories within the archive.
    ''' </summary>
    ''' <param name="itemsToDelete">Collection of files or directories to delete.</param>
    Private Sub DeleteFiles(ByVal itemsToDelete As FileSet)
        SetEnabled(False)
        statusLabel.Text = "Deleting..."

        ' start asynchronous Delete operation
        _archive.DeleteAsync(itemsToDelete, ArchiveSaveAction.Auto)
    End Sub

    ''' <summary>
    ''' ZipArchive DeleteCompleted event - called when the asynchronous DeleteAsync method finishes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub DeleteCompleted(ByVal sender As Object, ByVal e As ZipDeleteCompletedEventArgs)
        ' refresh the TreeView content (ListView is refreshed as well)
        FillTreeView()

        If e.Cancelled Then
            ShowMessage("Operation was cancelled.")
        ElseIf e.[Error] IsNot Nothing Then
            If TypeOf e.[Error] Is ZipException Then
                ShowMessage(e.[Error].Message)
            Else
                ShowMessage(String.Format("Error occured: {0}", e.[Error]))
            End If
        Else
            statusLabel.Text = "Done"
            ShowMessage(String.Format("Deleted {0} {1}.", e.Result.FilesAffected, IIf(e.Result.FilesAffected = 1, "file", "files")))
        End If

        FinishJob()
    End Sub

#End Region

#Region "Move/Rename directory/file"

    ''' <summary>
    ''' Moves specified file/directory within the archive.
    ''' </summary>
    ''' <param name="itemPath">Path to a file or directory to move.</param>
    Private Sub MoveDirectory(ByVal itemPath As String)
        Dim dialog As New NewNameDialog(itemPath, False)
        If dialog.ShowDialog(Me) <> DialogResult.OK Then
            Return
        End If

        SetEnabled(False)
        statusLabel.Text = String.Format("Moving directory: {0}", itemPath)

        ' start asynchronous Move operation
        _archive.MoveAsync(itemPath, dialog.NewName)
    End Sub

    ''' <summary>
    ''' ZipArchive MoveCompleted event - called when the asynchronous MoveAsync method finishes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub MoveCompleted(ByVal sender As Object, ByVal e As ZipMoveCompletedEventArgs)
        FillTreeView()

        If e.Cancelled Then
            ShowMessage("Operation was cancelled.")
        ElseIf e.[Error] IsNot Nothing Then
            If TypeOf e.[Error] Is ZipException Then
                ShowMessage(e.[Error].Message)
            Else
                ShowMessage(String.Format("Error occured: {0}", e.[Error]))
            End If
        End If

        FinishJob()
    End Sub

    ''' <summary>
    ''' Renames specified file/directory within the archive.
    ''' </summary>
    ''' <param name="itemPath">Path to a file or directory to rename.</param>
    Private Sub RenameFile(ByVal itemPath As String)
        Dim zipItem As ZipItem = _archive.GetItem(itemPath)

        Dim dialog As New NewNameDialog(zipItem.Name, True)
        If dialog.ShowDialog(Me) <> DialogResult.OK Then
            Return
        End If

        SetEnabled(False)
        statusLabel.Text = String.Format("Renaming file: {0}", itemPath)

        Try
            ' rename item synchronously (this is not time expensive operation)
            ' optionaly the _archive.MoveAsync(zipItem.Path, "renamed path") method can be used
            zipItem.Rename(dialog.NewName)
        Catch [error] As ZipException
            ShowMessage([error].Message)
        Catch [error] As Exception
            ShowMessage(String.Format("Error occured: {0}", [error]))
        Finally
            FillTreeView()
            FinishJob()
        End Try
    End Sub

#End Region

#Region "Create directory"

    ''' <summary>
    ''' Creates a new directory at the specified path within the archive.
    ''' </summary>
    ''' <param name="basePath">A path to a directory where to create new directory.</param>
    Private Sub CreateArchiveDirectory(ByVal basePath As String)
        Dim dialog As New NewNameDialog("", False)
        If dialog.ShowDialog(Me) <> DialogResult.OK Then
            Return
        End If

        Dim path As String = System.IO.Path.Combine(basePath, dialog.NewName)

        SetEnabled(False)
        statusLabel.Text = String.Format("Creating directory: {0}", path)

        Try
            _archive.CreateDirectory(path)
        Catch [error] As ZipException
            ShowMessage([error].Message)
        Catch [error] As Exception
            ShowMessage(String.Format("Error occurred: {0}", [error]))
        Finally
            FillTreeView()
            FinishJob()
        End Try
    End Sub

#End Region

#Region "Archive methods"

    ''' <summary>
    ''' Creates a new ZIP archive at the specified path.
    ''' </summary>
    ''' <param name="zipFilePath">A path to a ZIP archive.</param>
    Private Sub CreateNewArchive(ByVal zipFilePath As String)
        CloseArchive()

        Try
            ' create a new archive
            _archive = New ZipArchive(zipFilePath, ArchiveOpenMode.Create, ArchiveAccessMode.ReadWrite)

            Initialize()
        Catch ex As ZipException
            ShowMessage(ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Opens an existing archive at specified path.
    ''' </summary>
    ''' <param name="zipFilePath">A path to a ZIP archive.</param>
    Public Sub OpenArchive(ByVal zipFilePath As String)
        CloseArchive()

        Try
            ' open an existing archive
            _archive = New ZipArchive(zipFilePath, ArchiveOpenMode.Open)

            Initialize()
        Catch ex As ZipException
            ShowMessage(ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Initializes recently opened archive and prepares the UI.
    ''' </summary>
    Private Sub Initialize()
        AddHandler _archive.ProgressChanged, AddressOf ProgressChanged
        AddHandler _archive.ShrinkProgress, AddressOf ShrinkProgress
        AddHandler _archive.ProblemDetected, AddressOf ProblemDetected
        AddHandler _archive.PasswordRequired, AddressOf PasswordRequired
        AddHandler _archive.AddCompleted, AddressOf AddCompleted
        AddHandler _archive.ExtractCompleted, AddressOf ExtractCompleted
        AddHandler _archive.DeleteCompleted, AddressOf DeleteCompleted
        AddHandler _archive.MoveCompleted, AddressOf MoveCompleted

        _optionsDialog.Initialize(_archive)

        FillTreeView()

        UpdateButtonsEnabled()

        treeViewLeft.Nodes(0).Expand()

        treeViewLeft.[Select]()
    End Sub

    ''' <summary>
    ''' Closes the archive.
    ''' </summary>
    Private Sub CloseArchive()
        If _archive IsNot Nothing Then
            _archive.Close(ArchiveSaveAction.None)
            _archive = Nothing
        End If
    End Sub

    ''' <summary>
    ''' Converts list of full paths to a FileSet.
    ''' </summary>
    ''' <param name="filePaths">List of full paths.</param>
    ''' <returns>A FileSet initialized with the list argument.</returns>
    Private Function GetFileSet(ByVal filePaths As IList(Of String)) As FileSet
        Dim [set] = New FileSet()

        If filePaths.Count = 0 Then
            Return [set]
        End If

        [set].BasePath = Path.GetDirectoryName(filePaths(0))
        For Each path As String In filePaths
            [set].Include(System.IO.Path.GetFileName(path), TraversalMode.NonRecursive)
        Next
        Return [set]
    End Function

    ''' <summary>
    ''' Converts full path to a FileSet.
    ''' </summary>
    ''' <param name="fullPath">A full path.</param>
    ''' <returns>A FileSet initialized with the path argument.</returns>
    Private Function GetFileSet(ByVal fullPath As String) As FileSet
        Dim basePath As String = Path.GetDirectoryName(fullPath)
        Dim name As String = Path.GetFileName(fullPath)
        If String.IsNullOrEmpty(basePath) Then
            Return New FileSet(fullPath, "*", TraversalMode.Recursive)
        Else
            Return New FileSet(basePath, name, TraversalMode.Recursive)
        End If
    End Function

#End Region

#Region "Various UI methods"

    ''' <summary>
    ''' Enables or disables buttons based on archive writeability.
    ''' </summary>
    Private Sub UpdateButtonsEnabled()
        btnAddDirectory.Enabled = IsArchiveWriteable
        btnAddFiles.Enabled = IsArchiveWriteable
        btnExtract.Enabled = (_archive IsNot Nothing AndAlso listViewRight.SelectedItems.Count > 0)
        btnExtractAll.Enabled = (_archive IsNot Nothing)
    End Sub

    ''' <summary>
    ''' Enables or disables form controls.
    ''' </summary>
    ''' <param name="enabled">Whether to enable or disable form controls.</param>
    Private Sub SetEnabled(ByVal enabled As Boolean)
        btnAddDirectory.Enabled = IsArchiveWriteable AndAlso enabled
        btnAddFiles.Enabled = IsArchiveWriteable AndAlso enabled
        btnExtract.Enabled = listViewRight.SelectedItems.Count > 0 AndAlso enabled
        btnExtractAll.Enabled = enabled

        btnOpenArchive.Enabled = enabled
        btnNewArchive.Enabled = enabled
        btnOptions.Enabled = enabled

        treeViewLeft.Enabled = enabled
        listViewRight.Enabled = enabled

        If enabled Then
            MyBase.Cursor = Cursors.[Default]
        Else
            MyBase.Cursor = Cursors.WaitCursor
        End If
    End Sub

    ''' <summary>
    ''' Enables form controls and releases helper objects.
    ''' </summary>
    Private Sub FinishJob()
        SetEnabled(True)
        statusLabel.Text = "Ready"

        If _progressDialog IsNot Nothing Then
            _progressDialog.Close()
        End If

        _progressDialog = Nothing
        _problemDialog = Nothing
    End Sub

    ''' <summary>
    ''' Shows the specified message.
    ''' </summary>
    ''' <param name="message">A message to show.</param>
    Private Sub ShowMessage(ByVal message As String)
        GeneralDialog.ShowDialog(Me, message)
    End Sub

    ''' <summary>
    ''' Creates and displays the progress dialog.
    ''' </summary>
    ''' <param name="title">A title of the progress dialog.</param>
    Private Sub CreateProgressDialog(ByVal title As String)
        _progressDialog = New ProgressDialog(_archive, title)
        _progressDialog.Show(Me)
    End Sub

    ''' <summary>
    ''' Fill TreeView with the content of the ZIP archive and also fill ListView if necessary.
    ''' </summary>
    Private Sub FillTreeView()
        Utils.FillTreeView(treeViewLeft, _archive)

        ' selecting the TreeView node causes the treeViewLeft_AfterSelect method to be called 
        ' which fills the ListView with the apropriate content
        If treeViewLeft.SelectedNode Is Nothing Then
            treeViewLeft.SelectedNode = treeViewLeft.Nodes(0)
        End If
    End Sub

#End Region
End Class
