Qt/C++ Tutorial 080. Downloading large files with QNetworkAccessManager

QNetworkRequest, Qt, QNetworkAccessManager, QNetworkReply

After the question appeared on the forum about downloading large files using the Qt library, I raised some of my projects and prepared a more detailed manual using this functionality. Moreover, the problem with downloading files was related to redirects. By default, QNetworkAccessManager does not switch to redirects for downloading files and retrieving pages, so you need to set the appropriate attribute in the request, then everything will work, but let's take a closer look.

The application will have the following functionality.

  • QLineEdit for entering the destination URL for downloading
    QLineEdit to enter the target directory for download in readOnly mode. We will fill it with QFileDialog.
    QProgressBar, which will show the progress of the download
    Button to cancel download

Look our downloader will be so

Project Structure

The project consists of

  • FileDownloader.pro - project profile
  • Downloader.h - Header file for downloading files
  • Downloader.cpp - Class implementation file for downloading files
  • Widget.h - Application window header file
  • Widget.cpp - Application window implementation file
  • Widget.ui - Graphical form of the application window
  • main.cpp - The file with the main application function

FileDownloader.pro, main.cpp, Widget.ui will not be considered, the first two are created by default, the latter is created through the Qt Designer graphic editor, look at it in the project itself, which is attached to the article at the very end.

Widget.h

In the header file, all the unimaginable slots for processing the interface buttons are declared and also the class for downloading files is declared on the stack

#ifndef WIDGET_H
#define WIDGET_H

#include "Downloader.h"

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    // Slot for download start
    void onDownloadButtonClicked();

    // Slot for selecting the download directory
    void onSelectTargetFolderButtonClicked();

    // Slot for canceling the download
    void onCancelButtonClicked();

    // Slot for updating download progress
    void onUpdateProgress(qint64 bytesReceived, qint64 bytesTotal);

private:
    Ui::Widget *ui;
    Downloader m_downloader; // Download Class
};

#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include "ui_Widget.h"

#include <QFileDialog>
#include <QStandardPaths>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    // Connect to slots
    connect(ui->downloadPushButton, &QPushButton::clicked, this, &Widget::onDownloadButtonClicked);
    connect(ui->selectTargetFolderPushButton, &QPushButton::clicked, this, &Widget::onSelectTargetFolderButtonClicked);
    connect(ui->cancelPushButton, &QPushButton::clicked, this, &Widget::onCancelButtonClicked);
    connect(&m_downloader, &Downloader::updateDownloadProgress, this, &Widget::onUpdateProgress);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::onDownloadButtonClicked()
{
    // We start downloading the file by passing the 
    // path to the directory where we will upload files, 
    // url, where the file is located
    m_downloader.get(ui->targetFolderLineEdit->text(), ui->urlLineEdit->text());
}

void Widget::onSelectTargetFolderButtonClicked()
{
    // Selecting the destination directory for downloading
    QString targetFolder = QFileDialog::getExistingDirectory(this,
                                                             tr("Select folder"),
                                                             QStandardPaths::writableLocation(QStandardPaths::DownloadLocation),
                                                             QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
    ui->targetFolderLineEdit->setText(targetFolder);
}

void Widget::onCancelButtonClicked()
{
    // Cancel upload
    m_downloader.cancelDownload();
    ui->downloadProgressBar->setMaximum(100);
    ui->downloadProgressBar->setValue(0);
}

void Widget::onUpdateProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    // Updating upload progress
    ui->downloadProgressBar->setMaximum(bytesTotal);
    ui->downloadProgressBar->setValue(bytesReceived);
}

Downloader.h

And now look at the class for downloading files, taking into account the progress of the download.

An important point is that large files need to be processed gradually, they can not be read by one query. Therefore, you need to handle the QNetworkReply::readyRead signal from the object of the current response to the request. This signal is emitted when the buffer contains data that we can assume.

