0

Hy,

I am trying to make a popup indicating the progress of the download of several files. I'm not used to working with await/async and think I'm probably making a big mistake in this case.

The goal is to follow the progress of the file download one by one, I don't want to run all the downloads at the same time. And also have a general look at the progress of all the files to download.

Here is my actual code:

In a first Window I genereate the file list and send it in a PopUp:


Popupdownload inputDialog = new Popupdownload(FileList);                         
   if (inputDialog.ShowDialog() == true)
   {
     MessageBox.Show(FileList.Count().ToString() + " files are downloaded");
   }

My popup class :

public partial class Popupdownload: Window
    {
        public Popupdownload(List<string> listFiles)
        {
            InitializeComponent();
            DowloadList(listFiles);
        }
        public async void DowloadList(List<string> listFiles)
        {
            int counter = 0;
            foreach (string e in listFiles)
            {
                FileInfo TempFile = new FileInfo(System.IO.Path.Combine(App.folderpath, e));
                Uri myUri = new Uri("http://********/" + e), UriKind.Absolute);
                await DownloadFile(myUri, TempFile.FullName);
                counter++;
                progressBarTotal.Value = 100*(double)counter / (double)listFiles.Count();
            }
        }

        Stopwatch sw = new Stopwatch();
        public async Task DownloadFile(Uri URL, string location)
        {
            using (WebClient webClient = new WebClient())
            {
                webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
                webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
                sw.Start();
                try
                {
                    webClient.DownloadFileAsync(URL, location);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("The file " + location + " hasn't been dowload. Contact admin");
                }
            }
        }
        private void ProgressChanged(object sender, System.Net.DownloadProgressChangedEventArgs e)
        {
            labelSpeed.Content = string.Format("{0} kb/s", (e.BytesReceived / 1024d / sw.Elapsed.TotalSeconds).ToString("0.00"));
            progressBar.Value = e.ProgressPercentage;
            labelPerc.Content = e.ProgressPercentage.ToString() + "%";
            labelDownloaded.Content = string.Format("{0} MB's / {1} MB's",
                (e.BytesReceived / 1024d / 1024d).ToString("0.00"),
                (e.TotalBytesToReceive / 1024d / 1024d).ToString("0.00"));
        }
        private void Completed(object sender, AsyncCompletedEventArgs e)
        {
            sw.Reset();
            if (e.Cancelled == true)
            {
                MessageBox.Show("Download has been canceled.");
            }
            else
            {
              //  MessageBox.Show("Download completed!");
            }
        }
    }

My current code is a digest of several sources, and I am not sure that some parts work well.

My previous synchronius version of it is working, but hasn't the current file download status preview that I'would like.

It seem to me that all dowloads are launch in same time and it doesn't wait until first has stopped. So it is probably my first issue.

Thanks for your help

Alex Jud
  • 85
  • 1
  • 9
  • The `WebClient` class doesn't provide awaitable methods. Therefore, your `DownloadFile` method completes before the download completes. Try the [HttpClient](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0) class. – Steeeve Aug 11 '21 at 11:12
  • Ok, I will get a try. I've seen an possible way in another post. https://stackoverflow.com/a/43169927/4362092. – Alex Jud Aug 11 '21 at 12:53

1 Answers1

0

Here is how I resolve my question:

First my program launch a PopUp with the file list in argument.

Popupdownload inputDialog = new Popupdownload(FileList);

