Get File Size on FTP Server and put it on a Label

后端 未结 1 987
傲寒
傲寒 2020-11-30 15:32

I\'m trying to get the size of a file that is hosted on a FTP Server and put it in a Label while the `BackgroundWorker works in the background.

相关标签:
1条回答
  • 2020-11-30 16:07

    The main problems (set Option Strict On to find more):
    You can't access the UI objects from a thread different than the UI Thread.
    The error you receive is:

    Cross-thread operation not valid:
    Control lblFileSizeD accessed from
    a thread other than the thread it was created on

    Then, the same error for lblDownloadedBytesD.

    Also, you are eating up your Error messages using an empty handler with

    Catch ex As Exception
    
    End Try
    

    This nullifies any handling, because theres's none. You are simply letting the code run past it without taking any action. The handlers are there to, well, handle the errors, not to let them go unchecked.

    When you need to access and update some UI component property, use the BackGroundWorker ReportProgress() method. This method has an overload that accepts a parameter of type Object. Meaning, you can feed it anything. This Object will be the e.UserState property in the ReportProgress ProgressChangedEventArgs class.

    The .RunWorkerAsync() method also accepts an Object parameter. This Object will become the e.Argument property of the BackgroundWorker.DoWork Event. This gives some flexibility in relation to the parameters you can actually pass to your BackGroundWorker.

    One more problem: the Ftp Download procedure does not support cancellation. When run, a user can't stop it.

    Last problem: as reported in the documentation, you should never reference the BackGroundWorker object you instantiated in your UI thread (the Form) in its DoWork event. Use the sender object and cast it to the BackGroundWorker class.

    In this example, all the UI references are delegated to a Class object that is passed to the DoWork event through the RunWorkerAsync(Object) method (using the e.Argument property).
    The Class object is updated with progress details and then fed to the ReportProgress(Int32, Object) method, which runs in the original Synchronization Context (the UI thread, where the RunWorkerAsync method is called).
    The UI can be updated safely. No cross-thread operations can occur.

    A cancellation method is also implemented. This allows to abort the download procedure and to delete a partial downloaded file, if one is created.

    The error handling is minimal, but this is something you need to integrate with your own tools.

    (I've used the same names for the UI Controls, it should be easier to test.)

    Imports System.ComponentModel
    Imports System.Globalization
    Imports System.IO
    Imports System.Net
    Imports System.Net.Security
    Imports System.Security.Cryptography.X509Certificates
    
    Public Class frmBGWorkerDownload
    
        Friend WithEvents BWorkerD As BackgroundWorker
        Public Sub New()
            InitializeComponent()
            BWorkerD = New BackgroundWorker()
            BWorkerD.WorkerReportsProgress = True
            BWorkerD.WorkerSupportsCancellation = True
        End Sub
    
        Private Class BGWorkerObject
            Public Property UserName As String
            Public Property Password As String
            Public Property ResourceURI As String
            Public Property FilePath As String
            Public Property FileLength As Long
            Public Property DownloadedBytes As Long
            Public Property BytesToDownload As Long
        End Class
    
        Private Sub btnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click
            pBarD.Value = 0
            Dim BGWorkerObj As BGWorkerObject = New BGWorkerObject With {
                .ResourceURI = txtFilePathD.Text,
                .FilePath = Path.Combine(txtSavePathD.Text, Path.GetFileName(txtFilePathD.Text)),
                .UserName = txtFTPUsernameD.Text,
                .Password = txtFTPPasswordD.Text
            }
            AddHandler BWorkerD.DoWork, AddressOf BWorkerD_DoWork
            AddHandler BWorkerD.ProgressChanged, AddressOf BWorkerD_ProgressChanged
            AddHandler BWorkerD.RunWorkerCompleted, AddressOf BWorkerD_RunWorkerCompleted
            BWorkerD.RunWorkerAsync(BGWorkerObj)
    
        End Sub
    
        Private Sub BWorkerD_DoWork(sender As Object, e As DoWorkEventArgs)
            Dim BGW As BackgroundWorker = TryCast(sender, BackgroundWorker)
            Dim BGWorkerObj As BGWorkerObject = TryCast(e.Argument, BGWorkerObject)
            Dim FTPRequest As FtpWebRequest
            Dim BufferSize As Integer = 131072
    
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
            ServicePointManager.ServerCertificateValidationCallback =
                Function(snd As Object, Cert As X509Certificate, Chain As X509Chain, Err As SslPolicyErrors)
                    Return True
                End Function
    
            FTPRequest = DirectCast(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
            FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
            'FTPRequest.Method = WebRequestMethods.Ftp.GetFileSize
            '----------------------- UPDATE  ------------------------
            FTPRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails
            '--------------------- END UPDATE  ------------------------
            FTPRequest.EnableSsl = True
    
            '----------------------- UPDATE  ------------------------
            Using FtpResponse As WebResponse = FTPRequest.GetResponse, 
              DirListStream As Stream = FtpResponse.GetResponseStream(), 
              listReader As StreamReader = New StreamReader(DirListStream)
                While Not listReader.EndOfStream
                    Dim DirContent As String = listReader.ReadLine()
                    If DirContent.Contains(Path.GetFileNameWithoutExtension(BGWorkerObj.ResourceURI)) Then
                        BGWorkerObj.FileLength = Convert.ToInt64(DirContent.Split(New String() {" "}, StringSplitOptions.RemoveEmptyEntries)(4))
                        BGW.ReportProgress(0, BGWorkerObj)
                        Exit While
                    End If
                End While
            End Using
            '----------------------- END UPDATE  ------------------------
    
            'Using FtpResponse As WebResponse = FTPRequest.GetResponse
            '    BGWorkerObj.FileLength = Convert.ToInt64(FtpResponse.ContentLength)
            '    BGW.ReportProgress(0, BGWorkerObj)
            'End Using
    
            If BGW.CancellationPending Then e.Cancel = True
    
            Try
                FTPRequest = CType(WebRequest.Create(BGWorkerObj.ResourceURI), FtpWebRequest)
                FTPRequest.EnableSsl = True
                FTPRequest.Credentials = New NetworkCredential(BGWorkerObj.UserName, BGWorkerObj.Password)
                FTPRequest.Method = WebRequestMethods.Ftp.DownloadFile
    
                Using Response As FtpWebResponse = DirectCast(FTPRequest.GetResponse, FtpWebResponse)
                    If Response.StatusCode > 299 Then
                        e.Result = 0
                        Throw New Exception("The Ftp Server rejected the request. StatusCode: " &
                                            Response.StatusCode.ToString(),
                                            New InvalidOperationException(Response.StatusCode.ToString()))
                        Exit Sub
                    End If
                    Using stream = Response.GetResponseStream(), 
                      fileStream As FileStream = File.Create(BGWorkerObj.FilePath)
                        Dim read As Integer
                        Dim buffer As Byte() = New Byte(BufferSize - 1) {}
                        Do
                            read = stream.Read(buffer, 0, buffer.Length)
                            fileStream.Write(buffer, 0, read)
                            BGWorkerObj.DownloadedBytes += read
                            BGWorkerObj.BytesToDownload = BGWorkerObj.FileLength - BGWorkerObj.DownloadedBytes
    
                            If BGW.CancellationPending Then
                                e.Cancel = True
                                Exit Do
                            Else
                                BGW.ReportProgress(CInt((CSng(BGWorkerObj.DownloadedBytes) / BGWorkerObj.FileLength) * 100), BGWorkerObj)
                            End If
                        Loop While read > 0
                    End Using
                End Using
    
            Catch ex As Exception
                If e.Cancel = False Then Throw
            Finally
                If e.Cancel = True Then
                    If File.Exists(BGWorkerObj.FilePath) Then
                        File.Delete(BGWorkerObj.FilePath)
                    End If
                End If
            End Try
    
        End Sub
    
        Private Sub BWorkerD_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
             pBarD.Value = e.ProgressPercentage
            lblPercentD.Text = e.ProgressPercentage.ToString() & " %"
    
            If lblFileSizeD.Text.Length = 0 Then
                lblFileSizeD.Text = CType(e.UserState, BGWorkerObject).FileLength.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
            End If
            lblDownloadedBytesD.Text = CType(e.UserState, BGWorkerObject).DownloadedBytes.ToString("N0", CultureInfo.CurrentUICulture.NumberFormat)
            If e.ProgressPercentage <= 15 Then
                lblDownloadedBytesD.ForeColor = Color.Red
            ElseIf e.ProgressPercentage <= 66 Then
                lblDownloadedBytesD.ForeColor = Color.Orange
            Else
                lblDownloadedBytesD.ForeColor = Color.LightGreen
            End If
        End Sub
    
        Private Sub BWorkerD_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            Dim DownloadAborted As Boolean = False
            If e.Error IsNot Nothing Then
                DownloadAborted = True
                lblDownloadedBytesD.ForeColor = Color.Red
                lblDownloadedBytesD.Text = "Error!"
            ElseIf e.Cancelled Then
                DownloadAborted = True
                lblDownloadedBytesD.ForeColor = Color.Yellow
                lblDownloadedBytesD.Text = "Cancelled!"
                pBarD.Value = 0
                lblPercentD.Text = "0%"
            Else
                lblDownloadedBytesD.ForeColor = Color.LightGreen
                lblDownloadedBytesD.Text = "Download completed"
            End If
    
            RemoveHandler BWorkerD.DoWork, AddressOf BWorkerD_DoWork
            RemoveHandler BWorkerD.ProgressChanged, AddressOf BWorkerD_ProgressChanged
            RemoveHandler BWorkerD.RunWorkerCompleted, AddressOf BWorkerD_RunWorkerCompleted
        End Sub
    
        Private Sub btnAbortDownload_Click(sender As Object, e As EventArgs) Handles btnAbortDownload.Click
            BWorkerD.CancelAsync()
        End Sub
    End Class
    

    A visual result of the operation described:


    A PasteBin of the Form's Designer + Code.

    0 讨论(0)
提交回复
热议问题