How to
How to

Reputation: 103

QT downloading large file error

When I try to downloading file up to 50mb example, no problem, but with a big files give the following error err

 void MainWindow::downloadFile() {
           QNetworkRequest requests;
           requests.setUrl(QUrl("https://urlToFile"));

           QSslConfiguration configSsl = QSslConfiguration::defaultConfiguration();
           configSsl.setProtocol(QSsl::AnyProtocol);
           requests.setSslConfiguration(configSsl);
           QNetworkAccessManager *manager5 = new QNetworkAccessManager(this);
           QNetworkReply *reply5;
           reply5 = manager5->get( requests );
           connect(manager5, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
}


    void MainWindow::downloadFinished(QNetworkReply *data) {
     QFile localFile("fileName");
        if (!localFile.open(QIODevice::WriteOnly))
            return;
        localFile.write(data->readAll());
        localFile.close();
    }

Upvotes: 1

Views: 2440

Answers (1)

Mike
Mike

Reputation: 8355

In your code, You are holding the whole file in memory until the download process finishes (that is when QNetworkAccessManager::finished() signal gets emitted). Of course, this is not the best way to deal with large files.

Remember that QNetworkReply is a QIODevice. This means that you should use the readyRead() signal to save data chunks to the disk as soon as they are received from the network, in order to avoid holding the whole file in memory until the download is finished.

Here is a minimal complete example:

#include <QtNetwork>

int main(int argc, char* argv[]){
    QCoreApplication a(argc, argv);

    QNetworkAccessManager nam;
    QFile file("downloadedFile.xxx");
    if(!file.open(QIODevice::ReadWrite)) return 1;
    QNetworkRequest request(QUrl("http://download_url/..."));
    QNetworkReply* reply = nam.get(request);

    QObject::connect(reply, &QNetworkReply::readyRead, [&]{
        //this will be called every time a chunk of data is received
        QByteArray data= reply->readAll();
        qDebug() << "received data of size: " << data.size();
        file.write(data);
    });

    //use the finished signal from the reply object to close the file
    //and delete the reply object
    QObject::connect(reply, &QNetworkReply::finished, [&]{
        qDebug() << "finished downloading";
        QByteArray data= reply->readAll();
        file.write(data);
        file.close();
        reply->deleteLater();
        a.quit();
    });

    return a.exec();
}

Update:

I have wrapped the whole thing in a class FileDownloader, which can be used to download a file using QNetworkAccessManager, Here is an example of using this class:

screenshot

#include <QtWidgets>
#include <QtNetwork>

//downloads one file at a time, using a supplied QNetworkAccessManager object
class FileDownloader : public QObject{
    Q_OBJECT
public:
    explicit FileDownloader(QNetworkAccessManager* nam, QObject* parent= nullptr)
        :QObject(parent),nam(nam)
    {

    }
    ~FileDownloader(){
        //destructor cancels the ongoing dowload (if any)
        if(networkReply){
            a_abortDownload();
        }
    }

    //call this function to start downloading a file from url to fileName
    void startDownload(QUrl url, QString fileName){
        if(networkReply) return;
        destinationFile.setFileName(fileName);
        if(!destinationFile.open(QIODevice::WriteOnly)) return;
        emit goingBusy();
        QNetworkRequest request(url);
        networkReply= nam->get(request);
        connect(networkReply, &QIODevice::readyRead, this, &FileDownloader::readData);
        connect(networkReply, &QNetworkReply::downloadProgress,
                this, &FileDownloader::downloadProgress);
        connect(networkReply, &QNetworkReply::finished,
                this, &FileDownloader::finishDownload);
    }

    //call this function to abort the ongoing download (if any)
    void abortDownload(){
        if(!networkReply) return;
        a_abortDownload();
        emit backReady();
    }

    //connect to the following signals to get information about the ongoing download
    Q_SIGNAL void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    Q_SIGNAL void downloadSuccessful();
    Q_SIGNAL void downloadError(QString errorString);
    //the next two signals are used to indicate transitions between busy and
    //ready states of the file downloader, they can be used to update the GUI
    Q_SIGNAL void goingBusy();
    Q_SIGNAL void backReady();

private:
    Q_SLOT void readData(){
        QByteArray data= networkReply->readAll();
        destinationFile.write(data);
    }
    Q_SLOT void finishDownload(){
        if(networkReply->error() != QNetworkReply::NoError){
            //failed download
            a_abortDownload();
            emit downloadError(networkReply->errorString());
        } else {
            //successful download
            QByteArray data= networkReply->readAll();
            destinationFile.write(data);
            destinationFile.close();
            networkReply->deleteLater();
            emit downloadSuccessful();
        }
        emit backReady();
    }
    //private function, cleans things up when the download is aborted
    //(due to an error or user interaction)
    void a_abortDownload(){
        networkReply->abort();
        networkReply->deleteLater();
        destinationFile.close();
        destinationFile.remove();
    }

    QNetworkAccessManager* nam;
    QUrl downloadUrl;
    QFile destinationFile;
    QPointer<QNetworkReply> networkReply;
};

//A sample GUI application that uses the above class
class Widget : public QWidget{
    Q_OBJECT
public:
    explicit Widget(QWidget* parent= nullptr):QWidget(parent){
        layout.addWidget(&lineEditUrl, 0, 0);
        layout.addWidget(&buttonDownload, 0, 1);
        layout.addWidget(&progressBar, 1, 0);
        layout.addWidget(&buttonAbort, 1, 1);
        layout.addWidget(&labelStatus, 2, 0, 1, 2);
        lineEditUrl.setPlaceholderText("URL to download");
        connect(&fileDownloader, &FileDownloader::downloadSuccessful,
                this, &Widget::downloadFinished);
        connect(&fileDownloader, &FileDownloader::downloadError,
                this, &Widget::error);
        connect(&fileDownloader, &FileDownloader::downloadProgress,
                this, &Widget::updateProgress);
        connect(&buttonDownload, &QPushButton::clicked,
                this, &Widget::startDownload);
        connect(&buttonAbort, &QPushButton::clicked,
                this, &Widget::abortDownload);

        showReady();
        //use goingBusy() and backReady() from FileDownloader signals to update the GUI
        connect(&fileDownloader, &FileDownloader::goingBusy, this, &Widget::showBusy);
        connect(&fileDownloader, &FileDownloader::backReady, this, &Widget::showReady);
    }

    ~Widget() = default;

    Q_SLOT void startDownload(){
        if(lineEditUrl.text().isEmpty()){
            QMessageBox::critical(this, "Error", "Enter file Url", QMessageBox::Ok);
            return;
        }
        QString fileName =
                QFileDialog::getSaveFileName(this, "Destination File");
        if(fileName.isEmpty()) return;
        QUrl url= lineEditUrl.text();
        fileDownloader.startDownload(url, fileName);
    }
    Q_SLOT void abortDownload(){
        fileDownloader.abortDownload();
    }

    Q_SLOT void downloadFinished(){
        labelStatus.setText("Download finished successfully");
    }
    Q_SLOT void error(QString errorString){
        labelStatus.setText(errorString);
    }
    Q_SLOT void updateProgress(qint64 bytesReceived, qint64 bytesTotal){
        progressBar.setRange(0, bytesTotal);
        progressBar.setValue(bytesReceived);
    }
private:
    Q_SLOT void showBusy(){
        buttonDownload.setEnabled(false);
        lineEditUrl.setEnabled(false);
        buttonAbort.setEnabled(true);
        labelStatus.setText("Downloading. . .");
    }
    Q_SLOT void showReady(){
        buttonDownload.setEnabled(true);
        lineEditUrl.setEnabled(true);
        buttonAbort.setEnabled(false);
        progressBar.setRange(0,1);
        progressBar.setValue(0);
    }

    QGridLayout layout{this};
    QLineEdit lineEditUrl;
    QPushButton buttonDownload{"Start Download"};
    QProgressBar progressBar;
    QPushButton buttonAbort{"Abort Download"};
    QLabel labelStatus{"Idle"};
    QNetworkAccessManager nam;
    FileDownloader fileDownloader{&nam};
};

int main(int argc, char* argv[]){
    QApplication a(argc, argv);

    Widget w;
    w.show();

    return a.exec();
}

#include "main.moc"

Upvotes: 14

Related Questions