My application makes use of QNetworkReply's for send and receiving data from a RESTful API.
There are many tutorials available for using the QNetworkReply with QNetworkAccessManager
Once such example which I used can be found here or even here
Basic Usage:
// Header
QNetworkAccessManager *manager;
QNetworkReply *myReply;
QMetaObject::Connection conReply;
// Making the request
void MainWindow::makeRequest(QString url) {
//...
QNetworkRequest request(QUrl(url));
myReply = manager->get(request) // or post(request)
conReply = QObject::connect(myReply, SIGNAL(finished()), this, SLOT(myReplyResponse()));
}
// Handling the request
void MainWindow::myReplyResponse(){
QObject::disconnect(conReply);
QByteArray data = myReply->readAll();
// or QByteArray data = myReply->read(myReply->bytesAvailable());
myReply->deleteLater();
// do something with this data
//...
}
Using a similar implementation, I request data every X seconds.
Problem:
When receiving the finished() signal, the code handling the reply is triggered, but when reading the data, I get a SIGSEGV.
This issue seems to occur at random, thus I cannot determine what triggers it.
Any suggestions would be gladly accepted.
What is probably happening is that it is delaying the order, let's say that an order is sent every second but it takes 2 seconds to replicate, after 2 seconds you have read the reply and you have deleted it from memory, when the other comes myReply is an empty pointer. What you must do is use sender() to obtain the replica, and it is always advisable to validate that you do not have an empty pointer:
*.h
private:
QNetworkAccessManager *manager;
*.cpp
[...]
manager = new QNetworkAccessManager(this);
[...]
void MainWindow::makeRequest(const QString &url)
{
Qurl mUrl(url);
QNetworkRequest request(mUrl);
QNetworkReply *myReply = manager->get(request); // or post(request)
connect(myReply, &QNetworkReply::finished, this, &MainWindow::myReplyResponse);
}
void MainWindow::myReplyResponse()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if(reply){
QByteArray data = reply->readAll();
qDebug()<<data;
reply->deleteLater();
}
}
Related
I tried to emit signal finished() from my class. But when I'm connecting the signal to my slot, it didn't do anything.
My class' name is blend_install, I declared it as blendinstaller and tried to connect that to the QEventLoop.
....
QEventLoop ac;
connect(&blendinstaller, SIGNAL(finished()), &ac, SLOT(quit()));
blendinstaller.show_progress();
blendinstaller.download(); // this will execute everything and in the end emit finished()
ac.exec();
....
The download() function:
current_prog = BLEND_INSTALL_NONE;
emit progress_changed(current_prog);
manager = new QNetworkAccessManager;
file_handler = new QFile(downloadTo);
file_handler->open(QFile::WriteOnly);
.... handle error .... // each of this (error handling) will emit finished() signal and return;
.... // each of this will represent the process of reporting event changes (for logging), emit a SIGNAL()
QNetworkRequest request;
request.setUrl(QUrl(downloadFrom));
reply = manager->get(request);
event = new QEventLoop;
connect(reply,SIGNAL(finished()),event,SLOT(quit()));
connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(downloadError(QNetworkReply::NetworkError)));
connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(downloadProgressL(qint64,qint64)));
event->exec();
.... handle error ....
.... write reply.readAll() to file ....
....
// these are instruction for a custom QProcess instance
proc.setProgram(extractWith);
proc.setArguments(ar);
proc.setWorkingDirectory(downloadIn);
event = new QEventLoop;
connect(&proc,SIGNAL(finished(int)),event,SLOT(quit()));
connect(&proc,SIGNAL(error(QProcess::ProcessError)),this,SLOT(extractError(QProcess::ProcessError)));
connect(&proc,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(extractFinished(int,QProcess::ExitStatus)));
proc.start();
proc.open_console();
event->exec();
.... handle error ....
....
.... attempt to find output of QProcess (extract an archive) ....
.... handle error, output of QProcess not found ....
....
emit installed(installOn);
emit finished(); // the SIGNAL I want to get.
qDebug("It's finished installing!");
So, TL;DR each of errors handling will return from the function but also emit finished() and in the end of function (assuming there is no error) it will emit finished().
It won't quit the loop.
Any idea?
The problem with your download() method is, that it is already a synchron method. You don't need this event loop. You already do everything in event loops inside your download() method.
Side note: And you seem to have some memory leaks, since you create QEventLoop without parent and never delete it.
UPDATE #1:
Your finished() event is not being handled by the outer QEventLoop (ac), because the finished() signal gets emitted before the QEventLoop even begins to handle events with exec().
As a ugly workarround you could invoke download() after exec() with a queued QMetaObject::invokeMethod() (Qt::QueuedConnection) call (but i would not recommend it).
Update #2
Here is a small example, which is not perfect, of course :P
class BlendDownloader
{
Q_OBJECT
public:
BlenDownloader() :
_file(NULL)
{
}
void download()
{
_file = new QFile("C:/myfile.blubb");
QNetworkRequest req("your url here");
QNetworkReply* reply = _mgr.get(req);
QObject::connect(reply, SIGNAL(finished()), this, SLOT(onDownloadFinished()));
// TODO: Also handle error callback here
}
private slots:
void onDownloadFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater();
// Write response data to file.
// Note: You might get problems with big files,
// since this buffers the entire response of QNetworkReply
// in internal buffer. It's better to use the "readyRead()"
// signal and write incrementally.
_file->write(reply->readAll());
// Do your parsing stuff now and emit "finished()" at the end.
// ... parsing, validation here ...
// Clean up
_file->close();
delete _file;
_file = NULL;
emit finished();
}
private:
QNetworkManager _mgr;
QFile* _file;
};
void QGCCore::getLatestGCSVersion()
{
QString url = "http://xxxxx";
QNetworkRequest newRequest(url);
newRequest.setUrl(url);
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
networkManager->get(newRequest);
}
void QGCCore::downloadFinished(QNetworkReply * reply)
{
reply->deleteLater();
}
The above code, with empty constructor and destructor.
If I start the software and close it, it gives warning:
QWaitCondition: destroyed while thread is still running.
However, if I wait a while to close this, then it is Ok...
What's going wrong here?
I realized downloading multiple files, but I don't know how to implement the total progress bar of the download, that is common.
My code:
QNetworkAccessManager manager;
QList<QNetworkReply *> currentDownloads;
void MainWindow::checkUpdate()
{
QStringList files;
files << "http://cavexp.net/uploads/game/Theugry/zips/resourcepacks.zip"
<< "http://cavexp.net/uploads/game/Theugry/zips/resourcepacks.zip";
doDownload(files);
}
void MainWindow::doDownload(const QVariant& v)
{
if (v.type() == QVariant::StringList) {
foreach (QString url, v.toStringList()) {
QNetworkReply* reply = manager.get(QNetworkRequest(QUrl(url)));
connect(&manager, SIGNAL(downloadProgress(qint64, qint64)),
this, SLOT(updateDownloadProgress(qint64, qint64)));
currentDownloads.append(reply);
}
}
}
void MainWindow::downloadFinished(QNetworkReply *reply)
{
currentDownloads.removeAll(reply);
reply->deleteLater();
}
void MainWindow::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
{
ui->progressBar->setMaximum(totalBytes);
ui->progressBar->setValue(bytesRead);
}
I would be grateful for any help and hints! Thank you.
You probably need to iterate over your 'currentDownloads' list and connect to each one's signal downloadProgress. Then your slot(s) will be called from all of them. In that slot(s) you'll have to sum up all information coming as parameters of QNetworkReply::downloadProgress signal.
You can create a dedicated object for each QNetworkReply instance of your currentDownloads list so that you know from to which file a coming signal belongs, but if I am not mistaking you can also use single slot for all of them and then there is a meta function in Qt that will tell you from which sender the signal came.
P.S. In response to your request for small example here is "straight-forward" approach (without using QSignalMapper or QObject::sender()):
Implement a class "ProgressListenner" something like this (beware I am writing pseudo-code and you'll need to add/fix some necessarily stuff to make it actually working):
class ProgressListenner
{
public:
ProgressListenner() : _lastKnownReceived(0), _lastKnownTotal(0){}
qint64 _lastKnownReceived;
qint64 _lastKnownTotal;
slots:
onDownloadProgress ( qint64 bytesReceived, qint64 bytesTotal )
{
_lastKnownReceived = bytesReceived;
_lastKnownTotal = bytesTotal;
}
}
Than after your line QList<QNetworkReply *> currentDownloads; add QList<ProgressListenner*> downloadListenners;. Inside your foreach each time you are adding new QNetworkReply object to currentDownloads also:
1. create new instance of ProgressListenner and add it to downloadListenners.
2. connect signal of that particular QNetworkReply to that corresponding ProgressListenner's slot: connect(reply, SIGNAL(downloadProgress(qint64, qint64)), pListenner, SLOT(onDownloadProgress (qint64, qint64)));
This way every time some QNetworkReply will fire it's progress signal, slot of corresponding ProgressListenner will be called.
Next step is sum up numbers from all downloads. One simple way is:
1. Create one more function in ProgressListenner class and make it static (important). Let say the name of function is CommonProgress.
2. At the end of onDownloadProgress function call also call CommonProgress
3. In CommonProgress function (taking care about thread safety!) iterate over all elements of downloadListenners and sum up their _lastKnownReceived and _lastKnownTotal. Do the necessarily arithmetic... Don't forget that bytesTotal can be -1!!!
i would write a programm that becomes data from a Url. When i open this Url with firefox, i became a .json data. The same i would make with my qt project: Open a url, that returns the same .json data. I must use the QNetworkAccessManager, right ?
But when i use the example from the qt project site, it make a syntax error. I have make all includes.
This is the code from the example , i use the same in my project.:
QNetworkRequest request;
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
QNetworkReply *reply = manager->get(request);
manager->get(QNetworkRequest(QUrl("http://qt.nokia.com")));
request.setUrl(QUrl("http://qt.nokia.com"));
I know that this dont returns me my .json data, but at first i will can opened a url.
Why my program make a syntax error. When i delete this, my program works. ( Without parsing, but it views and the buttoncklicks works.
Thank you
It seams you got error message something like this "invalid use of 'this' in non-member function" right?
When you use *this pointer it should point to some object.
You have to do something like here:
class Request : public QObject {
Q_OBJECT
public:
Request(QObject *parent=0);
~Request();
public slots:
void replyFinished(QNetworkReply *))
private:
QNetworkAccessManager *m_manager;
}
Then in request.cpp you can do it like here:
Request::Request(QObject *parent): QObject(parent) {
m_manager = new QNetworkAccessManager(this);
QObject::connect(m_manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply *)));
}
Request::~Request() {
delete m_manager;
}
void Request::replyFinished(QNetworkReply *reply) {
//do_something
}
Then you can use it like here:
Request *handle = new Request():
handle->do_some_methods();
//...etc
This is my code. But I am confused where should I delete m_networkManager. I can do that in
onRequestCompleted() slot but the problem is my program calls getData function frequently.
My fear is this case:
getData() is called.
Before onRequestCompleted() slot is fired, my
program calls getData() again.
onRequestCompleted() for the first getData() call gets fired.
my program deletes m_networkManager which was actually allocated other memory when getData() was called second time.
I think this situation can cause crash.
void MyApp::getData() {
QNetworkRequest request;
request.setUrl(QUrl("http://www.domain.foo"));
m_networkManager = new QNetworkAccessManager(this); // Instance variable
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onRequestCompleted(QNetworkReply *)));
m_networkManager->get(request);
}
void MyApp::onRequestCompleted(QNetworkReply *reply) {
QByteArray data = reply->readAll();
reply->deleteLater();
//m_networkManager->deleteLater();
}
Since you create new QNetworkAccessManager instance each time MyApp::getData() called, you need to manage to delete the right one in MyApp::onRequestCompleted() function. The problem in your code is that you always delete the last created QNetworkAccessManager and not the one that caused the slot call. My suggestion is following:
void MyApp::getData() {
[..]
// Not class member. Will be deleted in the slot.
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onRequestCompleted(QNetworkReply *)));
[..]
}
void MyApp::onRequestCompleted(QNetworkReply *reply) {
QByteArray data = reply->readAll();
reply->deleteLater();
// Delete object whose signal triggered this slot.
QObject *networkManager = sender();
networkManager->deleteLater();
}