Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downloading File in Qt From URL

Tags:

c++

qt

qt4

In my program I need to download a file, and I came across this article:

http://www.java2s.com/Code/Cpp/Qt/DownloadfromURL.htm

This code does work but it doesn't fit into my program so I re-coded it. I haven't completed it all but I've got the basics coded. However, when I test it, it pops up with a send error report window.

So far this is my code:

QtDownload.h

#include <QObject>
#include <QString>
#include <QNetworkAccessManager>
#include <QNetworkReply>


class QtDownload : public QObject
{
    Q_OBJECT
public:
    explicit QtDownload();
    ~QtDownload();

    void setTarget(const QString& t);

private:
    QNetworkAccessManager manager;
    QNetworkReply* reply;
    QString target;
    void connectSignalsAndSlots();

signals:

public slots:
    void download();
    void downloadFinished(QNetworkReply* data);
    void downloadProgress(qint64 recieved, qint64 total);
};

QtDownload.cpp

#include "qtdownload.h"

#include <QUrl>
#include <QNetworkRequest>
#include <QFile>

QtDownload::QtDownload()
    : QObject(0)
{
    this->connectSignalsAndSlots();
}

QtDownload::~QtDownload()
{
    if (reply != 0)
        delete reply;
}

void QtDownload::connectSignalsAndSlots()
{
    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(downloadFinished(QNetworkReply*)));
    QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));
}

void QtDownload::setTarget(const QString &t)
{
    this->target = t;
}

void QtDownload::downloadFinished(QNetworkReply *data)
{
    QFile localFile("downloadedfile");
    if (!localFile.open(QIODevice::WriteOnly))
        return;
    localFile.write(data->readAll());
    localFile.close();
    delete data;
    data = 0;
}

void QtDownload::download()
{
    QUrl url = QUrl::fromEncoded(this->target.toLocal8Bit());
    QNetworkRequest request(url);
    this->reply = manager.get(request);
}

void QtDownload::downloadProgress(qint64 recieved, qint64 total)
{

}

main.cpp

#include "qtdownload.h"
#include <QTimer>

int main()
{
    QtDownload dl;
    dl.setTarget("http://www.java2s.com/Code/Cpp/Qt/DownloadfromURL.htm");

    QTimer::singleShot(0, &dl, SLOT(download()));
}

As I said it's not completely finished but I want this part to be working before I move on.

I'm also new to Qt so any tips would be appreciated.

like image 894
Blair Harris Avatar asked Dec 08 '10 03:12

Blair Harris


3 Answers

  • You're using uninitialized pointer, so it points out to nowhere. Initialize reply with NULL in your constructor.
  • You should connect reply after it was created (reply = manager.get(...)), not inside of your constructor.
  • QNetworkReply is never deleted by QNetworkManager as docs say:

Do not delete the reply object in the slot connected to this signal. Use deleteLater().

So you shouldn't call delete on QNetworkReply in finished slot.

  • In finished slot setting data to 0 will only set parameter value to 0, not your class member reply. It's an unneeded line of code. You should set your reply member to NULL instead.

Also you should consider writing to a file every time you get data chunk, as whole file will be buffered in memory in your current case. It may lead to huge memory usage of your software when file at pointed URL is big.

like image 106
Kamil Klimek Avatar answered Nov 14 '22 19:11

Kamil Klimek


You need QCoreApplication to start the event loop for Qt4. Something like this should work (not tested) :

int main(int argc, char **argv) {
    QCoreApplication app(argc, argv);
    QtDownload dl;
    dl.setTarget("http://www.java2s.com/Code/Cpp/Qt/DownloadfromURL.htm");

    dl.download();
    QObject::connect(app, SIGNAL(aboutToQuit()), app, SLOT(quit()));
    return app.exec();
}

edit :: new version

I found some problems :

  1. You don't need the custom reply, also you never set it to 0 in your constructor, so if it was never used it will delete a random piece of memory in your ~QtDownload();
  2. you were deleting data inside QtDownload::downloadFinished, which shouldn't be done, it is handled by Qt, so it was getting deleted twice.
  3. because of #2, you were deleting reply 3 times.

Here's the modified version :

qtdownload.h :

#include <QObject>
#include <QString>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>


class QtDownload : public QObject {
    Q_OBJECT
public:
    explicit QtDownload();
    ~QtDownload();

    void setTarget(const QString& t);

private:
    QNetworkAccessManager manager;
    QString target;

signals:
    void done();

public slots:
    void download();
    void downloadFinished(QNetworkReply* data);
    void downloadProgress(qint64 recieved, qint64 total);
};

qtdownload.cpp :

#include "qtdownload.h"
#include <QCoreApplication>
#include <QUrl>
#include <QNetworkRequest>
#include <QFile>
#include <QDebug>

QtDownload::QtDownload() : QObject(0) {
    QObject::connect(&manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(downloadFinished(QNetworkReply*)));
}

QtDownload::~QtDownload() {

}


void QtDownload::setTarget(const QString &t) {
    this->target = t;
}

void QtDownload::downloadFinished(QNetworkReply *data) {
    QFile localFile("downloadedfile");
    if (!localFile.open(QIODevice::WriteOnly))
        return;
    const QByteArray sdata = data->readAll();
    localFile.write(sdata);
    qDebug() << sdata;
    localFile.close();

    emit done();
}

void QtDownload::download() {
    QUrl url = QUrl::fromEncoded(this->target.toLocal8Bit());
    QNetworkRequest request(url);
    QObject::connect(manager.get(request), SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64)));

}

void QtDownload::downloadProgress(qint64 recieved, qint64 total) {
    qDebug() << recieved << total;
}

main.cpp :

#include <QtCore>
#include "qtdownload.h"
int main(int argc, char **argv) {
    QCoreApplication app(argc, argv);
    QtDownload dl;
    dl.setTarget("http://localhost");

    dl.download();
    //quit when the download is done.
    QObject::connect(&dl, SIGNAL(done()), &app, SLOT(quit()));
    return app.exec();
}
like image 34
OneOfOne Avatar answered Nov 14 '22 20:11

OneOfOne


As you asked for it, some general comments:

void QtDownload::downloadFinished(QNetworkReply *data)
{
    QFile localFile("downloadedfile");
    if (!localFile.open(QIODevice::WriteOnly))
        return;
    localFile.write(data->readAll());
    localFile.close();
    delete data;
    data = 0;
}
  1. You read all data in one chunk. Bad for big files. Better read it incrementally.
  2. Deleting the argument data from a slot is dangerous. You don't know whether the network manager continues to use (or delete) the object "data" points to right after it emits the finished signal. Probably you don't even have to delete the reply, if its owned by the manager, something to check the documentation for.
  3. If opening the files fails, data is not deleted. So whatever is correct, its inconsistent. Either you leak or you have the risk of double-deletion.
  4. localFile.write(data->readAll()) is not guaranteed to write all data at once. that's why it has a return value, which you should check, to make sure everything is written. If it returns -1, you should handle the error.

    if (reply != 0)
        delete reply;
    

Omit the if. Deleting a null pointer is safe.

like image 1
Frank Osterfeld Avatar answered Nov 14 '22 19:11

Frank Osterfeld