And only after the download is completed QNetworkAccessManager will issue a finished signal, which will close the file and complete the connection with the removal of the object of the current response to the request.

#ifndef DOWNLOADER_H
#define DOWNLOADER_H

#include <QNetworkAccessManager>

class QNetworkReply;
class QFile;

class Downloader : public QObject
{
    Q_OBJECT
    using BaseClass = QObject;

public:
    explicit Downloader(QObject* parent = nullptr);

    // Method for starting the download
    bool get(const QString& targetFolder, const QUrl& url);

public slots:
    // Method of canceling the load
    void cancelDownload();

signals:
    // A signal that sends information about the progress of the download
    void updateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);

private slots:
    // Slot for gradual reading of downloaded data
    void onReadyRead();
    // Slot for processing request completion
    void onReply(QNetworkReply* reply);

private:
    QNetworkReply* m_currentReply {nullptr};    // Current request being processed
    QFile* m_file                 {nullptr};    // The current file to which the entry is being written
    QNetworkAccessManager m_manager;            // Network manager for downloading files
};

#endif // DOWNLOADER_H

Downloader.cpp

#include "Downloader.h"

#include <QNetworkReply>
#include <QNetworkRequest>
#include <QFile>
#include <QDir>

Downloader::Downloader(QObject* parent) :
    BaseClass(parent)
{
    // Connect to the finished signal
    connect(&m_manager, &QNetworkAccessManager::finished, this, &Downloader::onReply);
}

bool Downloader::get(const QString& targetFolder, const QUrl& url)
{
    if (targetFolder.isEmpty() || url.isEmpty())
    {
        return false;
    }

    // Create object of file class for download
    // Here there is a target directory and the name of the file that is allocated from the URL
    m_file = new QFile(targetFolder + QDir::separator() + url.fileName());
    // Trying to open the file
    if (!m_file->open(QIODevice::WriteOnly))
    {
        delete m_file;
        m_file = nullptr;
        return false;
    }

    // Creating a request
    QNetworkRequest request(url);
    // Allow to go on redirects
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    // Running the download
    m_currentReply = m_manager.get(request);

    // After that, we immediately connect to signals about readiness of data to read and update the progress of downloading
    connect(m_currentReply, &QNetworkReply::readyRead, this, &Downloader::onReadyRead);
    connect(m_currentReply, &QNetworkReply::downloadProgress, this, &Downloader::updateDownloadProgress);
    return true;
}

void Downloader::onReadyRead()
{
    // If there is data and the file is open
    if (m_file)
    {
        // write them to a file
        m_file->write(m_currentReply->readAll());
    }
}

void Downloader::cancelDownload()
{
    // Cancel request
    if (m_currentReply)
    {
        m_currentReply->abort();
    }
}

void Downloader::onReply(QNetworkReply* reply)
{
    // by completion of the request
    if (reply->error() == QNetworkReply::NoError)
    {
        // save file
        m_file->flush();
        m_file->close();
    }
    else
    {
        // Or delete it in case of error
        m_file->remove();
    }

    delete m_file;
    m_file = nullptr;
    reply->deleteLater();
}

Conclusion

Thus, we have an application that can download the required file for a given URL and place it in the target directory.

Link to download the project Downloader

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate
f

не могу понять как обработать ошибку некорректной ссылки?
Пример: "ftp://cddis.gsfc.nasa.gov/pub/slr/data/npt_crd/gracea/2010/gracea_20100101.npt.Z"

У вас скорее всего ошибка превышения интервала ожидания - QNetworkReply::TimeoutError

Гляньте вот эту статью про описание ошибок QNetworkAccessManager
Там в конце статьи есть пример обработки ошибок с выводом в qDebug()
f
  • #
  • June 6, 2018, 12:42 p.m.

