'  
'   Rebex Sample Code License
' 
'   Copyright 2026, 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.IO
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Threading.Tasks
Imports Rebex.Net
Imports Rebex.IO

Public Class MainForm
    Inherits System.Windows.Forms.Form
    Private Enum BackgroundOperation As Integer
        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 txtUrl As System.Windows.Forms.TextBox
    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 cbSecure As System.Windows.Forms.CheckBox
    Private WithEvents cbAcceptAll As System.Windows.Forms.CheckBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.txtUrl = New System.Windows.Forms.TextBox()
        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.cbSecure = New System.Windows.Forms.CheckBox()
        Me.cbAcceptAll = New System.Windows.Forms.CheckBox()
        Me.statusStrip.SuspendLayout()
        Me.SuspendLayout()
        '
        'txtUrl
        '
        Me.txtUrl.Location = New System.Drawing.Point(40, 8)
        Me.txtUrl.Name = "txtUrl"
        Me.txtUrl.Size = New System.Drawing.Size(336, 20)
        Me.txtUrl.TabIndex = 0
        Me.txtUrl.Text = "ftp://test.rebex.net/pub/example/readme.txt"
        '
        'Label1
        '
        Me.Label1.Location = New System.Drawing.Point(8, 8)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(32, 20)
        Me.Label1.TabIndex = 1
        Me.Label1.Text = "URL:"
        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(40, 32)
        Me.txtUser.Name = "txtUser"
        Me.txtUser.Size = New System.Drawing.Size(120, 20)
        Me.txtUser.TabIndex = 3
        Me.txtUser.Text = "demo"
        '
        'txtPass
        '
        Me.txtPass.Location = New System.Drawing.Point(40, 56)
        Me.txtPass.Name = "txtPass"
        Me.txtPass.PasswordChar = Global.Microsoft.VisualBasic.ChrW(42)
        Me.txtPass.Size = New System.Drawing.Size(120, 20)
        Me.txtPass.TabIndex = 4
        Me.txtPass.Text = "password"
        '
        'Label2
        '
        Me.Label2.Location = New System.Drawing.Point(8, 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(8, 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(168, 32)
        Me.Label4.Name = "Label4"
        Me.Label4.Size = New System.Drawing.Size(208, 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, 112)
        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, 132)
        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"
        '
        'cbSecure
        '
        Me.cbSecure.Location = New System.Drawing.Point(40, 80)
        Me.cbSecure.Name = "cbSecure"
        Me.cbSecure.Size = New System.Drawing.Size(80, 32)
        Me.cbSecure.TabIndex = 12
        Me.cbSecure.Text = "TLS/SSL"
        '
        'cbAcceptAll
        '
        Me.cbAcceptAll.Location = New System.Drawing.Point(136, 80)
        Me.cbAcceptAll.Name = "cbAcceptAll"
        Me.cbAcceptAll.Size = New System.Drawing.Size(312, 32)
        Me.cbAcceptAll.TabIndex = 13
        Me.cbAcceptAll.Text = "Do not validate any server certificates - this is insecure but useful for testing" &
    "."
        '
        'MainForm
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(458, 148)
        Me.Controls.Add(Me.cbAcceptAll)
        Me.Controls.Add(Me.cbSecure)
        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.Controls.Add(Me.txtUrl)
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
        Me.MaximizeBox = False
        Me.Name = "MainForm"
        Me.Text = "WinForm Sample FTP Downloader"
        Me.statusStrip.ResumeLayout(False)
        Me.statusStrip.PerformLayout()
        Me.ResumeLayout(False)
        Me.PerformLayout()

    End Sub

#End Region

    Const Title As String = "WinForm Sample FTP Downloader"

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

    Private Async Sub cmdGet_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdGet.Click
        ' Validate URL and get the path
        Dim url As Uri
        Try
            url = New Uri(txtUrl.Text)
        Catch err As Exception
            ShowMessage(err)
            Exit Sub
        End Try
        If Not url.Scheme.Equals("ftp") Then
            MessageBox.Show("URL must be an FTP URL.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End If
        If url.AbsolutePath.EndsWith("/") Then
            MessageBox.Show("URL does not specify a remote file.", Title, MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End If
        remotePath = url.AbsolutePath

        ' Display File Save dialog and let user select destination
        dlgSaveFile.FileName = System.IO.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 Ftp class and start connecting
            ftp = New Ftp
            cmdAbort.Enabled = True
            statusLabel.Text = "Connecting..."

            _ftp.Settings.SslAcceptAllCertificates = cbAcceptAll.Checked

            Dim security As SslMode
            If cbSecure.Checked Then
                security = SslMode.Explicit
            Else
                security = SslMode.None
            End If

            Await ftp.ConnectAsync(url.Host, url.Port, security)

            statusLabel.Text = "Logging in..."
            Await ftp.LoginAsync(user, pass)

            ' 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..."
            Await ftp.GetFileAsync(remotePath, output)

            ' Close the file
            output.Close()
            output = Nothing
            MessageBox.Show("Transfer is finished.", Title, MessageBoxButtons.OK, MessageBoxIcon.Information)
            statusLabel.Text = "Disconnecting..."
            Await ftp.DisconnectAsync()
        Catch err As Exception
            ' If an error occurs display the message
            ShowMessage(err)
        Finally
            ' at the end clean up
            CleanUp()
        End Try

    End Sub

    Private Sub CleanUp()
        ' Dispose Ftp 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)
        Dim aggregateEx As AggregateException = TryCast(err, AggregateException)
        If (aggregateEx IsNot Nothing) Then
            For Each ex As Exception In aggregateEx.InnerExceptions
                ShowMessage(ex)
            Next
            Return
        End If

        Dim ftpErr As FtpException = TryCast(err, FtpException)

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

        If ((Not ftpErr Is Nothing) AndAlso (ftpErr.Status = FtpExceptionStatus.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 Ftp 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 Ftp = ftp
        If Not tmp Is Nothing Then
            tmp.Abort()
            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 FtpTransferProgressChangedEventArgs) 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
