'  
'   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.IO
Imports System.Threading.Tasks

Imports Rebex.Net
Imports Rebex.IO

Public Class MainForm
    Inherits System.Windows.Forms.Form
    Private Enum BackgroundOperation
        ConnectFinished
        LoginFinished
        GetFileFinished
        DisconnectFinished
    End Enum

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        CheckForIllegalCrossThreadCalls = True
        Application.EnableVisualStyles()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    '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

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

    '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.
    Private WithEvents Label1 As System.Windows.Forms.Label
    Private WithEvents Label2 As System.Windows.Forms.Label
    Private WithEvents Label3 As System.Windows.Forms.Label
    Private WithEvents Label4 As System.Windows.Forms.Label
    Private WithEvents dlgSaveFile As System.Windows.Forms.SaveFileDialog
    Private WithEvents cmdGet As System.Windows.Forms.Button
    Private WithEvents txtUser As System.Windows.Forms.TextBox
    Private WithEvents txtPass As System.Windows.Forms.TextBox
    Private WithEvents pbProgress As System.Windows.Forms.ProgressBar
    Private WithEvents cmdAbort As System.Windows.Forms.Button
    Private WithEvents statusStrip As System.Windows.Forms.StatusStrip
    Private WithEvents statusLabel As System.Windows.Forms.ToolStripStatusLabel
    Private WithEvents Label5 As System.Windows.Forms.Label
    Private WithEvents txtPath As System.Windows.Forms.TextBox
    Private WithEvents txtServer As System.Windows.Forms.TextBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.Label1 = New System.Windows.Forms.Label()
        Me.cmdGet = New System.Windows.Forms.Button()
        Me.txtUser = New System.Windows.Forms.TextBox()
        Me.txtPass = New System.Windows.Forms.TextBox()
        Me.Label2 = New System.Windows.Forms.Label()
        Me.Label3 = New System.Windows.Forms.Label()
        Me.Label4 = New System.Windows.Forms.Label()
        Me.pbProgress = New System.Windows.Forms.ProgressBar()
        Me.dlgSaveFile = New System.Windows.Forms.SaveFileDialog()
        Me.cmdAbort = New System.Windows.Forms.Button()
        Me.statusStrip = New System.Windows.Forms.StatusStrip()
        Me.statusLabel = New System.Windows.Forms.ToolStripStatusLabel()
        Me.txtServer = New System.Windows.Forms.TextBox()
        Me.Label5 = New System.Windows.Forms.Label()
        Me.txtPath = New System.Windows.Forms.TextBox()
        Me.statusStrip.SuspendLayout()
        Me.SuspendLayout()
        '
        'Label1
        '
        Me.Label1.Location = New System.Drawing.Point(8, 8)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(48, 20)
        Me.Label1.TabIndex = 1
        Me.Label1.Text = "Server:"
        Me.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'cmdGet
        '
        Me.cmdGet.Location = New System.Drawing.Point(376, 8)
        Me.cmdGet.Name = "cmdGet"
        Me.cmdGet.Size = New System.Drawing.Size(80, 20)
        Me.cmdGet.TabIndex = 2
        Me.cmdGet.Text = "Get"
        '
        'txtUser
        '
        Me.txtUser.Location = New System.Drawing.Point(56, 32)
        Me.txtUser.Name = "txtUser"
        Me.txtUser.Size = New System.Drawing.Size(100, 20)
        Me.txtUser.TabIndex = 3
        Me.txtUser.Text = "demo"
        '
        'txtPass
        '
        Me.txtPass.Location = New System.Drawing.Point(56, 56)
        Me.txtPass.Name = "txtPass"
        Me.txtPass.PasswordChar = Global.Microsoft.VisualBasic.ChrW(42)
        Me.txtPass.Size = New System.Drawing.Size(100, 20)
        Me.txtPass.TabIndex = 4
        Me.txtPass.Text = "password"
        '
        'Label2
        '
        Me.Label2.Location = New System.Drawing.Point(24, 32)
        Me.Label2.Name = "Label2"
        Me.Label2.Size = New System.Drawing.Size(32, 20)
        Me.Label2.TabIndex = 5
        Me.Label2.Text = "User:"
        Me.Label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'Label3
        '
        Me.Label3.Location = New System.Drawing.Point(24, 56)
        Me.Label3.Name = "Label3"
        Me.Label3.Size = New System.Drawing.Size(32, 20)
        Me.Label3.TabIndex = 6
        Me.Label3.Text = "Pass:"
        Me.Label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'Label4
        '
        Me.Label4.Location = New System.Drawing.Point(192, 32)
        Me.Label4.Name = "Label4"
        Me.Label4.Size = New System.Drawing.Size(176, 48)
        Me.Label4.TabIndex = 7
        Me.Label4.Text = "This sample demonstrates using asynchronous methods. It does not block the user interface at all!"
        '
        'pbProgress
        '
        Me.pbProgress.Location = New System.Drawing.Point(8, 79)
        Me.pbProgress.Name = "pbProgress"
        Me.pbProgress.Size = New System.Drawing.Size(448, 20)
        Me.pbProgress.TabIndex = 8
        '
        'dlgSaveFile
        '
        Me.dlgSaveFile.FileName = "doc1"
        '
        'cmdAbort
        '
        Me.cmdAbort.Enabled = False
        Me.cmdAbort.Location = New System.Drawing.Point(376, 32)
        Me.cmdAbort.Name = "cmdAbort"
        Me.cmdAbort.Size = New System.Drawing.Size(80, 20)
        Me.cmdAbort.TabIndex = 9
        Me.cmdAbort.Text = "Abort"
        '
        'statusStrip
        '
        Me.statusStrip.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.statusLabel})
        Me.statusStrip.Location = New System.Drawing.Point(0, 108)
        Me.statusStrip.Name = "statusStrip"
        Me.statusStrip.Size = New System.Drawing.Size(458, 16)
        Me.statusStrip.SizingGrip = False
        Me.statusStrip.TabIndex = 10
        '
        'statusLabel
        '
        Me.statusLabel.Name = "statusLabel"
        Me.statusLabel.Size = New System.Drawing.Size(90, 16)
        Me.statusLabel.Text = "Ready"
        '
        'txtServer
        '
        Me.txtServer.Location = New System.Drawing.Point(56, 8)
        Me.txtServer.Name = "txtServer"
        Me.txtServer.Size = New System.Drawing.Size(100, 20)
        Me.txtServer.TabIndex = 11
        Me.txtServer.Text = "test.rebex.net"
        '
        'Label5
        '
        Me.Label5.Location = New System.Drawing.Point(160, 8)
        Me.Label5.Name = "Label5"
        Me.Label5.Size = New System.Drawing.Size(32, 23)
        Me.Label5.TabIndex = 12
        Me.Label5.Text = "Path:"
        Me.Label5.TextAlign = System.Drawing.ContentAlignment.MiddleRight
        '
        'txtPath
        '
        Me.txtPath.Location = New System.Drawing.Point(192, 8)
        Me.txtPath.Name = "txtPath"
        Me.txtPath.Size = New System.Drawing.Size(176, 20)
        Me.txtPath.TabIndex = 13
        Me.txtPath.Text = "/pub/example/readme.txt"
        '
        'MainForm
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(458, 124)
        Me.Controls.Add(Me.txtPath)
        Me.Controls.Add(Me.Label5)
        Me.Controls.Add(Me.txtServer)
        Me.Controls.Add(Me.statusStrip)
        Me.Controls.Add(Me.cmdAbort)
        Me.Controls.Add(Me.pbProgress)
        Me.Controls.Add(Me.Label4)
        Me.Controls.Add(Me.Label3)
        Me.Controls.Add(Me.Label2)
        Me.Controls.Add(Me.txtPass)
        Me.Controls.Add(Me.txtUser)
        Me.Controls.Add(Me.cmdGet)
        Me.Controls.Add(Me.Label1)
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
        Me.MaximizeBox = False
        Me.Name = "MainForm"
        Me.Text = "WinForm Sample SFTP Downloader"
        Me.statusStrip.ResumeLayout(False)
        Me.statusStrip.PerformLayout()
        Me.ResumeLayout(False)
        Me.PerformLayout()

    End Sub