у меня никак не получается обработать ошибку некорректной ссылки(

    connect(&m_downloader.manager, &QNetworkAccessManager::finished, this, &Widget::onResult);
    connect(m_downloader.currentReply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &Widget::errorSlot);
R

Здравствуйте. В вашем примере не обновляется прогресс-бар. А если точнее - он выставляет 100% после закачки, и всё. Во время самой закачки просто 0%. Сомневаюсь что так задумано, но всё же, как сделать чтобы обновлялся каждый кусок?

Какого размера пробуете скчивать файлы и на какой скорости у вас работает интернет.
Есть ещё один неприятный момент в том, что не на всех платформах правильно передаётся информация о прогрессе закачки.
Так что какую используете операционную систему?

А почему вы не сделаете обработку ошибок в слоте onResult.

Кстати, по поводу кастов перегруженных сигналов и слотов. Посмотрите на использование Overload шаблона , ппроще писать перегруженные сигнал/слотовые соединения будет.
R

Проблема была в ресурсе, с которого скачиваю. Пытался тянуть с google disc - а там ограничение 25мб на прямые ссылки. Поэтому он и втупал.


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

ну.. хз.. Github может быть, там вам и контроль версии и всё остальное, вроде и ссылки на метки прямые.

Ну или поднимите свой онлайн ресурс ))
Если честно, понятия не имею, не было такой необходимости, а если и была, то объодился ресурсами своего сайта.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Looking for a Job?
14,000.00 руб. - 40,000.00 руб.
Разработчик Qt
Annino, Moscow Oblast, Russia
5,000.00 руб. - 15,000.00 руб.
Дизайнер
Moskovskiy, Moscow, Russia
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

For registered users on the site there is a minimum amount of advertising

A
Aug. 22, 2019, 11:24 p.m.
Aleksandr73

Qt - Test 001. Signals and slots

  • Result:47points,
  • Rating points-6
Aug. 21, 2019, 10:23 a.m.
Andrej Ermoshin

C++ - Test 002. Constants

  • Result:58points,
  • Rating points-2
Aug. 21, 2019, 10:15 a.m.
Andrej Ermoshin

C++ - Test 001. The first program and data types

  • Result:86points,
  • Rating points6
Last comments
Aug. 19, 2019, 7:41 a.m.
Andrej Jankovich

это проблема дистрибутива, попробуйте установить через пакетный менеджер snap Суть проблемы: libQt5Core которая лежит в дистрибутиве требует версию glibc >= 2.25 у вас видимо …
b
Aug. 18, 2019, 6:09 a.m.
bbb116

cqtdeployer /home/aleks/CQtDeployer/bin/cqtdeployer: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /home/aleks/CQtDeployer/lib/libQt5Core.so.5) linux mint …
D
Aug. 17, 2019, 9:04 a.m.
Damir

github ChekableTView Правой групповая смена значения при перетаскивании левой как обычно.
Aug. 16, 2019, 1:03 p.m.
Evgenij Legotskoj

Потому, что в минуте 60 секунд
Aug. 16, 2019, 12:16 p.m.
Dmitrij

а почему делитель 60000, а не 1000?
Now discuss on the forum
Aug. 24, 2019, 7:21 a.m.
Evgenij Legotskoj

Не помню, давно уже с QML не работал, по-моему, обычно пишет в консоль, что не находит файл. В любом случае какую-то ошибку в консоль выкидывает. Но если честно, если у вас проект будет ак…
BG
Aug. 24, 2019, 4:27 a.m.
Brjus Gliff

Спасибо, вначале в документации было не понятно что к чему, теперь разобрался
I
Aug. 21, 2019, 8:36 a.m.
Intruder

Александр, мне не нужно перебирать. Вы говорите правильно, сначала я написал избыточный код просто не подумав. Задача такая, мне нужно просто переложить из QMap в атрибуты xml тега все, что там …
Aug. 21, 2019, 3:16 a.m.
nayk1982

Если Вы разрабатываете какую-то универсальную утилиту, которая вообще не привязана к логике, тогда как вариант: 1. Получить список таблиц через QSqlDatabase::tables 2. Для каждой табли…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB