QT C++ download multiple files with progress total - c++

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!!!

Related

QNetworkReply throwing SIGSEGV when finished signal emitted

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();
}
}

Test method with Signals and Slots

I have the following two simple methods in a Qt Console Application:
void Webservice::getFile(PostElement el)
{
parameter = &el;
QUrl url(PATH);
QUrlQuery query;
query.addQueryItem(el.getParam(), el.getValue());
url.setQuery(query);
request.setUrl(url);
manager->get(request);
connect(manager, SIGNAL(finished(QNetworkReply*)), this,
SLOT(downloadCompleted(QNetworkReply*)));
}
void Webservice::downloadCompleted(QNetworkReply *reply)
{
QByteArray b = reply->readAll();
qDebug() << "Download completed!";
QFile file("/tmp/" + parameter->getValue());
file.open(QIODevice::WriteOnly);
file.write(b);
file.close();
reply->deleteLater();
}
I created a QTest project to test getFile method.
#include <QtTest>
#include <QCoreApplication>
// add necessary includes here
#include "webservice.h"
class WebserviceTest : public QObject
{
Q_OBJECT
public:
WebserviceTest();
~WebserviceTest();
private:
Webservice ws;
private slots:
void initTestCase();
void cleanupTestCase();
void getFile();
};
WebserviceTest::WebserviceTest()
{
}
WebserviceTest::~WebserviceTest()
{
}
void WebserviceTest::initTestCase()
{
}
void WebserviceTest::cleanupTestCase()
{
}
void WebserviceTest::getFile()
{
PostElement el{"file", "myPic.png"};
ws.getFile(el); // it should wait for the slot to be executed
}
QTEST_MAIN(WebserviceTest)
#include "tst_webservicetest.moc"
As you can see, it has signals and slots. If I run that one on my original project, I see that downloadCompleted executes without issues (because it is using the Event Loop). But in the test project the slot is not called. I googled and found some examples using QSignalSpy, but at the moment I have not executed the slot successfully.
How to tell my test to "wait" for the slot to be completed?
That's exactly the purpose of QSignalSpy, it basically spins new QEventLoop that waits for signal to be executed. Your tests starts and asynchronous operation but it doesn't wait for it to finish so it will destruct objects while the operation is ongoing.
Qt test also has some built-in timeouts that prevents tests to be stuck which are stopped when QSignalSpy is used. In your case you need to emit new signal from your class Webservice indicating that download is finished, e.g. (you can also pass arguments to the signal) if needed:
class Webservice : public QObject {
Q_OBJECT
// Constructors etc.
signals:
void finished();
}
Then at the end of your void Webservice::downloadCompleted(QNetworkReply *reply):
emit finished();
and in your test something like this:
PostElement el{"file", "myPic.png"};
// Prepare spy
QSignalSpy spy(&ws, SIGNAL(finished));
ws.getFile(el);
// Wait for result - by default this waits 5 seconds
spy.wait();
// Check if we had a timeout or actually signal was raised
QCOMPARE(spy.count(), 1);

get HTML from QWebEnginePage in QWebEngineView using Lamda

I want to get the HTML code of a web page opened in QWebEngineView I use toHtml() function in QWebEnginePage Class like this
QWebEnginePage *page = ui->widget->page();
QString HTML = "";
page->toHtml([&HTML](QString html){qDebug() << "code \n\n\n" << html;});
the HTML code of html page appeared in qDebug good without problem
the problem here is when I want to use HTML string outside the function when I show the size of the HTML varible it is equal to zero and empty
so I tried this
QWebEnginePage *page = ui->widget->page();
QString HTML = "";
page->toHtml([&HTML](QString html){HTML = html;}); // crash
qDebug() << "i want to use HTML here outside the function = " << HTML;
but the app crash show so what should I do so I put the HTML data in the HTML variable so I can use it outside the function
Thanks in advance
Your problem is caused by the fact that the lambda is run asynchronously. So it is really called after you have exited the method in which you call toHtml method and that also explains the crash - HTML is a local variable within the method which has already exited so the lambda just randomly corrupts the memory used to be occupied by HTML variable.
What you want to do here is to synchronize things i.e. block your method until the lambda is executed. It can be done with QEventLoop but that would need to involve sending a special signal from the lambda to indicate the fact that the lambda finished executing. So it would look somewhat like this (non-tested):
class MyClass: public QObject
{
Q_OBJECT
public:
MyClass(QWebEnginePage & page, QObject * parent = 0);
void notifyHtmlReceived();
QString getHtml();
void setHtml(const QString & html) { m_html = html; }
Q_SIGNALS:
void htmlReceived();
private Q_SLOTS:
void requestHtmlFromPage();
private:
QWebEnginePage & m_page;
QString m_html;
};
MyClass::MyClass(QWebEnginePage & page, QObject * parent) :
QObject(parent),
m_page(page)
{}
void MyClass::notifyHtmlReceived()
{
emit htmlReceived();
}
QString MyClass::getHtml()
{
QEventLoop loop;
QObject::connect(this, SIGNAL(htmlReceived()), &loop, SLOT(quit()));
// Schedule the slot to run in 0 seconds but not right now
QTimer::singleShot(0, this, SLOT(requestHtmlFromPage()));
// The event loop would block until the lambda receiving the HTML is executed
loop.exec();
// If we got here, the html has been received and the result was saved in m_html
return m_html;
}
void MyClass::requestHtmlFromPage()
{
m_page.toHtml([this](QString html)
{
this->setHtml(html);
this->notifyHtmlReceived();
});
}

