'  
'   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.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Xml
Imports System.Resources
Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Diagnostics
Imports System.Threading

Imports Rebex
Imports Rebex.IO
Imports Rebex.Net

'' <summary>
'' Simple SFTP client.
'' </summary>
Public Class MainForm
    Inherits System.Windows.Forms.Form
   
    Private components As System.ComponentModel.Container = Nothing
    Private tbLog As System.Windows.Forms.RichTextBox
    Private WithEvents listServer As System.Windows.Forms.ListView
    Private columnName As System.Windows.Forms.ColumnHeader
    Private columnSize As System.Windows.Forms.ColumnHeader
    Private lblServer As System.Windows.Forms.Label
    Private WithEvents listLocal As System.Windows.Forms.ListView
    Private lblLocal As System.Windows.Forms.Label
    Private WithEvents lblServerDirectory As System.Windows.Forms.Label
    Private columnHeader1 As System.Windows.Forms.ColumnHeader
    Private columnHeader2 As System.Windows.Forms.ColumnHeader
    Private WithEvents lblLocalDirectory As System.Windows.Forms.Label
    Private columnDate As System.Windows.Forms.ColumnHeader
    Private columnHeader3 As System.Windows.Forms.ColumnHeader
    Private WithEvents btnStop As System.Windows.Forms.Button
    Private WithEvents btnDelete As System.Windows.Forms.Button
    Private WithEvents btnUpload As System.Windows.Forms.Button
    Private WithEvents btnDownload As System.Windows.Forms.Button
    Private WithEvents btnRefresh As System.Windows.Forms.Button
    Private WithEvents btnMakeDir As System.Windows.Forms.Button
    Private WithEvents cbDrives As System.Windows.Forms.ComboBox
    Private WithEvents btnConnect As System.Windows.Forms.Button
    Private gbControl As System.Windows.Forms.GroupBox
    Private headerLeft As System.Windows.Forms.PictureBox
    Private WithEvents headerRight As System.Windows.Forms.PictureBox
    Private headerMiddle As System.Windows.Forms.Panel

    Private _sftp As Sftp                           ' SFTP session
    Private listActive As ListView                  ' active list (server x local)
    Private _serverDirs As Integer                  ' number of dirs on server side
    Private _localDirs As Integer                   ' number of dirs on local side
    Private _selectedDrive As Integer = -1          ' selected drive (A:, C:, ...) in combobox
    Private _localPath As String                    ' local path
    Private _isWorking As Boolean                   ' determines whether any operation is running
    Private _transferTime As DateTime               ' transfer launch-time
    Private _problemForm As ProblemDetectedForm     ' informs the user about problems while transferring data
    Private _data As ConnectionFormData

    ' constansts
    Private Const SYMLINK As String = "--->"  ' symlink tag
    Private WithEvents trackBar As System.Windows.Forms.TrackBar
    Private WithEvents lblSpeed As System.Windows.Forms.Label
    Private WithEvents mainStatusStrip As System.Windows.Forms.StatusStrip
    Private WithEvents pbTransfer As System.Windows.Forms.ToolStripProgressBar
    Private WithEvents lblProgress As System.Windows.Forms.ToolStripStatusLabel
    Private WithEvents mainSplitContainer As MainForm.SplitContainer
    Private Const URL As String = "https://www.rebex.net/sftp.net/" ' url

    Public Overloads Sub WriteToLog(ByVal x As Exception)
        ' SftpExceptions are automatilcally logged by component logger.
        ' No need to display them again.
        ' We don't wont duplicate entries for such exceptions.
        If TypeOf x Is SftpException Then Return

        WriteToLog("* " & x.ToString(), RichTextBoxLogWriter.COLORERROR)
    End Sub

    Public Overloads Sub WriteToLog(ByVal message As String, ByVal color As Color)
        tbLog.Focus()
        tbLog.SelectionColor = color
        tbLog.AppendText((message & ControlChars.Cr & ControlChars.Lf))
    End Sub

    '' <summary>
    '' get filename from path (xxxxx/xxxx/yyy -> yyy)
    '' </summary>
    '' <param name="path">sftp path</param>
    '' <returns>filename</returns>
    Private Shared Function GetFilenameFromPath(ByVal path As String) As String
        Dim idx As Integer = path.LastIndexOf("/")

        If idx <> -1 AndAlso idx + 1 <> path.Length Then
            path = path.Substring((idx + 1))
        End If

        Return path
    End Function 'GetFilenameFromPath

    '' <summary>
    '' format datetime to readable form in the panels
    '' </summary>
    '' <param name="dt">datetime</param>
    '' <returns>formatted string</returns>
    Private Shared Function FormatFileTime(ByVal dt As DateTime) As String
        Return dt.ToString("yyyy-MM-dd HH:mm")
    End Function 'FormatFileTime

    ''<summary>
    ''Wrapper for Invoke method that doesn't throw an exception after the object has been
    ''disposed while the calling method was running in a background thread.
    ''</summary>
    ''<param name="method"></param>
    ''<param name="args"></param>
    Private Sub SafeInvoke(ByVal method As [Delegate], ByVal args() As Object)
        Try
            If Not IsDisposed Then Invoke(method, args)
        Catch x As ObjectDisposedException
        End Try
    End Sub

    '' <summary>
    '' event displaying sftp state
    '' </summary>
    Public Sub StateChanged(ByVal sender As Object, ByVal e As SftpStateChangedEventArgs)
        Select Case e.NewState
            Case SftpState.Disconnected, SftpState.Disposed
                lblProgress.Text = "Disconnected"
                btnConnect.Text = "Connect"
                listServer.Items.Clear()
            Case SftpState.Ready
                lblProgress.Text = "Ready"
        End Select
    End Sub

    ''' <summary>
    ''' handles the displaying traversing event
    ''' </summary>
    Private Sub Traversing(ByVal sender As Object, ByVal e As SftpTraversingEventArgs)
        If e.Action = TransferAction.Listing Then
            Return
        End If

        Dim strBatchInfo As String = String.Format("({0} file{1} traversed)    ", e.FilesTotal, (IIf(e.FilesTotal > 1, "s", Nothing)))

        Select Case e.TraversingState
            Case TraversingState.DirectoryRetrieved
                strBatchInfo &= "Directory retrieved."
                Exit Select
            Case TraversingState.DirectoryRetrieving
                strBatchInfo &= "Retrieving directory..."
                Exit Select
            Case TraversingState.HierarchyRetrieved
                strBatchInfo &= String.Format("Hierarchy retrieved ({0} byte{1} in {2} file{3}).", _
                 e.BytesTotal, (IIf(e.BytesTotal > 1, "s", "")), e.FilesTotal, (IIf(e.FilesTotal > 1, "s", Nothing)))
                Exit Select
            Case TraversingState.HierarchyRetrieving
                strBatchInfo &= "Retrieving hierarchy..."
                Exit Select
        End Select

        lblProgress.Text = strBatchInfo
    End Sub


    ''' <summary>
    ''' handles the transfer progress changed event
    ''' </summary>
    Private Sub TransferProgressChanged(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
        Dim strBatchInfo As String = String.Format("({0} / {1} file{2} processed)    ", e.FilesProcessed, e.FilesTotal, (IIf(e.FilesProcessed > 1, "s", "")))

        SetProgressValue(Convert.ToInt32(e.ProgressPercentage))

        Select Case e.TransferState
            Case TransferProgressState.DataBlockProcessed
                strBatchInfo &= e.BytesTransferred & " bytes"
                Exit Select
            Case TransferProgressState.DirectoryProcessing
                strBatchInfo &= "Processing directory..."
                Exit Select
            Case TransferProgressState.FileTransferring
                strBatchInfo &= "Transferring file..."
                Exit Select
            Case TransferProgressState.FileTransferred
                strBatchInfo &= "File transferred."
                Exit Select
            Case TransferProgressState.TransferCompleted
                strBatchInfo &= "Transfer completed."
                ShowTransferStatus(e.BytesTransferred, e.FilesTransferred)
                Exit Select
        End Select

        lblProgress.Text = strBatchInfo
    End Sub

    Private Sub SetProgressValue(ByVal value As Integer)
        ' workaround for progress bar smoothing
        If (value = 100) Then
            pbTransfer.Maximum = 101
            pbTransfer.Value = 101
            pbTransfer.Maximum = 100
            System.Threading.Thread.Sleep(200)
        Else
            pbTransfer.Value = value
        End If
    End Sub

    ''' <summary>
    ''' handles the delete progress changed event
    ''' </summary>
    Private Sub DeleteProgressChanged(ByVal sender As Object, ByVal e As SftpDeleteProgressChangedEventArgs)
        Dim strDeleteInfo As String = String.Format("({0} / {1} file{2} deleted)    ", e.FilesProcessed, e.FilesTotal, (IIf(e.FilesProcessed > 1, "s", "")))

        Select Case e.DeleteState
            Case DeleteProgressState.DeleteCompleted
                strDeleteInfo &= "Delete completed."
                Exit Select
            Case DeleteProgressState.DirectoryDeleted
                strDeleteInfo &= "Directory deleted."
                Exit Select
            Case DeleteProgressState.DirectoryProcessing
                strDeleteInfo &= "Processing directory..."
                Exit Select
            Case DeleteProgressState.FileDeleted
                strDeleteInfo &= "File deleted."
                Exit Select
            Case DeleteProgressState.FileDeleting
                strDeleteInfo &= "Deleting file..."
                Exit Select
        End Select

        lblProgress.Text = strDeleteInfo
    End Sub

    ''' <summary>
    ''' handles the batch transfer problem detected event
    ''' </summary>
    Private Sub ProblemDetected(ByVal sender As Object, ByVal e As SftpProblemDetectedEventArgs)
        _problemForm.ShowModal(Me, e)
    End Sub


    '' <summary>
    '' make local list + drives combo box
    '' </summary>
    Public Sub MakeLocalList()
        Dim dirs As Integer = 0
        Dim files As Integer = 0
        Dim size As Long = 0
        Dim c As Integer
        ' make a logical drives list [first run only]
        If cbDrives.Items.Count <= 0 Then

            Dim drives As String() = Directory.GetLogicalDrives()

            Dim lowerDrive As String = "c:\"

            ' find "lower" acceptable drive
            For c = drives.Length - 1 To 0 Step -1
                If drives(c).ToLower().CompareTo("c:\") >= 0 Then
                    lowerDrive = drives(c).ToLower()
                End If
            Next c

            ' feed the drives' list and select one
            For c = 0 To drives.Length - 1
                Dim drive As String = drives(c)

                cbDrives.Items.Add(drive)

                If _selectedDrive = -1 AndAlso drive.ToLower().CompareTo(lowerDrive) = 0 Then
                    cbDrives.SelectedIndex = c
                    _selectedDrive = c
                End If
            Next c

            If cbDrives.SelectedIndex <= 0 Then
                cbDrives.SelectedIndex = 0
            End If
        End If
        ' create the list
        listLocal.Items.Clear()

        ' directory up
        If Not (_localPath Is Nothing) AndAlso _localPath.Length > 3 Then
            listLocal.Items.Add(New ListViewItem("..", 0))
            dirs += 1
        Else
            _localPath = cbDrives.SelectedItem.ToString()
        End If

        Dim currentDirectory As New DirectoryInfo(_localPath)
        Dim list As DirectoryInfo() = currentDirectory.GetDirectories()

        For c = 0 To list.Length - 1
            Dim row(2) As String
            row(0) = list(c).Name
            row(1) = ""

            ' time [on read-only devices LWT is not available for dirs?]
            Try
                row(2) = FormatFileTime(list(c).LastWriteTime)
            Catch
                row(2) = ""
            End Try

            listLocal.Items.Add(New ListViewItem(row, 1 + c))
            dirs += 1
        Next c

        _localDirs = dirs

        ' files
        Dim list2 As FileInfo() = currentDirectory.GetFiles()

        For c = 0 To list2.Length - 1
            Dim row(2) As String
            row(0) = list2(c).Name
            row(1) = list2(c).Length.ToString()
            row(2) = FormatFileTime(list2(c).LastWriteTime)

            listLocal.Items.Add(New ListViewItem(row, 1 + dirs + files))
            size += list2(c).Length
            files += 1
        Next c

        ' stats
        lblLocal.Text = dirs.ToString() & " folder" & IIf(dirs > 1, "s", "") & ", " & files.ToString() & " file" & IIf(files > 1, "s", "") & ", " & System.Convert.ToInt32(size / 1024).ToString() & " K"

        ' working directory
        lblLocalDirectory.Text = currentDirectory.FullName
        _localPath = currentDirectory.FullName
    End Sub

    '' <summary>
    '' make sftp list
    '' </summary>
    Public Sub MakeSftpList()
        Dim dirs As Integer = 0
        Dim files As Integer = 0
        Dim size As Long = 0

        listServer.Items.Clear()

        ' not connected?
        If _sftp Is Nothing Then
            lblServerDirectory.Text = ""
            lblServer.Text = ""
            _serverDirs = 0
            Return
        End If

        ' make the list
        Dim list As SftpItemCollection = Nothing

        Try
            _isWorking = True
            btnStop.Visible = True
            Dim ar As IAsyncResult = _sftp.BeginGetList(CType(Nothing, String), Nothing, Nothing)
            While Not ar.IsCompleted
                Application.DoEvents()
                System.Threading.Thread.Sleep(1)
            End While
            list = _sftp.EndGetList(ar)
            list.Sort()
        Catch ex As Exception
            WriteToLog(ex)
            listServer.Items.Add(New ListViewItem("..", 0))
            Return
        Finally
            btnStop.Visible = False
            _isWorking = False
        End Try

        ' items
        listServer.Items.Add(New ListViewItem("..", 0))
        dirs += 1

        Dim c As Integer
        For c = 0 To list.Count - 1
            Dim row(2) As String
            row(0) = list(c).Name
            row(1) = Nothing
            row(2) = FormatFileTime(list(c).LastWriteTime.Value)

            If list(c).IsLink Then
                row(1) = SYMLINK
            ElseIf list(c).IsFile Then
                row(1) = list(c).Length.ToString()
                size += list(c).Length
                files += 1
            ElseIf list(c).IsDirectory Then
                dirs += 1
            End If

            Dim lvi As ListViewItem
            lvi = New ListViewItem(row)
            lvi.Tag = list(c)
            listServer.Items.Add(lvi)

        Next c

        listServer.ListViewItemSorter = New ListViewColumnSorter()
        listServer.Sort()

        ' stats
        lblServer.Text = dirs.ToString() & " folder" & IIf(dirs > 1, "s", "") & ", " & files.ToString() & " file" & IIf(files > 1, "s", "") & ", " & System.Convert.ToInt32(size / 1024).ToString() & " K"

        ' working directory
        lblServerDirectory.Text = _sftp.GetCurrentDirectory()

        _serverDirs = dirs
    End Sub

    '' <summary>
    '' constructor
    '' </summary>
    Public Sub New()
        MyBase.New()
        InitializeComponent()

        CheckForIllegalCrossThreadCalls = True
        Application.EnableVisualStyles()

        trackBar.Enabled = False
        lblServer.Text = ""
        listLocal.Focus()
        btnConnect.Select()

        MakeLocalList()
        _problemForm = New ProblemDetectedForm

        WriteToLog("Welcome to Rebex SFTP!", Color.DarkGreen)

    End Sub

    '' <summary>
    '' clean up any resources being used
    '' </summary>
    Protected Overloads Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

#Region "Windows Form Designer generated code"

    '' <summary>
    '' Required method for Designer support - do not modify
    '' the contents of this method with the code editor.
    '' </summary>
    Private Sub InitializeComponent()
        Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(MainForm))
        Me.listServer = New System.Windows.Forms.ListView()
        Me.columnName = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.columnSize = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.columnDate = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.listLocal = New System.Windows.Forms.ListView()
        Me.columnHeader1 = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.columnHeader2 = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.columnHeader3 = CType(New System.Windows.Forms.ColumnHeader(), System.Windows.Forms.ColumnHeader)
        Me.tbLog = New System.Windows.Forms.RichTextBox()
        Me.lblServer = New System.Windows.Forms.Label()
        Me.lblServerDirectory = New System.Windows.Forms.Label()
        Me.lblLocal = New System.Windows.Forms.Label()
        Me.lblLocalDirectory = New System.Windows.Forms.Label()
        Me.btnStop = New System.Windows.Forms.Button()
        Me.btnDelete = New System.Windows.Forms.Button()
        Me.btnUpload = New System.Windows.Forms.Button()
        Me.btnDownload = New System.Windows.Forms.Button()
        Me.btnRefresh = New System.Windows.Forms.Button()
        Me.btnMakeDir = New System.Windows.Forms.Button()
        Me.cbDrives = New System.Windows.Forms.ComboBox()
        Me.btnConnect = New System.Windows.Forms.Button()
        Me.gbControl = New System.Windows.Forms.GroupBox()
        Me.headerLeft = New System.Windows.Forms.PictureBox()
        Me.headerMiddle = New System.Windows.Forms.Panel()
        Me.headerRight = New System.Windows.Forms.PictureBox()
        Me.trackBar = New System.Windows.Forms.TrackBar()
        Me.lblSpeed = New System.Windows.Forms.Label()
        Me.mainStatusStrip = New System.Windows.Forms.StatusStrip()
        Me.pbTransfer = New System.Windows.Forms.ToolStripProgressBar()
        Me.lblProgress = New System.Windows.Forms.ToolStripStatusLabel()
        Me.mainSplitContainer = New MainForm.SplitContainer()
        Me.gbControl.SuspendLayout()
        CType(Me.headerLeft, System.ComponentModel.ISupportInitialize).BeginInit()
        CType(Me.headerRight, System.ComponentModel.ISupportInitialize).BeginInit()
        CType(Me.trackBar, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.mainStatusStrip.SuspendLayout()
        CType(Me.mainSplitContainer, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.mainSplitContainer.Panel1.SuspendLayout()
        Me.mainSplitContainer.Panel2.SuspendLayout()
        Me.mainSplitContainer.SuspendLayout()
        Me.SuspendLayout()
        '
        'listServer
        '
        Me.listServer.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
            Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.listServer.Columns.AddRange(New System.Windows.Forms.ColumnHeader() {Me.columnName, Me.columnSize, Me.columnDate})
        Me.listServer.FullRowSelect = True
        Me.listServer.Location = New System.Drawing.Point(12, 80)
        Me.listServer.MultiSelect = False
        Me.listServer.Name = "listServer"
        Me.listServer.Size = New System.Drawing.Size(264, 304)
        Me.listServer.TabIndex = 2
        Me.listServer.UseCompatibleStateImageBehavior = False
        Me.listServer.View = System.Windows.Forms.View.Details
        '
        'columnName
        '
        Me.columnName.Text = "Name"
        Me.columnName.Width = 120
        '
        'columnSize
        '
        Me.columnSize.Text = "Size"
        Me.columnSize.TextAlign = System.Windows.Forms.HorizontalAlignment.Right
        '
        'columnDate
        '
        Me.columnDate.Text = "Date"
        Me.columnDate.Width = 120
        '
        'listLocal
        '
        Me.listLocal.Anchor = CType(((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
            Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.listLocal.Columns.AddRange(New System.Windows.Forms.ColumnHeader() {Me.columnHeader1, Me.columnHeader2, Me.columnHeader3})
        Me.listLocal.FullRowSelect = True
        Me.listLocal.Location = New System.Drawing.Point(398, 80)
        Me.listLocal.MultiSelect = False
        Me.listLocal.Name = "listLocal"
        Me.listLocal.Size = New System.Drawing.Size(256, 342)
        Me.listLocal.TabIndex = 25
        Me.listLocal.UseCompatibleStateImageBehavior = False
        Me.listLocal.View = System.Windows.Forms.View.Details
        '
        'columnHeader1
        '
        Me.columnHeader1.Text = "Name"
        Me.columnHeader1.Width = 120
        '
        'columnHeader2
        '
        Me.columnHeader2.Text = "Size"
        Me.columnHeader2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right
        '
        'columnHeader3
        '
        Me.columnHeader3.Text = "Date"
        Me.columnHeader3.Width = 120
        '
        'tbLog
        '
        Me.tbLog.AccessibleRole = System.Windows.Forms.AccessibleRole.None
        Me.tbLog.BackColor = System.Drawing.SystemColors.Control
        Me.tbLog.Cursor = System.Windows.Forms.Cursors.IBeam
        Me.tbLog.Dock = System.Windows.Forms.DockStyle.Fill
        Me.tbLog.Font = New System.Drawing.Font("Courier New", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.tbLog.Location = New System.Drawing.Point(0, 0)
        Me.tbLog.MaxLength = 1000000
        Me.tbLog.Name = "tbLog"
        Me.tbLog.ReadOnly = True
        Me.tbLog.Size = New System.Drawing.Size(673, 96)
        Me.tbLog.TabIndex = 8
        Me.tbLog.Text = ""
        Me.tbLog.WordWrap = False
        '
        'lblServer
        '
        Me.lblServer.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.lblServer.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.lblServer.Location = New System.Drawing.Point(12, 387)
        Me.lblServer.Name = "lblServer"
        Me.lblServer.Size = New System.Drawing.Size(152, 13)
        Me.lblServer.TabIndex = 9
        Me.lblServer.Text = "12345"
        '
        'lblServerDirectory
        '
        Me.lblServerDirectory.BackColor = System.Drawing.SystemColors.InactiveCaption
        Me.lblServerDirectory.FlatStyle = System.Windows.Forms.FlatStyle.Popup
        Me.lblServerDirectory.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.lblServerDirectory.ForeColor = System.Drawing.SystemColors.InactiveCaptionText
        Me.lblServerDirectory.Location = New System.Drawing.Point(12, 64)
        Me.lblServerDirectory.Name = "lblServerDirectory"
        Me.lblServerDirectory.Size = New System.Drawing.Size(264, 16)
        Me.lblServerDirectory.TabIndex = 1
        Me.lblServerDirectory.Text = "/root"
        Me.lblServerDirectory.TextAlign = System.Drawing.ContentAlignment.MiddleLeft
        '
        'lblLocal
        '
        Me.lblLocal.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.lblLocal.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.lblLocal.Location = New System.Drawing.Point(398, 425)
        Me.lblLocal.Name = "lblLocal"
        Me.lblLocal.Size = New System.Drawing.Size(256, 13)
        Me.lblLocal.TabIndex = 12
        Me.lblLocal.Text = "12345"
        '
        'lblLocalDirectory
        '
        Me.lblLocalDirectory.BackColor = System.Drawing.SystemColors.InactiveCaption
        Me.lblLocalDirectory.FlatStyle = System.Windows.Forms.FlatStyle.Popup
        Me.lblLocalDirectory.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.lblLocalDirectory.ForeColor = System.Drawing.SystemColors.InactiveCaptionText
        Me.lblLocalDirectory.Location = New System.Drawing.Point(398, 64)
        Me.lblLocalDirectory.Name = "lblLocalDirectory"
        Me.lblLocalDirectory.Size = New System.Drawing.Size(256, 16)
        Me.lblLocalDirectory.TabIndex = 23
        Me.lblLocalDirectory.Text = "/root"
        Me.lblLocalDirectory.TextAlign = System.Drawing.ContentAlignment.MiddleLeft
        '
        'btnStop
        '
        Me.btnStop.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnStop.Location = New System.Drawing.Point(16, 232)
        Me.btnStop.Name = "btnStop"
        Me.btnStop.Size = New System.Drawing.Size(75, 23)
        Me.btnStop.TabIndex = 17
        Me.btnStop.Text = "Abort"
        Me.btnStop.Visible = False
        '
        'btnDelete
        '
        Me.btnDelete.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnDelete.Location = New System.Drawing.Point(16, 132)
        Me.btnDelete.Name = "btnDelete"
        Me.btnDelete.Size = New System.Drawing.Size(75, 23)
        Me.btnDelete.TabIndex = 11
        Me.btnDelete.Text = "Delete"
        '
        'btnUpload
        '
        Me.btnUpload.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnUpload.Location = New System.Drawing.Point(16, 208)
        Me.btnUpload.Name = "btnUpload"
        Me.btnUpload.Size = New System.Drawing.Size(75, 23)
        Me.btnUpload.TabIndex = 15
        Me.btnUpload.Text = "< Upload"
        '
        'btnDownload
        '
        Me.btnDownload.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnDownload.Location = New System.Drawing.Point(16, 184)
        Me.btnDownload.Name = "btnDownload"
        Me.btnDownload.Size = New System.Drawing.Size(75, 23)
        Me.btnDownload.TabIndex = 13
        Me.btnDownload.Text = "Download >"
        '
        'btnRefresh
        '
        Me.btnRefresh.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnRefresh.Location = New System.Drawing.Point(16, 72)
        Me.btnRefresh.Name = "btnRefresh"
        Me.btnRefresh.Size = New System.Drawing.Size(75, 23)
        Me.btnRefresh.TabIndex = 5
        Me.btnRefresh.Text = "Refresh"
        '
        'btnMakeDir
        '
        Me.btnMakeDir.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnMakeDir.Location = New System.Drawing.Point(17, 102)
        Me.btnMakeDir.Name = "btnMakeDir"
        Me.btnMakeDir.Size = New System.Drawing.Size(75, 23)
        Me.btnMakeDir.TabIndex = 7
        Me.btnMakeDir.Text = "Create folder"
        '
        'cbDrives
        '
        Me.cbDrives.ItemHeight = 13
        Me.cbDrives.Location = New System.Drawing.Point(17, 40)
        Me.cbDrives.Name = "cbDrives"
        Me.cbDrives.Size = New System.Drawing.Size(73, 21)
        Me.cbDrives.TabIndex = 4
        '
        'btnConnect
        '
        Me.btnConnect.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.btnConnect.Location = New System.Drawing.Point(16, 16)
        Me.btnConnect.Name = "btnConnect"
        Me.btnConnect.Size = New System.Drawing.Size(75, 23)
        Me.btnConnect.TabIndex = 3
        Me.btnConnect.Text = "Connect..."
        '
        'gbControl
        '
        Me.gbControl.Anchor = CType((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom), System.Windows.Forms.AnchorStyles)
        Me.gbControl.Controls.Add(Me.btnStop)
        Me.gbControl.Controls.Add(Me.btnDelete)
        Me.gbControl.Controls.Add(Me.btnUpload)
        Me.gbControl.Controls.Add(Me.btnDownload)
        Me.gbControl.Controls.Add(Me.btnRefresh)
        Me.gbControl.Controls.Add(Me.btnMakeDir)
        Me.gbControl.Controls.Add(Me.cbDrives)
        Me.gbControl.Controls.Add(Me.btnConnect)
        Me.gbControl.FlatStyle = System.Windows.Forms.FlatStyle.System
        Me.gbControl.Location = New System.Drawing.Point(286, 64)
        Me.gbControl.Name = "gbControl"
        Me.gbControl.Size = New System.Drawing.Size(106, 371)
        Me.gbControl.TabIndex = 25
        Me.gbControl.TabStop = False
        Me.gbControl.Text = "Controls"
        '
        'headerLeft
        '
        Me.headerLeft.BackColor = System.Drawing.Color.White
        Me.headerLeft.Image = CType(resources.GetObject("headerLeft.Image"), System.Drawing.Image)
        Me.headerLeft.Location = New System.Drawing.Point(0, 0)
        Me.headerLeft.Margin = New System.Windows.Forms.Padding(0)
        Me.headerLeft.Name = "headerLeft"
        Me.headerLeft.Size = New System.Drawing.Size(472, 59)
        Me.headerLeft.TabIndex = 26
        Me.headerLeft.TabStop = False
        '
        'headerMiddle
        '
        Me.headerMiddle.BackColor = System.Drawing.Color.White
        Me.headerMiddle.BackgroundImage = CType(resources.GetObject("headerMiddle.BackgroundImage"), System.Drawing.Image)
        Me.headerMiddle.Location = New System.Drawing.Point(472, 0)
        Me.headerMiddle.Name = "headerMiddle"
        Me.headerMiddle.Size = New System.Drawing.Size(64, 59)
        Me.headerMiddle.TabIndex = 27
        '
        'headerRight
        '
        Me.headerRight.Anchor = CType((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.headerRight.BackColor = System.Drawing.Color.White
        Me.headerRight.Cursor = System.Windows.Forms.Cursors.Hand
        Me.headerRight.Image = CType(resources.GetObject("headerRight.Image"), System.Drawing.Image)
        Me.headerRight.Location = New System.Drawing.Point(536, 0)
        Me.headerRight.Margin = New System.Windows.Forms.Padding(0)
        Me.headerRight.Name = "headerRight"
        Me.headerRight.Size = New System.Drawing.Size(136, 59)
        Me.headerRight.TabIndex = 28
        Me.headerRight.TabStop = False
        '
        'trackBar
        '
        Me.trackBar.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.trackBar.AutoSize = False
        Me.trackBar.Location = New System.Drawing.Point(12, 403)
        Me.trackBar.Maximum = 100
        Me.trackBar.Minimum = 1
        Me.trackBar.Name = "trackBar"
        Me.trackBar.Size = New System.Drawing.Size(264, 32)
        Me.trackBar.TabIndex = 30
        Me.trackBar.TickFrequency = 5
        Me.trackBar.Value = 100
        '
        'lblSpeed
        '
        Me.lblSpeed.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left), System.Windows.Forms.AnchorStyles)
        Me.lblSpeed.Font = New System.Drawing.Font("Microsoft Sans Serif", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.lblSpeed.Location = New System.Drawing.Point(170, 387)
        Me.lblSpeed.Name = "lblSpeed"
        Me.lblSpeed.Size = New System.Drawing.Size(106, 13)
        Me.lblSpeed.TabIndex = 31
        Me.lblSpeed.Text = "Speed: Unlimited"
        '
        'mainStatusStrip
        '
        Me.mainStatusStrip.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.pbTransfer, Me.lblProgress})
        Me.mainStatusStrip.Location = New System.Drawing.Point(0, 542)
        Me.mainStatusStrip.Name = "mainStatusStrip"
        Me.mainStatusStrip.Size = New System.Drawing.Size(673, 22)
        Me.mainStatusStrip.TabIndex = 32
        Me.mainStatusStrip.Text = "StatusStrip1"
        '
        'pbTransfer
        '
        Me.pbTransfer.Name = "pbTransfer"
        Me.pbTransfer.Size = New System.Drawing.Size(160, 16)
        '
        'lblProgress
        '
        Me.lblProgress.Name = "lblProgress"
        Me.lblProgress.Size = New System.Drawing.Size(60, 17)
        Me.lblProgress.Text = "Welcome!"
        '
        'mainSplitContainer
        '
        Me.mainSplitContainer.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
            Or System.Windows.Forms.AnchorStyles.Left) _
            Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
        Me.mainSplitContainer.Location = New System.Drawing.Point(0, 0)
        Me.mainSplitContainer.Name = "mainSplitContainer"
        Me.mainSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal
        '
        'mainSplitContainer.Panel1
        '
        Me.mainSplitContainer.Panel1.Controls.Add(Me.lblLocal)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.lblLocalDirectory)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.listLocal)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.gbControl)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.trackBar)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.lblSpeed)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.lblServer)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.listServer)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.lblServerDirectory)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.headerRight)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.headerMiddle)
        Me.mainSplitContainer.Panel1.Controls.Add(Me.headerLeft)
        Me.mainSplitContainer.Panel1MinSize = 320
        '
        'mainSplitContainer.Panel2
        '
        Me.mainSplitContainer.Panel2.Controls.Add(Me.tbLog)
        Me.mainSplitContainer.Panel2MinSize = 30
        Me.mainSplitContainer.Size = New System.Drawing.Size(673, 539)
        Me.mainSplitContainer.SplitterDistance = 439
        Me.mainSplitContainer.TabIndex = 33
        '
        'MainForm
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(673, 564)
        Me.Controls.Add(Me.mainSplitContainer)
        Me.Controls.Add(Me.mainStatusStrip)
        Me.Icon = CType(resources.GetObject("$this.Icon"), System.Drawing.Icon)
        Me.KeyPreview = True
        Me.MinimumSize = New System.Drawing.Size(664, 590)
        Me.Name = "MainForm"
        Me.Text = "Simple WinForm SFTP Client"
        Me.gbControl.ResumeLayout(False)
        CType(Me.headerLeft, System.ComponentModel.ISupportInitialize).EndInit()
        CType(Me.headerRight, System.ComponentModel.ISupportInitialize).EndInit()
        CType(Me.trackBar, System.ComponentModel.ISupportInitialize).EndInit()
        Me.mainStatusStrip.ResumeLayout(False)
        Me.mainStatusStrip.PerformLayout()
        Me.mainSplitContainer.Panel1.ResumeLayout(False)
        Me.mainSplitContainer.Panel2.ResumeLayout(False)
        CType(Me.mainSplitContainer, System.ComponentModel.ISupportInitialize).EndInit()
        Me.mainSplitContainer.ResumeLayout(False)
        Me.ResumeLayout(False)
        Me.PerformLayout()

    End Sub 'InitializeComponent 
