[Solved] Cannot download using QNetworkAccessManager



  • Hello. QNetworkAccessManager proved to be a pretty tough task. I have been fighting it for more than a week already... The following code is not working. It does not give any errors but it does not load the file either.

    Downloader.h
    [code]
    #ifndef DOWNLOADER_H
    #define DOWNLOADER_H

    #include <QObject>
    #include <QNetworkAccessManager>
    #include <QNetworkRequest>
    #include <QNetworkReply>
    #include <QUrl>
    #include <QDateTime>
    #include <QFile>
    #include <QDebug>

    class Downloader : public QObject
    {
    Q_OBJECT
    public:
    explicit Downloader(QObject *parent = 0);
    void doDownload();
    QString getData();

    signals:

    public slots:
    void replyFinished (QNetworkReply *reply);

    private:
    QNetworkAccessManager *manager;
    QByteArray data;
    QString dataFinal;
    };

    #endif // DOWNLOADER_H
    [/code]

    Downloader.cpp

    [code]
    #include "Downloader.h"

    Downloader::Downloader(QObject *parent) :
    QObject(parent)
    {
    }

    void Downloader::doDownload()
    {
    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
    manager->get(QNetworkRequest(QUrl ("ftp://someftp.com/somefile.TXT")));
    }

    void Downloader::replyFinished (QNetworkReply *reply)
    {
    if(reply->error())
    {
    qDebug() << "ERROR!";
    qDebug() << reply->errorString();
    }
    else
    {
    data = reply->readAll();
    QString dataStr(data);
    dataFinal = dataStr;
    reply->deleteLater();
    }
    }

    QString Downloader::getData()
    {
    return dataFinal;
    }
    [/code]

    mainWindow.cpp

    [code]
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "Downloader.h"

    MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    }

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

    void MainWindow::on_pushButton_clicked()
    {
    Downloader d;
    d.doDownload();

    QString metarData;
    metarData = d.getData();
    ui->plainTextEdit->setPlainText(metarData);
    

    }
    [/code]

    Thank you so much in advance!

    Igor


  • Moderators

    You are reading the data too soon. QNAM is asynchronous. You need to wait for the reply to come, before you can read it.



  • Hm, thanks! Too bad the examples that I have seen omit that. I will search for the solution.
    Again, thanks a lot!

    EDIT:
    So I have found that I need to have
    [code]
    QEventLoop loop;
    loop.exec();
    [/code]

    Can you prompt please where exactly it goes in the code?

    Thanks!


  • Moderators

    They don't. You are simply doing it wrong in your MainWindow. Downloader class is done correctly.



  • sierdzio. Thank you again. Can you please elaborate what exactly I am doing wrong? Cheers!


  • Moderators

    @
    Downloader d;
    d.doDownload();

    QString metarData;
    // This is WAY too soon!
    metarData = d.getData();
    @

    instead of getting the data in the very same method, you need to wait until the reply comes. Either use a forever loop (ugly solution!), or, just like you are doing in Downloader: respond to replyFinished().



  • sierdzio,

    Sorry, I am trying to understand what you mean by "like you are doing in Downloader: respond to replyFinished()."

    I have been searching more on this and another suggestion that I have come across with was to put the d object on the heap. I changed it to:

    [code]
    Downloader* d = new Downloader;
    d->doDownload();

    QString metarData;
    metarData = d->getData();
    ui->plainTextEdit->setPlainText(metarData);
    

    [/code]

    Still no luck... Sorry for being so n00b.. ))


  • Moderators

    When the button is clicked, only send the request (doDownload). Then getData in a slot connected to finished(). You need to refactor the code bit. Here is a small helper:
    @
    // Downloader
    signals:
    void downloadFinished(Downloader *d);
    ...
    void Downloader::replyFinished (QNetworkReply *reply) {
    ...
    emit downloadFinished(this);
    }

    // MainWindow
    void MainWindow::on_pushButton_clicked()
    {
    Downloader *d = new Downloader(this);
    connect (d, SIGNAL(downloadFinished(Downloader *)), this, SLOT(someSlot(Downloader *)));
    d->doDownload();
    ...
    }
    ...
    void MainWindow::someSlot(Downloader *down)
    {
    QString metarData;
    metarData = down->getData();
    // Optional:
    down->deleteLater();
    }
    @



  • sierdzio. You do belong to the Hall of Fame for your support and knowledge. I am still trying to nail it down. This is what I have now:

    [code]
    #include "Downloader.h"

    Downloader::Downloader(QObject *parent) :
    QObject(parent)
    {
    }

    void Downloader::doDownload()
    {
    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply*)),this, SLOT(replyFinished(QNetworkReply*)));
    manager->get(QNetworkRequest(QUrl("ftp://ftp.com/ftp.TXT")));

    }

    void Downloader::replyFinished (QNetworkReply *reply)
    {
    if(reply->error())
    {
    qDebug() << "ERROR!";
    qDebug() << reply->errorString();
    }
    else
    {
    dataByte = reply->readAll();
    emit downloadFinished(this);
    reply->deleteLater();
    }
    }

    void Downloader::downloadFinished(Downloader *dfinished)
    {
    QString dataStr(dataByte);
    dataFinal = dataStr;
    dfinished->deleteLater();
    }

    QString Downloader::getData()
    {
    return dataFinal;
    }

    [/code]

    MainWindow.cpp
    [code]
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "Downloader.h"

    MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    }

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

    void MainWindow::on_pushButton_clicked()
    {
    d = new Downloader(this); // declared in header
    connect(d, SIGNAL(downloadFinished(Downloader *)), this, SLOT(someSlot(Downloader *)));
    d->doDownload();

    ui->plainTextEdit->setPlainText(metarData);
    

    }

    void MainWindow::someSlot(Downloader* down)
    {
    metarData = d->getData();
    down->deleteLater();
    }
    [/code]

    Now, I get a weird error saying that
    [code]
    multiple definition of Downloader::downloadFInished(Downloader*)
    first define here
    [/code]

    Weird, because I only have it defined in Downloader.h
    [code]
    #ifndef DOWNLOADER_H
    #define DOWNLOADER_H

    #include <QObject>
    #include <QNetworkAccessManager>
    #include <QNetworkRequest>
    #include <QNetworkReply>
    #include <QUrl>
    #include <QDateTime>
    #include <QFile>
    #include <QDebug>
    #include <QEventLoop>

    class Downloader : public QObject
    {
    Q_OBJECT
    public:
    explicit Downloader(QObject *parent = 0);
    void doDownload();
    QString getData();

    signals:
    void downloadFinished(Downloader *dfinished);

    public slots:
    void replyFinished (QNetworkReply *reply);

    private:
    QNetworkAccessManager *manager;
    QByteArray dataByte;
    QString dataFinal;
    };

    #endif // DOWNLOADER_H
    [/code]

    I love Qt, but it is so discouraging to spend many days on something that took me a day to figure out how to do it in libcurl and Java.

    sierdzio. Thank you again!


  • Moderators

    If you want easy, go with QHttp or QFtp ;) QNAM is powerful, but also more complicated. The asynchronous paradigm is much better, but also more complicated and harder to get.

    In your case, you've hit a small detail that some people new to Qt do stumble upon: a signal is not a method, only the slots need implementation. So, you should delete all this:
    @
    // Delete!
    void Downloader::downloadFinished(Downloader *dfinished)
    {
    QString dataStr(dataByte);
    dataFinal = dataStr;
    dfinished->deleteLater();
    }
    @

    And only leave this line in the header file:
    @
    signals:
    void downloadFinished(Downloader *dfinished);
    @



  • sierdzio. You are the MAN! Thank you. Now the file is getting downloaded. I see it in qDebug. One problem still remains though due the asynchronous process, I believe. The thing is that when in the following code:

    [code]
    void MainWindow::on_pushButton_clicked()
    {
    d = new Downloader(this);
    d->doDownload();
    connect(d, SIGNAL(downloadFinished(Downloader *)), this, SLOT(someSlot(Downloader *)));

    qDebug() << "MetarData: " <&lt; metarData;
    ui-&gt;plainTextEdit->setPlainText(metarData);
    

    }

    void MainWindow::someSlot(Downloader* down)
    {
    metarData = d->getData();
    down->deleteLater();
    }
    [/code]

    when I do this:
    [code]
    ui->plainTextEdit->setPlainText(metarData);
    [/code]

    it does not do anything. Apparently, it does not wait for the file to be first downloaded and goes straight to setPlainText because

    [code]
    qDebug() << "MetarData: " << metarData
    [/code]

    returns an empty string. In my main application, where metarData is further passed to other functions, I even get a CTD.
    I thought that connect should take care of this and the code would wait for the full data to be downloaded first.

    Thanks!



  • On the other hand, I can move it to the someSlot function and let it be there. Anyways, thanks a lot for your great help!


  • Moderators

    You assign the variable at the wrong time. Do this instead:
    @
    void MainWindow::someSlot(Downloader* down)
    {
    metarData = d->getData();
    ui->plainTextEdit->setPlainText(metarData);
    down->deleteLater();
    }
    @



  • Yes. Thanks a lot. Very much appreciated.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.