The Popupdownload windows :

    public partial class Popupdownload: Window
{
// Global Clock for speed processing
    Stopwatch sw = new Stopwatch();

// PopUp Initialisation 
    public Popupdownload(List<string> listFiles)
    {
        InitializeComponent();
        DowloadListASync(listFiles, type);
    }
// List processing with Progress Status Update
    public async void DowloadListASync(List<string> listFiles)
    {
        int counter = 0;
        long totalSize = 0;
        int totalcounter = listFiles.Count();
        progressLabelTotal.Content = 1 + "/" + totalcounter.ToString();
        var sw = Stopwatch.StartNew();
        foreach (string e in listFiles)
        {
            progressBarTotal.Value = 100 * (double)counter / (double)totalcounter;
            FileInfo TempFile = new FileInfo(System.IO.Path.Combine(App.folderpath, e));
            Uri myUri = new Uri("http://www.XXXX/" + e, UriKind.Absolute);
            if (!TempFile.Directory.Exists)
            {
                TempFile.Directory.Create();
            }
            if (TempFile.Exists) TempFile.Delete();
            await DownloadAFile(myUri, TempFile.FullName);
            counter++;
            TempFile = new FileInfo(System.IO.Path.Combine(App.folderpath, e));
            if (TempFile.Exists)
            {
                totalSize += TempFile.Length;
            }
            else
            {
                totalcounter--;
                counter--;
            }
            var totalSpeed = (totalSize / 1024d / (sw.Elapsed.TotalSeconds));
            if (totalSpeed < 800)
            {
                labelTotalSpeed.Content = string.Format("{0} kb/s", totalSpeed.ToString("0.00"));
            }
            else
            {
                totalSpeed = ((totalSize / 1024d) / 1024d / (sw.Elapsed.TotalSeconds));
                labelTotalSpeed.Content = string.Format("{0} Mb/s", totalSpeed.ToString("0.00"));
            }
            progressBarTotal.Value = 100 * (double)counter / (double)totalcounter;
            labelTotalPerc.Content = ((double)counter / (double)totalcounter).ToString("P");

            progressLabelTotal.Content = (counter != totalcounter) ? (1 + counter).ToString() + "/" + totalcounter.ToString() : "Téléchargment effectué";
        }
        Close;
    }
//async Download file with HttpClient, and keep progress
    public async Task DownloadAFile(Uri URL, string location)
    {
        using (var client = new HttpClientDownloadWithProgress(URL.AbsoluteUri, location))
        {

            var watch = Stopwatch.StartNew();
            FileInfo TempFile = new FileInfo(location);
            client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => {
                Nomfichier.Content = TempFile.Name;
                labelPerc.Content = progressPercentage.ToString() + "%";
                progressBar.Value = (double)progressPercentage;
                // labelSpeed.Content = string.Format("{0} kb/s", (totalBytesDownloaded / 1024d / (watch.Elapsed.TotalSeconds)).ToString("0.00"));

                var speed = (totalBytesDownloaded / 1024d / (watch.Elapsed.TotalSeconds));
                if (speed < 800)
                {
                    labelSpeed.Content = string.Format("{0} kb/s", speed.ToString("0.00"));
                }
                else
                {
                    speed = ((totalBytesDownloaded / 1024d) / 1024d / (watch.Elapsed.TotalSeconds));
                    labelSpeed.Content = string.Format("{0} Mb/s", speed.ToString("0.00"));
                }
                //   Console.WriteLine($"{progressPercentage}% ({totalBytesDownloaded}/{totalFileSize})");
            };
            await client.StartDownload();
            Nomfichier.Content = "";
        }
    }

and the HttpClientDownload class used like in comment:

    public class HttpClientDownloadWithProgress : IDisposable
    {
        private readonly string _downloadUrl;
        private readonly string _destinationFilePath;

        private HttpClient _httpClient;

        public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage);

        public event ProgressChangedHandler ProgressChanged;

        public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath)
        {
            _downloadUrl = downloadUrl;
            _destinationFilePath = destinationFilePath;
        }

        public async Task StartDownload()
        {
            _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };

            using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead))
                await DownloadFileFromHttpResponseMessage(response);
        }

        private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
        {
            response.EnsureSuccessStatusCode();

            var totalBytes = response.Content.Headers.ContentLength;

            using (var contentStream = await response.Content.ReadAsStreamAsync())
                await ProcessContentStream(totalBytes, contentStream);
        }

        private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
        {
            var totalBytesRead = 0L;
            var readCount = 0L;
            var buffer = new byte[8192];
            var isMoreToRead = true;

            using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
            {
                do
                {
                    var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
                    if (bytesRead == 0)
                    {
                        isMoreToRead = false;
                        TriggerProgressChanged(totalDownloadSize, totalBytesRead);
                        continue;
                    }

                    await fileStream.WriteAsync(buffer, 0, bytesRead);

                    totalBytesRead += bytesRead;
                    readCount += 1;

                    if (readCount % 100 == 0)
                        TriggerProgressChanged(totalDownloadSize, totalBytesRead);
                }
                while (isMoreToRead);
            }
        }

        private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead)
        {
            if (ProgressChanged == null)
                return;

            double? progressPercentage = null;
            if (totalDownloadSize.HasValue)
                progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);

            ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage);
        }

        public void Dispose()
        {
            _httpClient?.Dispose();
        }
    }

My last wish would be to be able to run downloads in bundles of 3-4 and handle 404 errors when the target file is missing. Work In Progress. If anyone has a clue.

Thanks @steeeve for your comment.

Alex Jud
  • 85
  • 1
  • 9