0

Есть такой метод:

public static async void DownloadFile(string filename, IProgress<decimal> progress)
{
    progress.Report(0m);
using (WebClient request = new WebClient())
{
    request.Credentials = new NetworkCredential(&quot;login&quot;, &quot;password&quot;);

    byte[] fileData = request.DownloadData(&quot;ftp://....&quot; + filename);

    var pathNormalize = $&quot;{MyPath.Desktop}\\{filename}&quot;.Replace(&quot;\r&quot;, string.Empty);

    using (var file = File.Create(pathNormalize))
    {
        await file.WriteAsync(fileData, 0, fileData.Length);
        var percent = 100m * ((decimal)file.Position / file.Length);
        progress.Report(percent);
    }
}

}

Вызываю так:

var progress = new Progress<decimal>((p) =>
{
    progressBar1.Value = (int)Convert.ToDecimal(p)));
if (progressBar1.Value &gt;= progressBar1.Maximum)
{
    progressBar1.Value = 0;
    MessageBox.Show(&quot;Completed!&quot;);
}

});

FTP.DownloadFile(name, progress);

Проблема:

При вызове метода DownloadFile блокируется основной поток и в конце значение ProgressBar становится 100.

Если вызывать так:

Task.Run(() => FTP.DownloadFile(name, progress));

Основной поток не блокируется, но ProgressBar также перескакивает к значению 100.

При всём при этом, после скачивание файла не показывает текст Completed!.

Нашел на просторах интернета уже не один пример скачивания файла с FTP, но ни один так и не удалось подружить с ProgessBar.

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

upd:

