'  
'   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.Data
Imports System.Threading
Imports System.IO
Imports Rebex.Net

Public Class MainForm
    Inherits System.Windows.Forms.Form
    Private lblRemote As System.Windows.Forms.Label
    Private lblLocal As System.Windows.Forms.Label
    Private txtRemote As System.Windows.Forms.TextBox
    Private WithEvents cmdDownload As System.Windows.Forms.Button
    Private WithEvents cmdUpload As System.Windows.Forms.Button
    Private pbProgress As System.Windows.Forms.ProgressBar
    Private statusStrip As System.Windows.Forms.StatusStrip
    Private statusLabel As System.Windows.Forms.ToolStripStatusLabel
    Private WithEvents cmdAbort As System.Windows.Forms.Button
    Private txtLocal As System.Windows.Forms.TextBox
    Private txtPort As System.Windows.Forms.TextBox
    Private txtHostname As System.Windows.Forms.TextBox
    Private txtUsername As System.Windows.Forms.TextBox
    Private txtPassword As System.Windows.Forms.TextBox
    Private lblHostname As System.Windows.Forms.Label
    Private lblUsername As System.Windows.Forms.Label
    Private lblPort As System.Windows.Forms.Label
    Private lblPassword As System.Windows.Forms.Label

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    ' Instance of Rebex.Net.Sftp
    Private _ftp As Sftp

    ' Remote offset to start at
    Private _remoteOffset As Long

    'Connection info
    Dim localPath As String
    Dim remotePath As String
    Dim hostname As String
    Dim username As String
    Dim password As String
    Dim port As Integer

    ' This is called at the change of SFTP object state
    Private Sub StateChanged(ByVal sender As Object, ByVal e As SftpStateChangedEventArgs)
        statusLabel.Text = e.NewState.ToString()
    End Sub 'StateChanged

    ' This is called on transfer progress
    Private Sub TransferProgress(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs)
        pbProgress.Value = Convert.ToInt32(e.CurrentFileProgressPercentage)
        statusLabel.Text = "Transferred " & e.CurrentFileBytesTransferred & " bytes"
    End Sub 'TransferProgress

    Public Sub New()
        MyBase.New()

        CheckForIllegalCrossThreadCalls = True
        Application.EnableVisualStyles()

        InitializeComponent()

        OperationDone()
    End Sub 'New


    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides 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"

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.txtLocal = New System.Windows.Forms.TextBox()
        Me.lblRemote = New System.Windows.Forms.Label()
        Me.lblLocal = New System.Windows.Forms.Label()
        Me.txtRemote = New System.Windows.Forms.TextBox()
        Me.cmdDownload = New System.Windows.Forms.Button()
        Me.cmdUpload = New System.Windows.Forms.Button()
        Me.pbProgress = New System.Windows.Forms.ProgressBar()
        Me.statusStrip = New System.Windows.Forms.StatusStrip()
        Me.statusLabel = New System.Windows.Forms.ToolStripStatusLabel()
        Me.cmdAbort = New System.Windows.Forms.Button()
        Me.txtPort = New System.Windows.Forms.TextBox()
        Me.txtHostname = New System.Windows.Forms.TextBox()
        Me.txtUsername = New System.Windows.Forms.TextBox()
        Me.txtPassword = New System.Windows.Forms.TextBox()
        Me.lblHostname = New System.Windows.Forms.Label()
        Me.lblUsername = New System.Windows.Forms.Label()
        Me.lblPort = New System.Windows.Forms.Label()
        Me.lblPassword = New System.Windows.Forms.Label()
        Me.statusStrip.SuspendLayout()
        Me.SuspendLayout()
        '
        'txtLocal
        '
        Me.txtLocal.Location = New System.Drawing.Point(80, 56)
        Me.txtLocal.Name = "txtLocal"
        Me.txtLocal.Size = New System.Drawing.Size(248, 20)
        Me.txtLocal.TabIndex = 0
        '
        'lblRemote
        '
        Me.lblRemote.Location = New System.Drawing.Point(8, 80)
        Me.lblRemote.Name = "lblRemote"
        Me.lblRemote.Size = New System.Drawing.Size(72, 24)
        Me.lblRemote.TabIndex = 1
        Me.lblRemote.Text = "Remote file:"
        Me.lblRemote.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'lblLocal
        '
        Me.lblLocal.Location = New System.Drawing.Point(8, 56)
        Me.lblLocal.Name = "lblLocal"
        Me.lblLocal.Size = New System.Drawing.Size(72, 24)
        Me.lblLocal.TabIndex = 2
        Me.lblLocal.Text = "Local file:"
        Me.lblLocal.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'txtRemote
        '
        Me.txtRemote.Location = New System.Drawing.Point(80, 80)
        Me.txtRemote.Name = "txtRemote"
        Me.txtRemote.Size = New System.Drawing.Size(248, 20)
        Me.txtRemote.TabIndex = 1
        Me.txtRemote.Text = "/pub/example/readme.txt"
        '
        'cmdDownload
        '
        Me.cmdDownload.Location = New System.Drawing.Point(192, 144)
        Me.cmdDownload.Name = "cmdDownload"
        Me.cmdDownload.Size = New System.Drawing.Size(64, 20)
        Me.cmdDownload.TabIndex = 9
        Me.cmdDownload.Text = "Download"
        '
        'cmdUpload
        '
        Me.cmdUpload.Location = New System.Drawing.Point(120, 144)
        Me.cmdUpload.Name = "cmdUpload"
        Me.cmdUpload.Size = New System.Drawing.Size(64, 20)
        Me.cmdUpload.TabIndex = 8
        Me.cmdUpload.Text = "Upload"
        '
        'pbProgress
        '
        Me.pbProgress.Location = New System.Drawing.Point(8, 144)
        Me.pbProgress.Name = "pbProgress"
        Me.pbProgress.Size = New System.Drawing.Size(104, 20)
        Me.pbProgress.TabIndex = 8
        '
        'statusStrip
        '
        Me.statusStrip.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.statusLabel})
        Me.statusStrip.Location = New System.Drawing.Point(0, 166)
        Me.statusStrip.Name = "statusStrip"
        Me.statusStrip.Size = New System.Drawing.Size(334, 22)
        Me.statusStrip.TabIndex = 9
        '
        'statusLabel
        '
        Me.statusLabel.Name = "statusLabel"
        Me.statusLabel.Size = New System.Drawing.Size(90, 21)
        Me.statusLabel.Text = "Ready"
        '
        'cmdAbort
        '
        Me.cmdAbort.Location = New System.Drawing.Point(264, 144)
        Me.cmdAbort.Name = "cmdAbort"
        Me.cmdAbort.Size = New System.Drawing.Size(64, 20)
        Me.cmdAbort.TabIndex = 10
        Me.cmdAbort.Text = "Abort"
        '
        'txtPort
        '
        Me.txtPort.Location = New System.Drawing.Point(272, 8)
        Me.txtPort.Name = "txtPort"
        Me.txtPort.Size = New System.Drawing.Size(56, 20)
        Me.txtPort.TabIndex = 3
        Me.txtPort.Text = "22"
        '
        'txtHostname
        '
        Me.txtHostname.Location = New System.Drawing.Point(80, 8)
        Me.txtHostname.Name = "txtHostname"
        Me.txtHostname.Size = New System.Drawing.Size(152, 20)
        Me.txtHostname.TabIndex = 2
        Me.txtHostname.Text = "test.rebex.net"
        '
        'txtUsername
        '
        Me.txtUsername.Location = New System.Drawing.Point(80, 32)
        Me.txtUsername.Name = "txtUsername"
        Me.txtUsername.Size = New System.Drawing.Size(88, 20)
        Me.txtUsername.TabIndex = 6
        Me.txtUsername.Text = "demo"
        '
        'txtPassword
        '
        Me.txtPassword.Location = New System.Drawing.Point(240, 32)
        Me.txtPassword.Name = "txtPassword"
        Me.txtPassword.PasswordChar = Global.Microsoft.VisualBasic.ChrW(42)
        Me.txtPassword.Size = New System.Drawing.Size(88, 20)
        Me.txtPassword.TabIndex = 7
        Me.txtPassword.Text = "password"
        '
        'lblHostname
        '
        Me.lblHostname.Location = New System.Drawing.Point(8, 8)
        Me.lblHostname.Name = "lblHostname"
        Me.lblHostname.Size = New System.Drawing.Size(72, 24)
        Me.lblHostname.TabIndex = 15
        Me.lblHostname.Text = "Hostname:"
        Me.lblHostname.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'lblUsername
        '
        Me.lblUsername.Location = New System.Drawing.Point(8, 32)
        Me.lblUsername.Name = "lblUsername"
        Me.lblUsername.Size = New System.Drawing.Size(72, 24)
        Me.lblUsername.TabIndex = 16
        Me.lblUsername.Text = "Username:"
        Me.lblUsername.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'lblPort
        '
        Me.lblPort.Location = New System.Drawing.Point(240, 8)
        Me.lblPort.Name = "lblPort"
        Me.lblPort.Size = New System.Drawing.Size(32, 24)
        Me.lblPort.TabIndex = 17
        Me.lblPort.Text = "Port:"
        Me.lblPort.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'lblPassword
        '
        Me.lblPassword.Location = New System.Drawing.Point(176, 32)
        Me.lblPassword.Name = "lblPassword"
        Me.lblPassword.Size = New System.Drawing.Size(64, 24)
        Me.lblPassword.TabIndex = 18
        Me.lblPassword.Text = "Password:"
        Me.lblPassword.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'MainForm
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(334, 188)
        Me.Controls.Add(Me.lblPassword)
        Me.Controls.Add(Me.lblPort)
        Me.Controls.Add(Me.lblUsername)
        Me.Controls.Add(Me.lblHostname)
        Me.Controls.Add(Me.txtPassword)
        Me.Controls.Add(Me.txtUsername)
        Me.Controls.Add(Me.txtHostname)
        Me.Controls.Add(Me.txtPort)
        Me.Controls.Add(Me.cmdAbort)
        Me.Controls.Add(Me.statusStrip)
        Me.Controls.Add(Me.pbProgress)
        Me.Controls.Add(Me.cmdUpload)
        Me.Controls.Add(Me.cmdDownload)
        Me.Controls.Add(Me.txtRemote)
        Me.Controls.Add(Me.lblLocal)
        Me.Controls.Add(Me.lblRemote)
        Me.Controls.Add(Me.txtLocal)
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.Name = "MainForm"
        Me.Text = "Resumable SFTP Transfer"
        Me.statusStrip.ResumeLayout(False)
        Me.statusStrip.PerformLayout()
        Me.ResumeLayout(False)
        Me.PerformLayout()

    End Sub 'InitializeComponent 