#End Region

    '' <summary>
    '' win form client starts here
    '' </summary>
    <STAThread()> _
    Shared Sub Main()
            ' set the trial license key
            Rebex.Licensing.Key = Rebex.Samples.TrialKey.Key


        Application.EnableVisualStyles()
        Application.Run(New MainForm)
    End Sub

    '' <summary>
    '' connect/disconnect sftp server
    '' </summary>
    Private Sub btnConnect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnConnect.Click
        If Not (_sftp Is Nothing) Then
            If _isWorking Then
                Return
            End If
            ' disconnect

            trackBar.Enabled = False
            _sftp.Disconnect()
            _sftp.Dispose()
            _sftp = Nothing
            btnConnect.Text = "Connect"
            MakeSftpList()   ' clearing
            Return
        End If

        Dim connectionData As New ConnectionFormData(FileTransferComponent.Sftp)
        connectionData.LoadConfig()

        ' connection dialog
        Dim con As New ConnectionForm(connectionData)

        ' show connection dialog
        con.ShowDialog()

        ' not confirmed
        If con.DialogResult <> Windows.Forms.DialogResult.OK Then Return

        ' save confirmed data
        connectionData.SaveConfig()

        _data = connectionData

        ' create sftp object
        _sftp = New Sftp

        ' assign the logger for logging CommandSent and ResponseRead events (and exceptions thrown by Sftp class)
        _sftp.LogWriter = New RichTextBoxLogWriter(tbLog, tbLog.MaxLength, con.Data.LogLevel)

        ' set event handlers
        AddHandler _sftp.StateChanged, AddressOf StateChanged
        AddHandler _sftp.Traversing, AddressOf Traversing
        AddHandler _sftp.TransferProgressChanged, AddressOf TransferProgressChanged
        AddHandler _sftp.DeleteProgressChanged, AddressOf DeleteProgressChanged
        AddHandler _sftp.ProblemDetected, AddressOf ProblemDetected
        AddHandler _sftp.AuthenticationRequest, AddressOf AuthenticationRequest

        AddHandler _sftp.FingerprintCheck, AddressOf FingerprintCheck

        ' connect
        Try
            _sftp.Settings.UseLargeBuffers = True

            _sftp.Timeout = 30000

            ' proxy
            If con.Data.ProxyType <> FileTransferProxyType.None Then
                _sftp.Proxy = New Proxy(CType(con.Data.ProxyType, ProxyType), con.Data.ProxyHost, con.Data.ProxyPort)
                If con.Data.ProxyUser.Length > 0 Then
                    _sftp.Proxy.AuthenticationMethod = ProxyAuthentication.Basic
                    _sftp.Proxy.Credentials = New System.Net.NetworkCredential(con.Data.ProxyUser, con.Data.ProxyPassword)
                End If
            End If

            ' set preferred SSH host key algorithm 
            If _data.ServerKeyVerifyingMode = KeyVerifyingMode.LocalyStoredFingerprint Then
                _sftp.Settings.SshParameters.PreferredHostKeyAlgorithm = _data.ServerKeyAlgorithm
            End If

            Try
                btnStop.Visible = True
                _isWorking = True
                Dim ar As IAsyncResult = _sftp.BeginConnect(con.Data.ServerHost, con.Data.ServerPort, Nothing, Nothing)
                While Not ar.IsCompleted
                    Application.DoEvents()
                    System.Threading.Thread.Sleep(1)
                End While
                _sftp.EndConnect(ar)
            Finally
                btnStop.Visible = False
                _isWorking = False
            End Try

            ' login
            trackBar.Enabled = True

            If Not con.Data.UseGssApi Then
                If con.Data.PrivateKey IsNot Nothing Then
                    If String.IsNullOrEmpty(con.Data.ServerPassword) Then
                        _sftp.Login(con.Data.ServerUser, Nothing, con.Data.PrivateKey)
                    Else
                        _sftp.Login(con.Data.ServerUser, con.Data.ServerPassword, con.Data.PrivateKey)
                    End If
                Else
                    _sftp.Login(con.Data.ServerUser, con.Data.ServerPassword)
                End If
            Else
                _sftp.Login(New SshGssApiCredentials())
            End If
            btnConnect.Text = "Disconnect"
            MakeSftpList()
        Catch ex As Exception
            WriteToLog(ex)
            _sftp.Dispose()
            _sftp = Nothing
            Return
        Finally
            _isWorking = False
        End Try
    End Sub

    Private Sub AuthenticationRequest(ByVal sender As Object, ByVal e As SshAuthenticationRequestEventArgs)
        ' get responses from user
        Dim dialog As New AuthenticationRequestDialog(e)
        Dim result As DialogResult = dialog.ShowDialog()

        ' process result
        If result <> DialogResult.OK Then
            e.Cancel = True
        End If
    End Sub

    '' <summary>
    '' disconnect on exit
    '' </summary>
    Private Sub MainForm_Closing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        If Not (_sftp Is Nothing) Then
            _sftp.Dispose()
        End If
    End Sub

    '' <summary>
    '' CWD or GET file command on server side
    '' </summary>
    Private Sub ListServerClick()
        Try
            If listServer.Items.Count < 1 Then
                Return
            End If
            If listServer.SelectedItems.Count < 1 Then
                listServer.Items(0).Selected = True
            End If
            listServer.Focus()

            ' sftp state test
            If _isWorking Then
                Return
            End If
            Dim index As Integer = listServer.SelectedIndices(0)
            Dim item As ListViewItem = listServer.SelectedItems(0)

            ' parse listitem

            Dim file As Boolean = (index >= _serverDirs)
            Dim path As String = item.Text

            ' symlink directory click
            If item.SubItems.Count > 1 AndAlso item.SubItems(1).Text = SYMLINK Then
                path = _sftp.ResolveSymlink(path)
                Dim info As SftpItem = _sftp.GetInfo(path)
                Select Case info.Type
                    Case SftpItemType.Directory
                        file = False
                    Case SftpItemType.RegularFile
                        file = True
                    Case Else
                        WriteToLog("* unsupported symlink type", RichTextBoxLogWriter.COLORERROR)
                        Exit Sub
                End Select
            End If

            If file Then
                ' GET file
                DownloadFiles(path, _localPath)
            Else
                ' CWD
                Dim directory As String
                If index = 0 Then
                    directory = ".."
                Else
                    directory = path
                End If

                _sftp.ChangeDirectory(directory)

                MakeSftpList()
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    Private Overloads Sub listServer_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles listServer.KeyPress
        If e.KeyChar = ControlChars.Cr Then
            ListServerClick()
            listServer.Focus()
        End If
    End Sub

    Private Overloads Sub listServer_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles listServer.DoubleClick
        ListServerClick()
        listServer.Focus()
    End Sub

    '' <summary>
    '' change drive letter
    '' </summary>
    Private Sub cbDrives_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cbDrives.SelectedIndexChanged
        Try
            _localPath = cbDrives.SelectedItem.ToString()
            MakeLocalList()
        Catch ex As Exception
            WriteToLog(ex)
            listLocal.Items.Clear()
            lblLocalDirectory.Text = (lblLocal.Text = Nothing)
        End Try
    End Sub

    '' <summary>
    '' LCD or PUT command on local side
    '' </summary>
    Private Sub ListLocalClick()
        Try
            If listLocal.Items.Count < 1 Then Return

            If listLocal.SelectedItems.Count < 1 Then
                listLocal.Items(0).Selected = True
            End If

            listLocal.Focus()

            ' sftp state test
            Dim index As Integer = listLocal.SelectedIndices(0)
            Dim item As ListViewItem = listLocal.SelectedItems(0)

            If index >= _localDirs Then
                ' upload file
                UploadFiles(item.Text, _localPath)
            Else
                ' local change directory
                _localPath = Path.Combine(_localPath, item.Text)
                MakeLocalList()
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    Private Overloads Sub listLocal_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles listLocal.KeyPress ', listLocal.DoubleClick
        If e.KeyChar = ControlChars.Cr Then
            ListLocalClick()
            If listLocal.Items.Count > 0 Then
                listLocal.Focus()
            End If
        End If
    End Sub

    Private Overloads Sub listLocal_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles listLocal.DoubleClick 'listLocal.KeyPress, 
        ListLocalClick()
        listLocal.Focus()
    End Sub

    '' <summary>
    '' make dir
    '' </summary>
    Private Sub btnMakeDir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnMakeDir.Click
        Try
            Dim dir As New MakeDir

            ' SFTP: MKDIR
            If listActive Is listServer Then
                If _isWorking Then
                    Return
                End If
                dir.ShowDialog()
                If Not (dir.Directory Is Nothing) Then
                    _sftp.CreateDirectory(dir.Directory)
                    MakeSftpList()
                End If
            Else
                ' LOCAL: MKDIR
                dir.ShowDialog()

                If Not (dir.Directory Is Nothing) Then
                    Directory.CreateDirectory(Path.Combine(_localPath, dir.Directory))
                    MakeLocalList()
                End If
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' refresh listing
    '' </summary>
    Private Sub btnRefresh_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnRefresh.Click
        Try
            If listActive Is listServer Then
                If _isWorking Then
                    Return
                End If
                MakeSftpList()
            Else
                MakeLocalList()
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' download file or directory tree
    '' </summary>
    '' <param name="fileName">remote file name</param>
    '' <param name="localPath">local path</param>
    Private Sub DownloadFiles(ByVal fileName As String, ByVal localPath As String)
        ' ftp state test
        If _isWorking Then Return

        ' transfer
        Try
            _isWorking = True
            btnStop.Visible = True
            _problemForm.Initialize()
            _transferTime = DateTime.Now

            _sftp.BeginDownload(fileName, localPath, TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.ThrowException, _
                New AsyncCallback(AddressOf DownloadCallback), Nothing)
        Catch ex As Exception
            btnStop.Visible = False
            _isWorking = False
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' upload file or directory tree
    '' </summary>
    '' <param name="fileName">remote file name</param>
    '' <param name="localPath">local path</param>
    Private Sub UploadFiles(ByVal fileName As String, ByVal localPath As String)
        localPath = Path.Combine(localPath, fileName)

        ' ftp state test
        If _sftp Is Nothing Then
            WriteToLog("Not connected!", Color.DarkOrange)
            Return
        End If

        If _isWorking Then Return

        ' transfer
        Try
            _isWorking = True
            btnStop.Visible = True
            _problemForm.Initialize()
            _transferTime = DateTime.Now

            _sftp.BeginUpload(localPath, ".", TraversalMode.Recursive, TransferMethod.Copy, ActionOnExistingFiles.ThrowException, _
                New AsyncCallback(AddressOf UploadCallback), Nothing)
        Catch ex As Exception
            btnStop.Visible = False
            _isWorking = False
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' callback for download finished
    '' </summary>
    Public Sub DownloadCallback(ByVal asyncResult As IAsyncResult)
        Try
            _sftp.EndDownload(asyncResult)
            TransferFinished(Nothing, False)
        Catch ex As Exception
            TransferFinished(ex, False)
        End Try
    End Sub

    '' <summary>
    '' callback for upload finished
    '' </summary>
    Public Sub UploadCallback(ByVal asyncResult As IAsyncResult)
        Try
            _sftp.EndUpload(asyncResult)
            TransferFinished(Nothing, True)
        Catch ex As Exception
            TransferFinished(ex, True)
        End Try
    End Sub
    Public Delegate Sub TransferFinishedDelegate(ByVal err As Exception, ByVal refreshSftp As Boolean)

    Private Sub TransferFinished(ByVal err As Exception, ByVal refreshSftp As Boolean)
        If Me.InvokeRequired Then
            SafeInvoke(New TransferFinishedDelegate(AddressOf TransferFinished), New Object() {err, refreshSftp})
        End If

        If Not err Is Nothing Then
            WriteToLog(err)
        End If

        Try
            If refreshSftp Then
                MakeSftpList()
            Else
                MakeLocalList()
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try

        _isWorking = False
        pbTransfer.Value = 0
        btnStop.Visible = False
    End Sub

    '' <summary>
    '' show transfer status: files, bytes, time, speed
    '' </summary>
    Private Sub ShowTransferStatus(ByVal bytesTransferred As Long, ByVal filesTransferred As Integer)
        ' unknown bytes rtansferred
        If bytesTransferred = 0 Then Return

        ' files and bytes transferred
        Dim outstring As String = String.Format("{0} file{1} ({2} byte{3}) transferred in", _
         filesTransferred, IIf(filesTransferred > 1, "s", ""), _
         bytesTransferred, IIf(bytesTransferred > 1, "s", ""))

        ' time spent
        Dim ts As TimeSpan = DateTime.Now.Subtract(_transferTime)

        ' speed
        If ts.TotalSeconds > 1 Then
            outstring &= IIf(ts.Days > 0, " " & ts.Days.ToString() & " day" & IIf(ts.Days > 1, "s", Nothing), Nothing)
            outstring &= IIf(ts.Hours > 0, " " & ts.Hours.ToString() & " hour" & IIf(ts.Hours > 1, "s", Nothing), Nothing)
            outstring &= IIf(ts.Minutes > 0, " " & ts.Minutes.ToString() & " min" & IIf(ts.Minutes > 1, "s", Nothing), Nothing)
            outstring &= IIf(ts.Seconds > 0, " " & ts.Seconds.ToString() & " sec" & IIf(ts.Seconds > 1, "s", Nothing), Nothing)
        Else
            outstring &= " in " & ts.TotalSeconds.ToString() & " sec"
        End If

        Dim speed As Double = bytesTransferred / ts.TotalSeconds
        If speed < 1 Then
            outstring &= String.Format(" at {0:F3} B/s", speed)
        ElseIf speed < 1024 Then
            outstring &= String.Format(" at {0:F0} B/s", speed)
        Else
            outstring &= String.Format(" at {0:F0} KB/s", speed / 1024)
        End If

        WriteToLog("> " & outstring, RichTextBoxLogWriter.COLORCOMMAND)
    End Sub

    '' <summary>
    '' handle download button click
    '' </summary>
    Private Sub btnDownload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnDownload.Click
        Try
            If Not listActive Is listServer Then Return

            If _isWorking Then Return

            If listServer.SelectedItems.Count > 0 Then
                DownloadFiles(listServer.SelectedItems(0).Text, _localPath)
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' handle upload button click
    '' </summary>
    Private Sub btnUpload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnUpload.Click
        Try
            If listActive Is listServer Then Return

            If _isWorking Then Return

            If listLocal.SelectedItems.Count > 0 Then
                UploadFiles(listLocal.SelectedItems(0).Text, _localPath)
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' handle delete button click
    '' </summary>
    Private Sub btnDelete_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnDelete.Click
        Try
            If listActive Is listServer Then
                If _isWorking Then Return

                If listServer.SelectedItems.Count > 0 Then
                    Dim index As Integer = listServer.SelectedIndices(0)
                    Dim item As ListViewItem = listServer.SelectedItems(0)

                    If index >= _serverDirs Then
                        Dim dialog As New DeleteDialog(DeleteDialog.DialogPrompt.DeleteFile)
                        dialog.Path = item.Text
                        dialog.ShowDialog()

                        If dialog.OK Then
                            _sftp.DeleteFile(item.Text)
                            MakeSftpList()
                        End If
                    Else
                        Dim dialog As New DeleteDialog(DeleteDialog.DialogPrompt.DeleteDirectory)
                        dialog.Path = item.Text
                        dialog.ShowDialog()

                        If dialog.OK Then
                            _sftp.RemoveDirectory(item.Text)
                            MakeSftpList()
                        End If
                    End If
                End If
            Else
                If listLocal.SelectedItems.Count > 0 Then
                    Dim index As Integer = listLocal.SelectedIndices(0)
                    Dim item As ListViewItem = listLocal.SelectedItems(0)
                    Dim localPath As String = Path.Combine(_localPath, item.Text)

                    If index >= _localDirs Then
                        Dim dialog As New DeleteDialog(DeleteDialog.DialogPrompt.DeleteFile)
                        dialog.Path = item.Text
                        dialog.ShowDialog()

                        If dialog.OK Then
                            File.Delete(localPath)
                            MakeLocalList()
                        End If
                    Else
                        Dim dialog As New DeleteDialog(DeleteDialog.DialogPrompt.DeleteDirectory)
                        dialog.path = item.Text
                        dialog.ShowDialog()

                        If dialog.OK Then
                            Directory.Delete(localPath)
                            MakeLocalList()
                        End If
                    End If
                End If
            End If
        Catch ex As Exception
            WriteToLog(ex)
        End Try
    End Sub

    '' <summary>
    '' activate panel on focus event
    '' </summary>
    Private Sub listServer_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles listServer.Enter, lblServerDirectory.Click
        listActive = listServer
        lblServerDirectory.BackColor = SystemColors.ActiveCaption
        lblServerDirectory.ForeColor = SystemColors.ActiveCaptionText
        lblLocalDirectory.BackColor = SystemColors.InactiveCaption
        lblLocalDirectory.ForeColor = SystemColors.InactiveCaptionText
    End Sub

    '' <summary>
    '' activate panel on focus event
    '' </summary>
    Private Sub listLocal_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles listLocal.Enter, lblLocalDirectory.Click
        listActive = listLocal
        lblServerDirectory.BackColor = SystemColors.InactiveCaption
        lblServerDirectory.ForeColor = SystemColors.InactiveCaptionText
        lblLocalDirectory.BackColor = SystemColors.ActiveCaption
        lblLocalDirectory.ForeColor = SystemColors.ActiveCaptionText
    End Sub

    '' <summary>
    '' "smart" resizing
    '' </summary>
    Private Sub SetRightSizeValues()

        ' header
        headerMiddle.Width = headerRight.Left - headerMiddle.Left
        headerMiddle.Left = headerLeft.Right
        headerLeft.Width = 472

        ' server directory
        Dim wLeft As Integer
        wLeft = gbControl.Left - listServer.Left - 6
        listServer.Width = wLeft
        lblServerDirectory.Width = wLeft
        trackBar.Width = wLeft
        lblSpeed.Left = gbControl.Left - lblSpeed.Width - 6

        ' local directory
        listLocal.Bounds = New Rectangle(gbControl.Right + 6, listLocal.Top, headerRight.Right - gbControl.Right - 9, listLocal.Height)
        lblLocalDirectory.Bounds = New Rectangle(gbControl.Right + 6, lblLocalDirectory.Top, headerRight.Right - gbControl.Right - 9, lblLocalDirectory.Height)
        lblLocal.Left = listLocal.Left


    End Sub

    Private Sub MainForm_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Resize
        If WindowState <> FormWindowState.Minimized Then
            SetRightSizeValues()
        End If
    End Sub

    '' <summary>
    '' stop transfer when something is being transferred
    '' </summary>
    Private Sub btnStop_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnStop.Click
        _sftp.AbortTransfer()
        _isWorking = False
    End Sub

    '' <summary>
    '' go to the url
    '' </summary>
    Private Sub headerRight_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles headerRight.Click
        If Environment.OSVersion.Platform = PlatformID.Win32NT Then
            System.Diagnostics.Process.Start(URL)
        End If

    End Sub

    '' <summary>
    '' space bar for changing panels server <-> local
    '' </summary>
    Private Sub MainForm_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
        If e.KeyChar = Convert.ToChar(Keys.Space) Then
            If listActive Is listLocal Then
                listServer.Focus()
            Else
                listLocal.Focus()
            End If
        End If
    End Sub

    Private Sub trackBar_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles trackBar.Scroll
        If trackBar.Value = 100 Then
            lblSpeed.Text = "Speed: Unlimited"
            _sftp.MaxDownloadSpeed = 0
            _sftp.MaxUploadSpeed = 0
        Else
            lblSpeed.Text = String.Format("Speed: {0} kB/s", trackBar.Value)
            _sftp.MaxDownloadSpeed = trackBar.Value
            _sftp.MaxUploadSpeed = trackBar.Value
        End If
    End Sub


    ' Workaround for a bug in Mono 2.10 (where SplitContainer doesn't implement ISupportInitialize)
    ' (this is not needed on Windows)
    Public Class SplitContainer
        Inherits System.Windows.Forms.SplitContainer
        Implements System.ComponentModel.ISupportInitialize

        Private Sub ISupportInitialize_BeginInit() Implements ISupportInitialize.BeginInit
        End Sub

        Private Sub ISupportInitialize_EndInit() Implements ISupportInitialize.EndInit
        End Sub



    End Class

    Private Sub FingerprintCheck(sender As Object, e As SshFingerprintEventArgs)
        Select Case _data.ServerKeyVerifyingMode
            Case KeyVerifyingMode.AcceptAnyKey
                e.Accept = True
                Exit Select

            Case KeyVerifyingMode.LocalyStoredFingerprint
                Dim fingerprint As String = e.Fingerprint.ToString(_data.ServerKeyHashAlgorithm)
                If fingerprint = _data.ServerKeyFingerprint Then
                    e.Accept = True
                    Exit Select
                End If

                Dim resolveMode As ResolveDialogMode
                Dim resolveMessage As String

                If Not String.IsNullOrEmpty(_data.ServerKeyFingerprint) Then
                    resolveMode = ResolveDialogMode.EntryDiffer
                    resolveMessage = "Stored fingerprint does not match the server key fingerprint."
                Else
                    resolveMode = ResolveDialogMode.EntryNotFound
                    resolveMessage = "Server key is not known yet."
                End If

                ' ask user for an action, if the stored fingerprint does not match
                Dim dlg As New ResolveInvalidKeyDialog(resolveMode, resolveMessage, _data.ServerKeyHashAlgorithm, _data.ServerKeyAlgorithm, _data.ServerKeyFingerprint, e.ServerKey.KeyAlgorithm, _
                    fingerprint)

                ' a) user has not specified any solution
                If dlg.ShowDialog() <> DialogResult.OK Then
                    e.Accept = False
                    Exit Select
                End If

                ' b) user has specified a solution, that changes the current mode
                Dim mode As KeyVerifyingMode = dlg.GetChoosenMode()
                Select Case mode
                    Case KeyVerifyingMode.AcceptAnyKey
                        e.Accept = True
                        Exit Select
                    Case KeyVerifyingMode.LocalyStoredFingerprint
                        _data.ServerKeyFingerprint = fingerprint
                        _data.SaveConfig()
                        ' save the changed mode
                        e.Accept = True
                        Exit Select
                    Case KeyVerifyingMode.RejectKey
                        e.Accept = False
                        Exit Select
                    Case Else
                        Throw New InvalidOperationException(String.Format("Unsupported enum value '{0}'", mode))
                End Select
                Exit Select
            Case Else
                Throw New InvalidOperationException(String.Format("Unsupported KeyVerifyingMode '{0}'.", _data.ServerKeyVerifyingMode))
        End Select
    End Sub

End Class

Public Class ListViewColumnSorter
    Inherits MultiComparer
    Implements IComparer

    Public Sub New()
        MyBase.New(New FileSystemItemComparer(FileSystemItemComparerType.FileType, SortingOrder.Ascending), New FileSystemItemComparer(FileSystemItemComparerType.Name))
    End Sub

    Private Overloads Function Compare(x As Object, y As Object) As Integer Implements IComparer.Compare
        Dim listViewX As ListViewItem = DirectCast(x, ListViewItem)
        Dim listViewY As ListViewItem = DirectCast(y, ListViewItem)

        Dim itemX As SftpItem = DirectCast(listViewX.Tag, SftpItem)
        Dim itemY As SftpItem = DirectCast(listViewY.Tag, SftpItem)

        If itemX Is Nothing Then
            If itemY Is Nothing Then
                Return 0
            End If

            Return -1
        ElseIf itemY Is Nothing Then
            Return 1
        End If

        Return MyBase.Compare(itemX, itemY)

    End Function 'Compare
End Class 'ListViewColumnSorter