public static void DownloadFile(string filename, DownloadProgressChangedEventHandler DownloadProgressChanged, System.ComponentModel.AsyncCompletedEventHandler DownloadCompleted)
{
    Thread thread = new Thread(() =>
    {
        using (WebClient request = new WebClient())
        {
            request.Credentials = new NetworkCredential("login", "password");
            request.DownloadProgressChanged += DownloadProgressChanged;
            request.DownloadFileCompleted += DownloadCompleted;
        var pathNormalize = $&quot;{MyPath.Desktop}\\{filename}&quot;.Replace(&quot;\r&quot;, string.Empty);
        request.DownloadFileAsync(new Uri(&quot;ftp://.....&quot; + filename), pathNormalize);
    }
});
thread.Start();

}

private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { this.BeginInvoke((MethodInvoker)delegate { progressBar1.Value = e.ProgressPercentage; }); }

private void DownloadCompleted(object sender, AsyncCompletedEventArgs e) { this.BeginInvoke((MethodInvoker)delegate { MessageBox.Show("Completed!"); }); }

  • 1
    Из другого потока с элементами интерфейса нужно работать через Invoke. – Геннадий П Jul 26 '20 at 09:04
  • 1
    И у вас прогресс показывается не скачивания файла, а записи на диск. Если файл небольшой, то он будет быстро перескакивать на 100. – Геннадий П Jul 26 '20 at 09:10
  • @ГеннадийП, вот попробовал сделать по другому. Вроде бы должен показывать прогресс скачивания, а не записи. Но проблема та же. Единственное, Completed! теперь показывает. (обновил первый пост) – Максим Jul 26 '20 at 09:37
  • 2
  • 1
    Не надо оборачивать в дополнительный поток вебклиент с вызовом DownloadFileAsync, т. к. он и так будет выполняться асинхронно. – Alexander Petrov Jul 26 '20 at 10:08
  • Вот готовый велосипед FluentFTP. Устанавливайте NuGet пакет, пробуйте. – aepot Jul 26 '20 at 11:11

1 Answers1

0

Проблема оказалось в том, что при скачивании с FTP с отображением прогресса, нужно для начала узнать размер файла.

Вот рабочий код:

public static void DownloadFile(string filename, DownloadProgressChangedEventHandler DownloadProgressChanged, System.ComponentModel.AsyncCompletedEventHandler DownloadCompleted)
{
    Thread thread = new Thread(() =>
    {
        var request = (FtpWebRequest)WebRequest.Create("ftp://......" + filename);
        request.Credentials = new NetworkCredential("login", "password");
        request.Method = WebRequestMethods.Ftp.GetFileSize;
        var response = (FtpWebResponse)request.GetResponse();
    using (Stream responseStream = response.GetResponseStream())
    {
        MainForm.bytes = response.ContentLength;
    }

    using (WebClient request_ = new WebClient())
    {
        request_.Credentials = new NetworkCredential(&quot;login&quot;, &quot;password&quot;);
        request_.DownloadProgressChanged += DownloadProgressChanged;
        request_.DownloadFileCompleted += DownloadCompleted;

        var pathNormalize = $&quot;{MyPath.Desktop}\\{filename}&quot;.Replace(&quot;\r&quot;, string.Empty);
        request_.DownloadFileAsync(new Uri(&quot;ftp://.....&quot; + filename), pathNormalize);
    }
});
thread.Start();

}

private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { this.BeginInvoke((MethodInvoker)delegate { progressBar1.Value = (int)(((float)e.BytesReceived / (float)bytes) * 100.0); }); }

private void DownloadCompleted(object sender, AsyncCompletedEventArgs e) { this.BeginInvoke((MethodInvoker)delegate { MessageBox.Show("Completed!"); }); }

//где-то вызов FTP.DownloadFile(name, DownloadProgressChanged, DownloadCompleted);

  • Использовать одновременно и WebRequest, и WebClient - как-то странно и вообще незачем. – Alexander Petrov Jul 26 '20 at 10:11
  • Обратите внимание на то что вы используете сильно устаревший код. Ваш сервер точно не умеет отдавать файлы по http? – aepot Jul 26 '20 at 10:22
  • @AlexanderPetrov, как иначе узнать размер перед загрузкой? – Максим Jul 26 '20 at 10:26
  • @aepot, в данный момент — может, но в будущем — не сможет. – Максим Jul 26 '20 at 10:26
  • 1
    @Максим вы же в курсе, что большинство провайдеров не поддерживает FTP, просто провайдерский NAT не пропускает FTP трафик. Приходится работать в пассивном режиме. Так вот, если вам нужно скачивать файлы с сервера, то это только HTTP. Если вам нужно эти файлы туда заливать, то можно использовать любой толковый FTP клиент - FileZilla или Total Commander. На своих серверах я держу FTP только именно для заливки данных на сервер, и то планирую от него отказаться, слишком много сетевых проблем. Вы уверены в том, что вы делаете? – aepot Jul 26 '20 at 10:37
  • К вашему вопросу я прилинковал качалку по HTTP с возможностью докачки, совсем недавно написал с нуля ее, рекомендую попробовать. Если же вам принципиален FTP, то расскажите, почему. Этому протоколу пора уже покоиться с миром. Еще, если не слышали, есть такая очень попалярная штука - WebDAV. – aepot Jul 26 '20 at 10:41
  • @aepot, потому что мне по максимум нужно упростить работу с загрузкой/выгрузкой файлов на FTP сервер. То есть, открыл программу — сразу видишь список файлов, нажал добавить — файл загрузился, нажал скачать — файл скачался. И ещё, выбранный файл перед загрузкой добавляется в архив с паролем.. то есть, если все эти действия делать в ручную, это очень муторно и долго. – Максим Jul 26 '20 at 10:47
  • 1
    То есть, вы делаете именно свой FTP клиент, а не просто скачиваете файлы, так? Что у вас на стороне сервера, proftpd? У вас используется шифрование FTP соединения? В вопросе вы рассматриваете только метод скачивания файлов, при этом не рассматриваете ни получение списка файлов, не навигацию по директориям, не заливку файла на сервер, не работу с правами доступа к файлам. Или с этим у вас все хорошо? Пока я вижу, что ваш логин и пароль от сервера просто утащат хакеры, так как соединение не зашифровано. Вы же используете не тот же самый логин и пароль, что и от ssh? – aepot Jul 26 '20 at 10:59
  • 1
    Если кратко, ошибка которую я сейчас вижу, то вы каждый раз устанавливаете новое FTP соединение при закачке файла, это значит, что вы каждый раз передаете логин и пароль на сервер в открытом виде. Я бы рекомендовал использовать максимум 2 соединения, одно для навигации по каталогам, второе для фоновой передачи данных. Чтобы качать или заливать файлы в уже открытом соединении, тогда не придется хотя-бы так часто передавать логин и пароль. Но подумайте о шифровании, у меня FTP соединение шифруется с помощью SSL сертификата. – aepot Jul 26 '20 at 11:08
  • 1
    @aepot, проблем с получением списка файлов, загрузкой файлов, не возникло. Логин и пароль, разумеется, в таком виде в котором есть — хранится не будет. И да, в данный момент я использовал логин/пароль что и от ssh. Но это просто чтобы отладить работу, реализовать необходимые задачи. А вот теперь уже, буду думать над защитой. Сразу отвечу на Ваш второй коммент: уже установил соединение один раз при открытии программы, спасибо. А вот SSL сертификата, у меня, к сожалению, нет. – Максим Jul 26 '20 at 11:15
  • 1
    SSL сертификат можете получить бесплатно, в автоматическом режиме, у Let's Ecrypt. У вас же есть какой-то домен и HTTP сервер, сертификат один и тот же подойдет, и для HTTP, и для FTP. Заодно и самый настоящий HTTPS у вас заработает. – aepot Jul 26 '20 at 11:25
  • 1
    Есть еще один способ избавиться от FTP, если вы не планируете давать FTP пользователям - это SCP, передача файлов по SSH. Есть такой клиент WinSCP, в комплекте с PuTTY идет, от тех же разработчиков. Безопасная передача файлов, и сертификат не потребуется. Возможно и для C# есть библиотеки, я бы рекомендовал вам поискать так же в этом направлении. – aepot Jul 26 '20 at 11:32
  • 1
    Вот, нашел - SSH.NET – aepot Jul 26 '20 at 11:39