How to use threads in qt

I am new to QT. I need to use threads for some purpose. I searched a lot about threading in QT but all the articles and videos are using same example. They are using dialog and putting a label with 2 buttons to print some data on label. I want to use threads with MainWindow. My application include reading a serial data and then displaying the related information on a label. That information contains a string and an audio file. String and audio file needs to be played at the same time. I have a connected a signal for serial read like below:
connect(&Serial, SIGNAL(readyRead()), this, SLOT(SerialRead()));
QString MainWindow::SerialRead()
{
word Words; //
QString serialData = Serial.readAll(); //Reading Serial Data
//Now here I want to start the two threads
//Thread 1 to display string
//Thread 2 to play audio
return 0;
}
How can I achieve above task. Can anyone please refer me to some usefull links or articles. Thanks
While I very highly recommend that you use std::thread instead of QThread, it's your call. However, on the Qt docs page of QThread there's a very good example that exactly fits what you need. Here it's:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString &parameter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
Basically, in this example, the Controller is your MainWindow, and the constructor of Controller is your MainWindow::SerialRead(). Be careful with memory and thread management though if you want to do that, because that Controller is made to destroy things when it exists, not when the thread is finished.
So you either use that controller as is (simply instantiate it in your MainWindow::SerialRead()), or change it to include parts of it in your MainWindow.
you may not need to use 2 threads to do such things. just emit a signal connected to setText(const QString&) and the other signal connected to the slot for playing audio. what is the size of serial data?

Wait for a SLOT to finish

I use QNetworkAccessManager to do form POST.
I have connected signals and slots as:
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(readCookies(QNetworkReply*)));
Now, I make a request by doing:
manager->post(request,postData);
Now readCookies(QNetworkReply *) will be run as soon as SIGNAL is emitted. Now, using the Cookies which I get in this slot, I have to make one more POST..
As signals & slots are asynchronous, I want to wait till I get the cookies from my first POST and then I again want to do another post using the cookies I got in first POST like
//Setting new request, headers etc...
manager->post(request2,postData2);
I want the later to always be executed after first one has executed (so that I get proper cookies value).
What is the way to go? I am new to all these SIGNALS & SLOTS so please bear with me.
You can do the post in your readCookies() slot:
void readCookies( QNetworkReply* reply ) {
if ( ...error? ) {
report error...
return;
}
...
manager->post(request2,postData2);
}
I will be called when the cookies is read, and you can then continue with your post. Connect that to a second slot, and so on.
Managing multiple, possibly parallely running asynchronous operations like this can become errorprone though, if you manage many of them in a single object. I would suggest to use the Command Pattern - here I described why I find it extremely useful in exactly this context. The sequence of request and asnychronous operations is encapsulated in a single object (abbreviated, with some pseudo-code):
class PostStuffOperation : public QObject {
Q_OBJECT
public:
enum Error {
NoError=0,
Error=1,
...
};
Error error() const; //operation successful or not?
QString errorString() const; //human-readable error description
... setters for all the information the operation needs
...
void start() {
...start your first request and connect it to cookiesRead
}
public Q_SLOTS:
void cookiesRead( QNetworkReply * ) {
if ( error ) {
// set error and errorString...
emit finished( this ); //couldn't read cookies, so the operation fails
return;
}
... do post
}
void postFinished( QNetworkReply* ) {
if ( error ) {
// set error and errorString...
}
emit finished( this ); //post finished - that means the whole operation finished
}
Q_SIGNALS:
void finished( PostStuffOperation* );
};
To start the operation, you do
PostStuffOperation op* = new PostStuffOperation( this );
... pass data like server, port etc. to the operation
connect( op, SIGNAL(finished()), this, SLOT(postOperationFinished()) );
op->start();
void postOperationFinished( PostStuffOperation* op ) {
if ( op->error != PostStuffOperation::NoError ) {
//handle error, e.g. show message box
}
}
It makes sense to have a common baseclass for such operations, see e.g. KDE's KJob.
You can connect a signal from this to a slot from your manager and emit the signal after reading the cookies. By example:
connect(this, SIGNAL(cookiesRead()), manager, SLOT(PostAgain());
So your readCookies function will be:
{
// Read cookies
emit cookiesRead();
}
Of course you can send all data you want form signal to slot.
Hope that helps
You can send a second signal connected to another slot (the resend slot), if you have finished the evaluation of your first cookie. You can do that directly in the slot. You can also call slots like a normal member function.