2

I have a ListBox that contains a list of DirectAdmin user backups. List is populated using WebRequestMethods.Ftp.ListDirectory and it looks like this:

enter image description here

I can download an archive using the button at the bottom right. When I click on the button, another form appears and downloads the archive.

enter image description here

My download code is this:

public static void DownloadFile(string server, string username, ...)
{
    Uri URI = new Uri($"ftp://{server}/{targetFilePath}");
    using (WebClient client = new WebClient())
    {
        client.Credentials = new NetworkCredential(username, password);
        if (progress != null)
        {
            client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(progress);
        }
        if (complete != null)
        {
            client.DownloadFileCompleted += new AsyncCompletedEventHandler(complete);
        }
        before?.Invoke();
        client.DownloadFileAsync(URI, localFilePath);
    }
}

and this is what I pass to the DownloadFile() method for the DownloadProgressChanged event:

delegate (object s2, DownloadProgressChangedEventArgs e2)
{
    TransferLabel.Invoke((MethodInvoker)delegate
    {
       TransferLabel.Text = $"{(e2.BytesReceived / 1024).ToString()} KB / {(e2.TotalBytesToReceive / 1024).ToString()} KB";
    });
    TransferProgressBar.Invoke((MethodInvoker)delegate
    {
       TransferProgressBar.Value = (int)(e2.BytesReceived / (float)e2.TotalBytesToReceive * 100);
    });
}

I'm using this same approach to upload a file and it works fine, but with download e2.TotalBytesToReceive returns -1 throughout the process:

enter image description here

and only when it's done, I get the correct value:

enter image description here

Why is that?


I've found a workaround to solve the problem. I'll change the ListBox to ListView and also store the filesize of the archives using ListDirectoryDetails. This way I can compare the e.BytesReceived to stored total bytes instead of e.TotalBytesToReceive. This would solve my problem, but I'm still curious about the problem. Why do I get -1? Am I doing something wrong, or is this a server related problem? Also is there anything I can do to fix it (get the correct value)?

akinuri
  • 10,690
  • 10
  • 65
  • 102
  • Read Remarks > Note section here: https://msdn.microsoft.com/en-us/library/system.net.webclient.downloadprogresschanged(v=vs.110).aspx – Evk Apr 25 '18 at 07:49

2 Answers2

3

With FTP protocol, WebClient in general does not know total download size. So you commonly get -1 with FTP.

See also Download file from FTP with Progress - TotalBytesToReceive is always -1?

Note that the behavior actually contradicts the .NET documentation, which says for FtpWebResponse.ContentLength (where the value of TotalBytesToReceive comes from):

For requests that use the DownloadFile method, the property is greater than zero if the downloaded file contained data and is zero if it was empty.

But you will easily find out many of questions about this (like the one I've linked above), effectively showing that the behavior is not always as documented. The FtpWebResponse.ContentLength has a meaningful value for GetFileSize method only.

The FtpWebRequest/WebClient makes no explicit attempt to find out a size of the file that it is downloading. All it does is that it tries to look for (xxx bytes). string in 125/150 responses to RETR command. No FTP RFC mandates that the server should include such information. ProFTPD (see data_pasv_open in src/data.c) and vsftpd (see handle_retr in postlogin.c) seem to include this information. Other common FTP servers (IIS, FileZilla) do not do this.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
  • "So you always get -1 with FTP" - that's not true. I just checked on some public ftp server and it returns ContentLength just fine when using `DownloadFile`. – Evk Apr 25 '18 at 08:26
  • I've deleted it already, but it was completely regular. Basically just `new WebClient().DownloadFileAsync(new Uri("ftp://speedtest.tele2.net/500MB.zip"), output)` (with `DownloadProgressChanged` like in OP question). – Evk Apr 25 '18 at 10:27
  • @Evk OK, I've updated my answer accordingly. – Martin Prikryl Apr 25 '18 at 10:45
0

Certainly for HTTP downloads it's possible for the server not to supply size information when performing a file download and you're left with no sensible information until the server signals that it's done.

Not sure for FTP (I'd note that there's a separate SIZE command defined in the FTP command set and so including such information during a Retrieve may be considered redundant).

I'm slightly surprised that the documentation for TotalBytesToRetrieve isn't more explicit on the possibility that the information will not be available and what will be returned in such circumstances.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448