#End Region

    Const Title As String = "WinForm Sample SFTP Downloader"

    WithEvents ftp As Sftp = Nothing
    Dim user As String = Nothing
    Dim pass As String = Nothing
    Dim server As String = Nothing
    Dim remotePath As String = Nothing
    Dim output As Stream = Nothing

    Private Sub cmdGet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdGet.Click
        ' Validate server information
        If (txtServer.Text.Trim() = "") Then
            MessageBox.Show("Server address can't be empty.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        If (txtPath.Text.Trim() = "") Then
            MessageBox.Show("Path can't be empty.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        If (txtPath.Text.EndsWith("/")) Then
            MessageBox.Show("Path does not specify a remote file.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        server = txtServer.Text
        remotePath = txtPath.Text

        ' Display File Save dialog and let user select destination
        dlgSaveFile.FileName = System.IO.Path.GetFileName(Path.GetFileName(remotePath))
        dlgSaveFile.Filter = "All files (*.*)|*.*"
        If dlgSaveFile.ShowDialog() <> System.Windows.Forms.DialogResult.OK Then
            Exit Sub
        End If

        user = txtUser.Text
        pass = txtPass.Text
        Try
            cmdGet.Enabled = False
            ' Create an instance of Sftp class and start connecting
            ftp = New Sftp
            statusLabel.Text = "Connecting..."

            Dim connect As Task = ftp.ConnectAsync(server, Sftp.DefaultPort, BackgroundOperation.ConnectFinished)
            connect.ContinueWith(AddressOf TaskFinished)
        Catch err As Exception
            ' If an error occurs, clean up and display the message
            CleanUp()
            ShowMessage(err)
        End Try

    End Sub

    ''<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

    Private Sub TaskFinished(ByVal finishedTask As Task)
        If Me.InvokeRequired Then
            SafeInvoke(New Action(Of Task)(AddressOf TaskFinished), New Object() {finishedTask})
            Return
        End If

        Try
            If finishedTask.IsFaulted Then
                Throw finishedTask.Exception
            End If

            If finishedTask.IsCanceled Then
                Throw New OperationCanceledException("Operation has been canceled.")
            End If

            Select Case (CType(finishedTask.AsyncState, BackgroundOperation))
                Case BackgroundOperation.ConnectFinished
                    statusLabel.Text = "Logging in..."
                    ftp.LoginAsync(user, pass, BackgroundOperation.LoginFinished).ContinueWith(AddressOf TaskFinished)
                Case BackgroundOperation.LoginFinished
                    ' Open the file
                    output = dlgSaveFile.OpenFile
                    If (output Is Nothing) Then
                        Throw New Exception("Unable to open file.")
                    End If

                    ' Download the file
                    statusLabel.Text = "Downloading..."
                    ftp.GetFileAsync(remotePath, output, BackgroundOperation.GetFileFinished).ContinueWith(AddressOf TaskFinished)
                    cmdAbort.Enabled = True
                Case BackgroundOperation.GetFileFinished
                    cmdAbort.Enabled = False
                    ' Close the file
                    output.Close()
                    output = Nothing
                    MessageBox.Show("Transfer is finished.", Title, MessageBoxButtons.OK, MessageBoxIcon.Information)
                    statusLabel.Text = "Disconnecting..."
                    ftp.DisconnectAsync(BackgroundOperation.DisconnectFinished).ContinueWith(AddressOf TaskFinished)
                Case BackgroundOperation.DisconnectFinished
                    ' Clean up
                    CleanUp()
            End Select
        Catch err As Exception
            ' If an error occurs, clean up and display the message
            CleanUp()
            ShowMessage(err)
        End Try
    End Sub

    Private Sub CleanUp()
        ' Dispose Sftp object
        If Not ftp Is Nothing Then
            ftp.Dispose()
            ftp = Nothing
        End If

        If (Not (output) Is Nothing) Then
            output.Close()
            output = Nothing
        End If

        ' Set the window title back to the original title
        Me.Text = Title

        ' Reset the progress bar
        pbProgress.Value = 0

        ' Reset the status bar
        statusLabel.Text = "Ready"

        ' Disable Abort button
        cmdAbort.Enabled = False

        ' Enable Get button
        cmdGet.Enabled = True
    End Sub

    Private Sub ShowMessage(ByVal err As Exception)
        If (TypeOf err Is AggregateException) Then
            Dim aggregateEx As AggregateException = CType(err, AggregateException)
            For Each inner As Exception In aggregateEx.InnerExceptions()
                ShowMessage(inner)
            Next
            Return
        End If
        Dim ftpErr As SftpException = TryCast(err, SftpException)

        If ((Not ftpErr Is Nothing) AndAlso (ftpErr.Status = SftpExceptionStatus.AsyncError)) Then
            err = ftpErr.InnerException
        End If

        If ((Not ftpErr Is Nothing) AndAlso (ftpErr.Status = SftpExceptionStatus.OperationAborted)) Then
            MessageBox.Show(err.Message, Title, MessageBoxButtons.OK, MessageBoxIcon.Information)
        Else
            MessageBox.Show(err.Message, Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

    Private Sub Abort()
        ' The ftp object may have been set to nothing while we were waiting
        ' for user's response in MainForm_Closing callback. It might be being
        ' set to nothing even at this moment by another thread. By using our
        ' own temporary object (which cannot be set nothing by other threads),
        ' we are able to make this method thread-safe.
        ' Also please note that even though the Sftp object itself might have
        ' been disconnected and disposed, it is still safe to call the Abort
        ' method - it will do nothing if no asynchronous operation is taking
        ' place. This allows us to avoid using synchronization classes
        ' such as Monitor or Mutex and still being thread-safe.
        Dim tmp As Sftp = ftp
        If Not tmp Is Nothing Then
            tmp.AbortTransfer()
            statusLabel.Text = "Aborting..."
            cmdAbort.Enabled = False
        End If
    End Sub

    Private Sub cmdAbort_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAbort.Click
        Abort()
    End Sub

    Private Sub MainForm_Closing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        ' If a form is closing and transfer is in progress, ask the user what to do
        If ftp Is Nothing Then
            Exit Sub
        End If
        Dim res As DialogResult = MessageBox.Show("Transfer is in progress, do you want to abort it?", Title, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning)
        If res <> System.Windows.Forms.DialogResult.Yes Then
            e.Cancel = True
        Else
            Abort()
        End If
    End Sub

    Private Sub TransferProgressChangedHandler(ByVal sender As Object, ByVal e As SftpTransferProgressChangedEventArgs) Handles ftp.TransferProgressChanged
        ' Gets called if there is a progress in transfer, so update our display accordingly
        If e.TransferState = TransferProgressState.DataBlockProcessed Then
            pbProgress.Value = CType(e.ProgressPercentage, Integer)
            Text = String.Format("{0} bytes transferred.", e.CurrentFileBytesTransferred)
        End If
    End Sub

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


        Application.EnableVisualStyles()
        Application.Run(New MainForm)
    End Sub 'Main
End Class