#End Region


    <STAThread()>
    Shared Sub Main()
            ' set the trial license key
            Rebex.Licensing.Key = Rebex.Samples.TrialKey.Key


        Application.Run(New MainForm)
    End Sub 'Main

    ' Download operation.
    Private Async Sub Download()
        PrepareOperation()

        Dim local As Stream = Nothing
        Try
            If File.Exists(localPath) Then
                ' If the local file exists, ask whether to resume transfer.
                Dim res As DialogResult = ShowMessage("Local file already exists. Do you want to resume the transfer? (If you answer 'no', the file will be overwritten.)", "Question", MessageBoxButtons.YesNoCancel)
                Select Case res
                    Case System.Windows.Forms.DialogResult.Yes
                        ' Open for writing and seek to the end if we are resuming
                        local = File.OpenWrite(localPath)
                        local.Seek(0, SeekOrigin.End)
                    Case System.Windows.Forms.DialogResult.No
                        local = File.Create(localPath)
                    Case Else
                        Return
                End Select
            Else
                local = File.Create(localPath)
            End If

            ' Connect to the server.
            Await _ftp.ConnectAsync(hostname, port)

            ' Log in.
            Await _ftp.LoginAsync(username, password)

            ' Check whether the file exists and determine its length.
            Dim info As SftpItem = Await _ftp.GetInfoAsync(remotePath, False)
            If info Is Nothing OrElse Not info.IsFile Then
                ShowMessage("Unable to download, remote file does not exist.", "Error")
                Return
            End If
            _remoteOffset = info.Length

            If local.Length > _remoteOffset Then
                ShowMessage("Unable to resume transfer, local file is longer than remote file.", "Error")
                Return
            End If

            ' Do the transfer if there data is available to download.
            If local.Length < _remoteOffset Then
                cmdAbort.Enabled = True
                _remoteOffset = local.Position
                Await _ftp.GetFileAsync(remotePath, local, _remoteOffset, -1)
            End If

            ShowMessage("Download finished.", "Info")
        Catch cancel As System.Threading.Tasks.TaskCanceledException
            ShowMessage("Transfer aborted.", "Info")
        Catch ex As SftpException
            ShowMessage(ex.ToString(), "Error")
        Catch x As Exception
            ShowMessage(x.ToString(), "Error")
        Finally
            If Not (local Is Nothing) Then
                local.Close()
            End If

            Disconnect()
        End Try
    End Sub 'BackgroundDownload



    ' Download operation.
    Private Async Sub Upload()
        PrepareOperation()
        Dim local As Stream = Nothing
        Try
            local = File.OpenRead(localPath)

            ' Connect to the server.
            Await _ftp.ConnectAsync(hostname, port)

            ' Log in.
            Await _ftp.LoginAsync(username, password)

            ' Check whether the file exists and determine its length.
            Dim info As SftpItem = Await _ftp.GetInfoAsync(remotePath, False)
            If info Is Nothing OrElse Not info.IsFile Then
                _remoteOffset = -1
            Else
                _remoteOffset = info.Length
            End If

            If _remoteOffset >= 0 Then
                ' If the remote file exists, ask whether to resume transfer.
                Dim res As DialogResult = ShowMessage("Remote file already exists. Do you want to resume the transfer? (If you answer 'no', the file will be overwritten.)", "Question", MessageBoxButtons.YesNoCancel)
                Select Case res
                    Case System.Windows.Forms.DialogResult.Yes

                    Case System.Windows.Forms.DialogResult.No
                        _remoteOffset = 0
                    Case Else
                        Return
                End Select
            Else
                _remoteOffset = 0
            End If

            If _remoteOffset > local.Length Then
                ShowMessage("Unable to resume transfer, remote file is longer than local file.", "Error")
                Return
            End If

            ' Do the transfer if there data is available to upload.
            If _remoteOffset < local.Length Then
                cmdAbort.Enabled = True
                local.Seek(_remoteOffset, SeekOrigin.Begin)
                Await _ftp.PutFileAsync(local, remotePath, _remoteOffset, -1)
            End If

            ShowMessage("Upload finished.", "Info")
        Catch cancel As System.Threading.Tasks.TaskCanceledException
            ShowMessage("Transfer aborted.", "Info")
        Catch ex As SftpException
            ShowMessage(ex.ToString(), "Error")
        Catch x As Exception
            ShowMessage(x.ToString(), "Error")
        Finally
            If Not (local Is Nothing) Then
                local.Close()
            End If

            Disconnect()
        End Try
    End Sub 'BackgroundUpload

    Private Async Sub Disconnect()
        Await _ftp.DisconnectAsync()
        _ftp.Dispose()
        _ftp = Nothing
        OperationDone()
    End Sub

    Private Sub OperationDone()
        ' Reset the progress bar
        pbProgress.Value = 0

        ' Enable editable boxes and buttons
        txtHostname.Enabled = True
        txtPort.Enabled = True
        txtUsername.Enabled = True
        txtPassword.Enabled = True
        txtRemote.Enabled = True
        txtLocal.Enabled = True
        cmdDownload.Enabled = True
        cmdUpload.Enabled = True

        cmdAbort.Enabled = False
    End Sub


    ' Starts the background transfer operation.
    Private Sub PrepareOperation()
        ' Initialize an instance of SFTP class and set up events
        _ftp = New Sftp
        AddHandler _ftp.StateChanged, AddressOf Me.StateChanged
        AddHandler _ftp.TransferProgressChanged, AddressOf Me.TransferProgress

        ' Initialize connection info
        hostname = txtHostname.Text
        port = Convert.ToInt32(txtPort.Text)
        password = txtPassword.Text
        username = txtUsername.Text
        remotePath = txtRemote.Text
        localPath = txtLocal.Text

        ' Disable editable boxes and buttons
        txtHostname.Enabled = False
        txtPort.Enabled = False
        txtPassword.Enabled = False
        txtUsername.Enabled = False
        txtRemote.Enabled = False
        txtLocal.Enabled = False
        cmdDownload.Enabled = False
        cmdUpload.Enabled = False
    End Sub 'StartBackgroundThread


    ' Abort the background thread on exit if it is active.
    Protected Overrides Sub OnFormClosed(ByVal e As FormClosedEventArgs)
        MyBase.OnFormClosed(e)

        Dim sftpTmp As Sftp = _ftp
        If (sftpTmp IsNot Nothing) Then
            Try
                If (sftpTmp.GetConnectionState().Connected) Then
                    sftpTmp.AbortTransfer()
                End If
            Catch x As ObjectDisposedException
                ' ftp object is already disposed, nothing to abort
            End Try
        End If

    End Sub 'OnFormClosed

    Private Sub cmdDownload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdDownload.Click
        Download()
    End Sub 'cmdDownload_Click


    Private Sub cmdUpload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdUpload.Click
        Upload()
    End Sub 'cmdUpload_Click


    ' Abort transfer if background operation is active.
    Private Sub cmdAbort_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdAbort.Click
        statusLabel.Text = "Aborting"

        _ftp.AbortTransfer()

        cmdAbort.Enabled = False
    End Sub 'cmdAbort_Click

    Private Function ShowMessage(text As String, title As String) As DialogResult
        Return ShowMessage(text, title, MessageBoxButtons.OK)
    End Function


    Private Function ShowMessage(text As String, title As String, buttons As MessageBoxButtons) As DialogResult
        ' on Mono, MessageBox.Show has to be called from the application's main thread
        If Me.InvokeRequired Then
            Return DirectCast(Me.Invoke(DirectCast(AddressOf ShowMessage, ShowMessageDelegate), text, title, buttons), DialogResult)
        End If

        Return MessageBox.Show(text, title, buttons)
    End Function

    Private Delegate Function ShowMessageDelegate(text As String, title As String, buttons As MessageBoxButtons) As DialogResult
End Class 'MainForm