1

Есть такой код, который скачивает и докачивает файл. Как можно отображать процесс скачивание на прогресс бар

static void DownloadFile(string sSourceURL, string sDestinationPath)
{
    long iFileSize = 0;
    int iBufferSize = 1024;
    iBufferSize *= 1000;
    long iExistLen = 0;
    System.IO.FileStream saveFileStream;
    if (System.IO.File.Exists(sDestinationPath))
    {
        System.IO.FileInfo fINfo =
           new System.IO.FileInfo(sDestinationPath);
        iExistLen = fINfo.Length;
    }
    if (iExistLen > 0)
        saveFileStream = new System.IO.FileStream(sDestinationPath,
          System.IO.FileMode.Append, System.IO.FileAccess.Write,
          System.IO.FileShare.ReadWrite);
    else
        saveFileStream = new System.IO.FileStream(sDestinationPath,
          System.IO.FileMode.Create, System.IO.FileAccess.Write,
          System.IO.FileShare.ReadWrite);

    System.Net.HttpWebRequest hwRq;
    System.Net.HttpWebResponse hwRes;
    hwRq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(sSourceURL);
    hwRq.AddRange((int)iExistLen);
    System.IO.Stream smRespStream;
    hwRes = (System.Net.HttpWebResponse)hwRq.GetResponse();
    smRespStream = hwRes.GetResponseStream();

    iFileSize = hwRes.ContentLength;

    int iByteSize;
    byte[] downBuffer = new byte[iBufferSize];

    while ((iByteSize = smRespStream.Read(downBuffer, 0, downBuffer.Length)) > 0)
    {
        saveFileStream.Write(downBuffer, 0, iByteSize);
    }
    saveFileStream.Close();

}
aepot
  • 49,560
  • Для скачки файла можно воспользоватся классом WebClient. У него есть событие DownloadProgressChanged, которое срабатывает при изменении прогресса загрузки. Останется только подписатся на это событие и в подписчике обновлять ваш ProgressBar. – Boris Makhlin May 31 '20 at 08:46
  • winforms или wpf? – aepot May 31 '20 at 10:27
  • 1
    -aepot WindowsForm – danrom11 May 31 '20 at 12:37
  • @danrom11 - Подскажи пожалуйста как в выше предоставленном коде добавить сколько скачено мб из скольки, поскольку всегда использовал WebClient сегодня наткнулся на эту тему, и не совсем понимаю как в твоем коде что реализовано. – mx01 Feb 18 '21 at 11:18

1 Answers1

3

Ваш код синхронный, интерфейс просто не будет обновляться пока он работает. Придется либо втыкать костыли в виде опасного Application.DoEvents() либо написать нормальный асинхронный метод, что я и сделал.

На самом деле, если поискать, примеров в гугле очень много, разных, в том числе и тот, который приведен в вопросе. Но все они сильно устаревшие, и многие не поддерживают докачку, либо портят файл при докачке.

К тому же HttpWebRequest устарел.

Не рекомендуется использовать HttpWebRequest для новой разработки. Вместо этого используйте класс HttpClient.

Вот пример для WinForms с использованием HttpClient.

  • Грузит файл асинхронно и сохраняет на диск
  • Докачивает, если часть файла уже загружена
  • По нажатию кнопки Cancel можно остановить загрузку и продолжить потом повторным нажатием кнопки Download
  • button1 - кнопка Download, button2 - кнопка Cancel, progressBar1 - прогресс бар
  • реализована базовая обработка ошибок
  • реализован отчет о прогрессе с помощью интерфейса IProgress и класса Progress

Полный код приложения.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button2.Enabled = false; // по умолчанию закачка не активна, кнопку `Cancel` выключаем
    }
// HttpClient создается один раз на всё время работы приложения
private static readonly HttpClient _client = new HttpClient();

// Токен отмены служит для прерывания работы загрузчика в любой момент
private CancellationTokenSource _cts;

// метод универсален, проверен в .NET Core и .NET Framework
private async Task DownloadAndSaveFileAsync(string url, string path, IProgress<int> status, CancellationToken token)
{
    const int bufferLength = 8192;
    long currentPosition = File.Exists(path) ? new FileInfo(path).Length : 0;

    using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
    {
        request.Headers.Range = new RangeHeaderValue(currentPosition, null);
        using (HttpResponseMessage response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
        {
            response.EnsureSuccessStatusCode();
            using (Stream responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
            using (FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None))
            {
                long contentLength = currentPosition + response.Content.Headers.ContentLength ?? 0;
                int progress = -1;
                int oldProgress;
                byte[] buffer = new byte[bufferLength];
                int bytesReceived;
                while ((bytesReceived = await responseStream.ReadAsync(buffer, 0, bufferLength, token).ConfigureAwait(false)) > 0)
                {
                    await fs.WriteAsync(buffer, 0, bytesReceived, token).ConfigureAwait(false);

                    currentPosition += bytesReceived;
                    oldProgress = progress;
                    progress = (int)(currentPosition * 100 / contentLength);
                    // так как значение от 0 до 100, нет особого смысла повтороно обновлять интерфейс, если значение не изменилось.
                    if (oldProgress != progress)
                    {
                        status?.Report(progress);
                    }
                }
            }
        }
    }
}

// обратите внимание на async здесь
private async void button1_Click(object sender, EventArgs e)
{
    if (_cts != null)
        return;
    button1.Enabled = false;
    button2.Enabled = true;

    using (_cts = new CancellationTokenSource())
    {
        try
        {
            // укажите здесь нужный URL и путь к файлу
            // обратите внимение на new Progress<int>(v => progressBar1.Value = v) - оно и меняет значение прогресс бара во время загрузки
            await DownloadAndSaveFileAsync("https://example.org/file.txt", "file.txt", new Progress<int>(v => progressBar1.Value = v), _cts.Token);
        }
        catch (OperationCanceledException) { }
        catch (HttpRequestException ex)
        {
            if (ex.Message.Contains("416")) // 416 (Requested Range Not Satisfiable)
            {
                MessageBox.Show("Файл уже закачан");
            }
            else
            {
                MessageBox.Show(ex.ToString(), "HttpRequestException");
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), ex.GetType().Name);
        }
        progressBar1.Value = 0;
    }
    _cts = null;
    button1.Enabled = true;
    button2.Enabled = false;
}

private void button2_Click(object sender, EventArgs e)
{
    if (_cts != null && !_cts.IsCancellationRequested)
        _cts.Cancel();
}

}

В данном примере есть один недостаток, он не будет работать если веб-сервер не поддерживает докачку файла. Но вы можете его доработать.

aepot
  • 49,560
  • Не подскажите, как получать проценты сколько скачено, или лучше сколько скачено мб из скольки – danrom11 Jun 03 '20 at 17:56
  • @danrom11 Так здесь и показываются проценты. Если вам нужны мегабайты, нужно немного переделать. – aepot Jun 03 '20 at 17:58
  • @danrom11 можно же так модифицировать с процентами, например в label1: new Progress<int>(v => { progressBar1.Value = v; label1.Text = v + "%" }). – aepot Jun 03 '20 at 18:10
  • 1
    Да я понял с процентами, что то тупонул, уже сделал их, спасибо большое – danrom11 Jun 03 '20 at 18:11
  • aepot, а можно ли сделать ограничение на скорость скачивания ? – danrom11 Jun 11 '20 at 12:36
  • 1
    @danrom11 можно, делайте :) – aepot Jun 11 '20 at 13:51
  • @danrom11 вот еще ссылка – aepot Jun 11 '20 at 13:54
  • спасибо ещё раз :) – danrom11 Jun 11 '20 at 15:58