I'm making a program that uses lots of timer and, at intervals of 4 seconds, does an online post to a php script.
I'm coding in QtCreator 5.1. I use classes just like the ones below.
The one below just populates a task list, but throughout the course of 8 to 12 hours, the memory that the program takes up just keep rising and rising gradually.
What am I doing wrong while using this class?
I have to be able to keep posting online like I already am, about every 4 to 8 seconds.
Here's a simple class for taking care of one of my processes:
Header file: tasklistprocess.h
#ifndef TASKLISTPROCESS_H
#define TASKLISTPROCESS_H
#include <QThread>
#include <QtCore>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QListWidget>
#include <QTabWidget>
#include "globalhelper.h"
#include "securityinfo.h"
class TaskListProcess : public QThread
{
Q_OBJECT
public:
explicit TaskListProcess(QListWidget *obj_main, QTabWidget *tabs_main, QString user, QObject *parent = 0);
signals:
void upTaskStorage(int key,QHash<QString,QString> item);
private:
GlobalHelper gh;
Securityinfo sci;
QNetworkAccessManager *nam;
QNetworkRequest request;
QByteArray data;
// this is the disposable params for reusage through out the class
QUrlQuery params;
QString post_data;
QString user_name;
QTimer *tasklist_tmr;
bool get_task_list;
QListWidget *obj;
QTabWidget *tabs;
private slots:
void setTaskList();
void replyFinished(QNetworkReply *reply);
void sendPost(QString file_name, QUrlQuery params);
};
#endif // TASKLISTPROCESS_H`
Source file: tasklistprocess.cpp
#include "tasklistprocess.h"
TaskListProcess::TaskListProcess(QListWidget *obj_main, QTabWidget *tabs_main, QString user, QObject *parent) :
QThread(parent)
{
user_name = user;
get_task_list = false;
obj = obj_main;
tabs = tabs_main;
tasklist_tmr = new QTimer(this);
connect(this,SIGNAL(started()),this,SLOT(setTaskList()));
connect(tasklist_tmr,SIGNAL(timeout()),this,SLOT(setTaskList()));
nam = new QNetworkAccessManager(this);
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
request.setRawHeader( "User-Agent" , "Mozilla Firefox" );
// here we connect up the data stream and data reply signals
connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
}
void TaskListProcess::setTaskList()
{
qDebug() << "Your task list was set";
bool in = false;
if(!(tasklist_tmr->isActive()))
{
tasklist_tmr->start(10000);
in = true;
}
if(!(get_task_list))
{
params.clear();
params.addQueryItem("user_name", user_name);
params.addQueryItem("logged_in", "1");
sendPost("getTaskList.php",params);
get_task_list = true;
}
else
{
if(post_data.contains("|*|"))
{
//here i retrieve a piece of information from a php script which is stored in a custom string format
// here we clear the list for the new data to be put in
if(obj->count()>0)
{
obj->clear();
}
int key = 0;
foreach(QString inner_task,post_data.split("|*|"))
{
QHash<QString,QString> task_cont;
//qDebug() << " ";
if(inner_task.contains("*,*"))
{
foreach(QString task_val,inner_task.split("*,*"))
{
if(task_val.contains("*=*"))
{
QStringList key_pairs = task_val.split("*=*");
task_cont.insert(key_pairs[0],key_pairs[1]);
if(key_pairs[0] == "tt")
{
QString val_in;
if(key_pairs[1].length()>10)
{
// this sets the title to the shortened version
// if the string length is too long
val_in = key_pairs[1].left(10) + "....";
}
else
{
val_in = key_pairs[1];
}
obj->addItem("Task :" + QString::fromUtf8(key_pairs[1].toStdString().c_str()));
}
}
}
}
//task_storage.insert(key,task_cont);
emit upTaskStorage(key,task_cont);
key ++;
}
}
get_task_list = false;
}
// here we're checking to see if they are looking at the task tab so it doesn't keep changing
// back and forth between the tabs
bool change = true;
if(tabs->currentIndex() != 0)
{
change = false;
}
if(change)
{
tabs->setCurrentIndex(0);
}else if(in)
{
tabs->setCurrentIndex(0);
}
}
void TaskListProcess::replyFinished(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Error in" << reply->url() << ":" << reply->errorString();
return;
}
QString data = reply->readAll().trimmed();
post_data = data;
if(get_task_list)
{
setTaskList();
}
}
void TaskListProcess::sendPost(QString file_name, QUrlQuery params)
{
post_data = "";
QUrl url(sci.getHost() + file_name);
url.setQuery(params);
data.clear();
data.append(params.toString().toUtf8());
request.setUrl(url);
nam->post(request, data);
}
From the Qt docs http://qt-project.org/doc/qt-5.1/qtnetwork/qnetworkaccessmanager.html
Note: After the request has finished, it is the responsibility of the
user to delete the QNetworkReply object at an appropriate time. Do not
directly delete it inside the slot connected to finished(). You can
use the deleteLater() function.
I would suggest calling reply->deleteLater() in your replyFinished() method.
You should call deleteLater() for an QNetworkReply object after use.
Note: After the request has finished, it is the responsibility of the user to delete the QNetworkReply object at an appropriate time. Do not directly delete it inside the slot connected to finished(). You can use the deleteLater() function.
More information here: http://harmattan-dev.nokia.com/docs/library/html/qt4/qnetworkaccessmanager.html
Thank you everyone for the hel.
It was very simply the "deleteLater()" on the reply in the
void replyFinished(QNetworkReply *reply)
{
}
and it should have looked like this
void replyFinished(QNetworkReply *reply)
{
// after all of your processing
reply->deleteLater();
}
it was such a small problem but one that drove me crazy for a long time so i hope this helps
Related
I have a function and connection between a slot and a signal.
Check the code below:
void NetworkAccessManager::sendPOST(QString url)
{
QNetworkCookieJar *cookieJar = new QNetworkCookieJar(manager_);
manager_->setCookieJar(cookieJar);
QNetworkRequest request(url);
QByteArray postData;
postData.append("j_username=admin&");
postData.append("j_password=admin");
manager_->post(request, postData);
connect(manager_, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinishedSlot(QNetworkReply *)));
}
void NetworkAccessManager::replyFinishedSlot(QNetworkReply *reply)
{
...
cookie = "...";
}
Code above owned by class NetworkAccessManager. Variable cookie is public and I need to change its value in replyFinishedSlot.
I try to use the function sendPOST() in constructer of another class and it works, but slot doing nothing and cookie varaible is empty. What do I do wrong?
Here's the code inside another class:
NetworkAccessManager *manager = new NetworkAccessManager();
manager->sendPOST("http://example.com");
qDebug() << "cookies: " << manager->cookies;
I guess that slot may not work because I never emit signal finished(), but I am not sure where should I emit this because my code shouldn't work with the user interface.
I have found a couple of problem solutions.
First one
I am saving the cookie in a file. Here's the code:
void NetworkAccessManager::replyFinishedSlot(QNetworkReply *reply)
{
...
QFile cookieFile("cookie.txt");
if (cookieFile.open(QIODevice::WriteOnly))
{
cookieFile.write(cookies.toUtf8());
cookieFile.close();
}
...
}
Here's the constructer of class where I use the function
NetworkAccessManager *manager = new NetworkAccessManager();
manager->sendPOST("http://example.com");
QString cookies = "";
QFile cookieFile("cookie.txt");
if (cookieFile.exists() && cookieFile.open(QIODevice::ReadOnly))
{
cookies = cookieFile.readAll();
cookieFile.close();
}
QMap<QString, QString> elements;
elements.insert("cookies", cookies);
Second one
I use a signal to send the info to QML (I transferred my NetworkAccessManager class to WebViewModel).
.h
class WebViewModel : public QObject
{
Q_OBJECT
public:
explicit WebViewModel(QObject *parent = nullptr);
~WebViewModel();
void sendPOST(QString url);
private slots:
void replyFinishedSlot(QNetworkReply *);
private:
QNetworkAccessManager *manager_;
signals:
void finished(QNetworkReply *);
void cookieReady(QString thisCookie);
};
.cpp
WebViewModel::WebViewModel(QObject* parent)
: QObject(parent)
{
manager_ = new QNetworkAccessManager(this);
sendPOST("http://example.com");
}
WebViewModel::~WebViewModel()
{
delete manager_;
}
void WebViewModel::sendPOST(QString url)
{
...
connect(manager_, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinishedSlot(QNetworkReply *)));
...
}
void WebViewModel::replyFinishedSlot(QNetworkReply *reply)
{
if(reply->error())
{
...
}
else
{
...
QString cookieString = cookie[0].name() + "=" + cookie[0].value() + "; domain=" + cookie[0].domain() + "; path=" + cookie[0].path();
emit cookieReady(cookieString);//the signal sends my variable to .qml
...
}
}
.qml
Connections {
target: WebViewModel
onCookieReady: {
testCookies = thisCookie
//thisCookie variable wasn't declared in the .qml file, thisCookie variable
//is accessible by signal void cookieReady(QString thisCookie) from .h C++
//and MUST have exactly the same name in .qml file as in .h file
}
}
I'm new to QT and I would like some help. If any of you could help me I would really appreciate it.
QUESTION:
I have an asynchronous class that makes an HTTP request and it's going to receive some data into a JSON format and from there I will extract the necessary information which should be passed to my custom widget. How can I do that? Because I don't know when the information will arrive.
WHAT I'VE DONE SO FAR:
My HTTP request and parsing JSON class:
WeatherAPI::WeatherAPI(QObject *parent) : QObject(parent) {
manager = new QNetworkAccessManager(this);
QObject::connect(manager, SIGNAL(finished(QNetworkReply * )), this, SLOT(readData(QNetworkReply * )));
}
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
QString strReply = (QString) reply->readAll();
QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
QJsonObject jsonObject = jsonResponse.object();
weatherObject.city = jsonObject["name"].toString();
weatherObject.temperature = QString::number(jsonObject["main"].toObject()["temp"].toDouble() - 273.15);
int ts = jsonObject["dt"].toInt();
weatherObject.time = QDateTime::fromSecsSinceEpoch(ts).toString("hh:mm");
auto weatherData = jsonObject["weather"].toArray()[0].toObject()["main"].toString();
if (weatherData == "Clouds") {
weatherObject.icon = "Sun.png";
}
} else {
qDebug() << "ERROR";
}
}
void WeatherAPI::requestDataForCity(const QString &city) {
QString link = linkTemplate.arg(city, key);
QUrl url(link);
manager->get(QNetworkRequest(url));
}
const WeatherObject &WeatherAPI::getWeatherObject() const {
return weatherObject;
}
Now here is my custom Widget:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
WeatherObject data = api->getWeatherObject();//HERE I'm getting the DATA
m_title = data.city;
m_time = data.time;
m_icon = data.icon;
m_temperature = data.temperature;
}
}
In that function from WeatherButton::initStyle I'm going to make an HTTP request and also I'm going to place the data into the necessary variable. Now my question is... How can I wait for that data to be received and just after that to place them into those variables?
So far the only solution I know so far is to use a QEventLoop, but at that moment I'm going to basically convert an async call into a sync one, which is not quite what I want. I want to be fully async.
WeatherObject data = api->getWeatherObject(); //HERE I'm getting the DATA
No, you do not get the data here. WeatherAPI::readData is where you get the data.
That is the point of the Signal - Slot mechanism. You do not wait for an event to happen, but react to it via callback, i.e. slot.
Having that in mind, you have to rethink and extend your code. Here is one way to do this:
In the WeatherAPI class define a dataReady(const WeatherObject &weatherObject) signal
Emit this signal in WeatherAPI::readData like this:
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
// the processing of the http response remains unchanged
// ...
emit dataReady(weatherObject);
} else {
qDebug() << "ERROR";
}
}
In the WeatherButton class define a onDataReady slot with the following implementation:
void WeatherButton::onDataReady(const WeatherObject &weatherObject) {
m_title = weatherObject.city;
m_time = weatherObject.time;
m_icon = weatherObject.icon;
m_temperature = weatherObject.temperature;
}
Connect the newly created signal and slot in WeatherButton::initStyle like this:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
connect(api, &WeatherAPI::dataReady, this, &WeatherButton::onDataReady);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
}
}
As a sidenote I should say, that initStyle is probably not the best place to instantiate the WeatherAPI. api seems to be an attribute of WeatherButton, hence it should be initialized in the constructor of the class.
I am going to use QNetworkAccessManager to make requests to HTTP server by my mobile app to the server. The question is, how do you link custom data to each request ? I tried to subclass QNetworkReply but I found out that I have to implement virtual methods close() and isSequential() but I don't know what those should return so I am afraid I am going to break network request functionality.
For example, when my app does the log in procedure, it has to store the email address of the account:
class MyApp : public QObject
{
Q_OBJECT
private:
QNetworkRequest request;
QNetworkReply *reply;
QNetworkAccessManager *manager;
...
}
void MyApp::do_log_in(QString email, QString password) {
QString s;
someobject.email=email; // <-- I have to store email address before sending request to server, but where do I store it?
s.append("http://myapp.com/do-auth.php?email=");
s.append(QUrl::toPercentEncoding(email));
s.append("&password=");
s.append(QUrl::toPercentEncoding(password));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(login_finished(QNetworkReply*)));
request.setUrl(QUrl(s));
manager->get(request);
}
void MyApp::login_finished(QNetworkReply *rep) {
DepservReply *reply;
QString email;
....
email= ...... // <-- I need to get the email address from QNetworkReply object somehow
///my code here handling server reply
....
}
So, how do I implement storage and retrieval of email in my case, what classes should I subclass and what methods should I re-implement ?
You can leverage the dynamic property system available in each QObject and hold the data in the reply:
// https://github.com/KubaO/stackoverflown/tree/master/questions/network-reply-tracking-40707025
#include <QtNetwork>
class MyCtl : public QObject
{
Q_OBJECT
QNetworkAccessManager manager{this};
// ...
void reply_finished(QNetworkReply *reply);
public:
MyCtl(QObject *parent = nullptr);
void do_log_in(const QString &email, const QString &password);
};
static const char kAuthGetSalt[] = "req_auth-get-salt";
static const char kDoAuth[] = "req_do-auth";
static const char kEmail[] = "req_email";
static const char kPassword[] = "req_password";
static const auto authGetSaltUrl = QStringLiteral("https://myapp.com/auth-get-salt.php?email=%1");
static const auto doAuthUrl = QStringLiteral("https://myapp.com/do-auth.php?email=%1&passwordHash=%2");
MyCtl::MyCtl(QObject *parent) : QObject{parent}
{
connect(&manager, &QNetworkAccessManager::finished, this, &MyCtl::reply_finished);
}
void MyCtl::do_log_in(const QString &email, const QString &password) {
auto url = authGetSaltUrl.arg(email);
auto reply = manager.get(QNetworkRequest{url});
reply->setProperty(kAuthGetSalt, true);
reply->setProperty(kEmail, email);
reply->setProperty(kPassword, password);
}
void MyCtl::reply_finished(QNetworkReply *reply) {
if (!reply->property(kAuthGetSalt).isNull()) {
reply->deleteLater(); // let's not leak the reply
if (reply->error() == QNetworkReply::NoError) {
auto salt = reply->readAll();
auto email = reply->property(kEmail).toString();
auto password = reply->property(kPassword).toString();
Q_ASSERT(!password.isEmpty() && !email.isEmpty());
QCryptographicHash hasher{QCryptographicHash::Sha1};
hasher.addData(salt); // the server must hash the same way
hasher.addData("----");
hasher.addData(password.toUtf8());
auto hash = hasher.result().toBase64(QByteArray::Base64UrlEncoding);
auto url = doAuthUrl.arg(email).arg(QString::fromLatin1(hash));
auto reply = manager.get(QNetworkRequest{url});
reply->setProperty(kDoAuth, true);
reply->setProperty(kEmail, email);
}
}
else if (!reply->property(kDoAuth).isNull()) {
if (reply->error() == QNetworkReply::NoError) {
auto email = reply->property(kEmail).toString();
// ...
}
}
}
Use a constant for a property name to avoid typos by letting the compiler check that you're using a valid identifier.
The example above rectifies the following critical safety issues in your code:
Sending security credentials over a clear connection: use https://, not http://.
Sending a password in cleartext: instead, send a salted hash of it. Your server should generate a random salt for each account when the accounts are created. Existing accounts can be left unsalted, but they should acquire a salt as soon as the user changes the password.
Also note that a QString to QUrl conversion will automatically percent-encode the string, so doing it explicitly is unnecessary.
In this case email is part of the request's URL so you could just extract it from there (the QNetworkReply has access to the QNetworkRequest it is handling, see QNetworkReply::request()).
You an also store more or less any kind of data as a dynamic property because QNetworkReply is a QObject derived class, see QObject::setProperty().
You can subclass QNAM to get more control of it.
network.h
class QNAMNetwork : public QNetworkAccessManager
{
Q_OBJECT
public:
explicit QNAMNetwork(QObject *parent = 0);
~QNAMNetwork();
inline void insertUserValue(const QString & key, const QString & value){this->m_user_values.insert(key,value);}
inline QString getUserValue(const QString & key){return this->m_user_values.value(key);}
signals:
void requestFinished(ExNetwork *, QNetworkReply *);
private slots:
void _sslErrors(QNetworkReply *, const QList<QSslError> &);
void _finished(QNetworkReply *);
private:
QMap<QString, QString> m_user_values;
};
network.cpp
QNAMNetwork::QNAMNetwork(QObject *parent):QNetworkAccessManager(parent)
{
connect(this, &QNAMNetwork::sslErrors, this, &QNAMNetwork::_sslErrors);
connect(this, &QNAMNetwork::finished, this, &QNAMNetwork::_finished);
}
QNAMNetwork::~QNAMNetwork()
{
//qDebug() << __FUNCTION__ << QString::number((long)this,16);
}
void QNAMNetwork::_sslErrors(QNetworkReply * reply, const QList<QSslError> & errors)
{
reply->ignoreSslErrors(errors);
}
void QNAMNetwork::_finished(QNetworkReply * reply)
{
emit requestFinished(this, reply);
}
usecase:
QNAMNetwork * network = new QNAMNetwork(this);
network->insertUserValue("email","yourmail#mail.com");
connect(network, SIGNAL(requestFinished(QNAMNetwork*,QNetworkReply*)), this, SLOT(requestFinished(QNAMNetwork*,QNetworkReply*)));
QNetworkRequest req(QUrl::fromUserInput(query)); //get url
network->get(req);
...
void YourClass::requestFinished(QNAMNetwork * net, QNetworkReply * rep)
{
QString email = net->getUserValue("email");
net->deleteLater();
rep->deleteLater();
}
I'm currently working on applciation that will add to table view new row, that was inserted into db's table. I started with basic class to handle notifies and setted up triggers:
CREATE OR REPLACE FUNCTION notify_tableIWantToObserve_update()
RETURNS trigger AS $$
DECLARE
BEGIN
PERFORM pg_notify(
CAST('tableIWantToObserve_update' AS text),
(NEW.tableIWantToObserve_id)::text);
return new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tRIGGER_notify_tableIWantToObserve_update
AFTER UPDATE
ON tableIWantToObserve
FOR EACH ROW
EXECUTE PROCEDURE notify_tableIWantToObserve_update();
So it will jsut send notfy with id of updated row in payload. That is what i want - becous reloading whole table just won't do the trick later.
I checked documentaton of QSqlDriver
http://doc.qt.io/qt-5/qsqldriver.html#notification-1
With it, I created my "handler":
// That's its constructor
MyDB = new QSqlDatabase(QSqlDatabase::addDatabase("QPSQL", "Main"));
//Removed my data from here (just fro sake of this post)
MyDB->setHostName("-");
MyDB->setPort(0);
MyDB->setDatabaseName("-");
MyDB->setUserName("-");
MyDB->setPassword("-");
MyDB->open();
if( MyDB->isOpen() )
{
qDebug()<<"Connected to DB!";
QObject::connect(
MyDB->driver(),
SIGNAL(notification(const QString&, QSqlDriver::NotificationSource, const QVariant)),
this,
SLOT(slot_DBNotification_Recieved_NotifiAndPayload((const QString&, const QVariant)));
);
}
else
qDebug()<<"NOT connected to DB!";
But it jsut wont work. Only with driver's signal useing single QString it will connect it - version i needed (with additional info) wont connect.
I updated my QT to 5.7, but still even in QTCreater, it just shows me that driver's signal is only with single string.
Is there any fix for that? I realy need to use that signal to retrieve that updated row id.
EDIT 1:
that slot of my handler:
void NotifiHandlerr::slot_DBNotification_Recieved_NotifiAndPayload(const QString& MSG, const QVariant &payload)
{
qDebug() << "I WAS NOTIFIED ABOUT : " + MSG+" WITH DATA : "+payload.toString();
}
EDIT 2:
I tried to add QSqlDriver::NotificationSource as argument in my slot, but i couldn't - it still repeated error in .h that NotificationSource wasn't declared.
EDIT 3:
I'm adding here most of the code (handler class)
// WHOLE .h
#include <QDebug>
#include <QObject>
#include <QString>
#include <QSqlDatabase>
#include <QSqlDriver>
#include <QVariant>
#include <QSqlDriverPlugin>
#include <qsqldriver.h>
class Handler : public QObject
{
Q_OBJECT
public slots:
void slot_DBNotification_Recieved_NotifiAndPayload
(const QString& name, QSqlDriver::NotificationSource source, const QVariant& payload);
public:
explicit Handler();
~Handler();
private:
QSqlDatabase MyDB;
};
//WHOLE .cpp
#include "Handler.h"
Handler::Handler()
{
MyDB = new QSqlDatabase(QSqlDatabase::addDatabase("QPSQL", "Main"));
MyDB->setHostName("-");
MyDB->setPort(0);
MyDB->setDatabaseName("-");
MyDB->setUserName("-");
MyDB->setPassword("-");
MyDB->open();
if( MyDB->isOpen() )
{
qDebug()<<"Connected to DB!";
MyDB->driver()->subscribeToNotification("tableIWantToObserve_update");
QObject::connect(
MyDB->driver(),
SIGNAL(notification(const QString&, QSqlDriver::NotificationSource, const QVariant)),
this,
SLOT(slot_DBNotification_Recieved_NotifiAndPayload((const QString&, const QVariant)));
);
}
else
qDebug()<<"NOT connected to DB!";
}
Handler::~Handler()
{
MyDB->driver()->unsubscribeFromNotification("tableIWantToObserve_update");
MyDB->cloe();
}
void NotificationMaster::slot_DBNotification_Recieved_NotifiAndPayload
(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
{
qDebug() << "I WAS NOTIFIED ABOUT : " + name+" WITH DATA : "+payload.toString();
}
And just to eliminate this idea - I added
QT += sql
in my .pro file
Your slot has a wrong signature, Here is how you should define it.
In your header file:
//in order to be able to use the enum QSqlDriver::NotificationSource
#include <QSqlDriver>
...
...
class Handler : public QObject{
Q_OBJECT
public:
explicit Handler(QObject *parent = 0);
~Handler();
...
...
...
public slots:
void SqlNotification(const QString& name, QSqlDriver::NotificationSource source,
const QVariant& payload);
...
...
};
and in the constructor, when you are connecting the slot, you should subscribe to the notification first:
QSqlDatabase::database().driver()->subscribeToNotification("notification_name");
connect(QSqlDatabase::database().driver(),
SIGNAL(notification(QString,QSqlDriver::NotificationSource,QVariant)), this,
SLOT(SqlNotification(QString,QSqlDriver::NotificationSource,QVariant)));
You may need to unsubscribe in the destructor(since you don't want to receive the notification any more):
QSqlDatabase::database().driver()->unsubscribeFromNotification("notification_name");
and your slot implementation:
void Handler::SqlNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload){
switch(source){
case QSqlDriver::UnknownSource:
qDebug() << "unkown source, name: " << name << "payload:" << payload.toString();
break;
case QSqlDriver::SelfSource:
qDebug() << "self source, name: " << name << "payload:" << payload.toString();
break;
case QSqlDriver::OtherSource:
qDebug() << "other source, name: " << name << "payload:" << payload.toString();
break;
}
}
Code posted by me and in anserw were more-or-less correct, but what need to be done is to recreate whoel project in newer version of qt creator so it will resolve issiues with missing functions and namespaces itself. Just create new project and paste all files there.
I am trying to save cookies that are produced by my app to disk location such as C:\Users\Username\AppData\Local\MyCompany\MyApp. I have implemented a webview and have pretty much finished coding my simple browser the final thing to do is save cookies.
I am can qDebug() the cookies I get from the webapp and they show the cookies are formed correctly but I am a)unsure where to go from there and b) not 100% sure on how to make a subclass of the cookiejar class?
Below I create my cookiejar object in my MainWindow constructor
view = new QWebView(this);
jar = new QNetworkCookieJar;
view->page()->networkAccessManager()->setCookieJar(jar);
And in my replyfinished slot I can see the cookie contained in the reply and I attempt to save it but nothing happens and I receive no run time errors. There isn't a great deal of stuff out there on this and have seen a few posts where the instruction was to make a subclass QNetworkCookieJar but have not made a subclass in Qt/C++ before.
Is there a simple way to store cookies, I am not looking for anything fancy. The cookies just make sure some check boxes are ticked on the login page.
// SLOT that accepts the read data from the webpage
void MainWindow::slotReplyFinished(QNetworkReply *reply){
if(reply->isFinished()){
QVariant variantCookies = reply->header(QNetworkRequest::SetCookieHeader);
QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(variantCookies);
qDebug() << "Cookies reply: " << cookies;
QNetworkCookie cookie; //Create a cookie
jar = new QNetworkCookieJar;
//view->page()->networkAccessManager()->setCookieJar(jar);
jar->setCookiesFromUrl(cookies, reply->request().url());
//qDebug() << "Saved cookies: " << jar->getAllCookies();
}
qDebug() << "Network reply: " << reply->errorString() << reply->error() << reply->request().url();
}
You will need to subclass QNetworkCookieJar and in that class you should implement your own persistent storage.
class MyNetworkCookieJar : public QNetworkCookieJar {
public:
bool saveCookiesToDisk() {
// .. my implementation
return true; // if i did
}
bool loadCookiesFromDisk() {
// .. load from disk
return false; // if unable to.
}
}
The sample application from Qt project implements a persistent cookie store, it could be a good starting point for you: http://qt.gitorious.org/qt/qt/trees/4.8/demos/browser
look at cookiejar.h and cookiejar.cpp
Base of qt example, http://qt.gitorious.org/qt/qt/trees/4.8/demos/browser, i wrote this class that save and use one cookie for me. Perhaps it helps you too. Note that it just save one cookie and not list of cookies.
#include "cookiejar.h"
CookieJar::CookieJar(QObject *parent)
: QNetworkCookieJar(parent)
, m_loaded(false)
{
}
void CookieJar::load()
{
if (m_loaded)
return;
QSettings settings;
settings.beginGroup(QLatin1String("cookies"));
QList<QNetworkCookie> savedCookies = QNetworkCookie::parseCookies(settings.value("cookies").toByteArray());
for (int j = 0; j < savedCookies.count(); j++)
insertCookie(savedCookies.at(j));
m_loaded = true;
emit cookiesChanged();
}
void CookieJar::save()
{
if (!m_loaded)
return;
QList<QNetworkCookie> cookies = allCookies();
QSettings settings;
settings.beginGroup(QLatin1String("cookies"));
settings.setValue("cookies", cookies[0].toRawForm());
}
QList<QNetworkCookie> CookieJar::cookiesForUrl(const QUrl &url) const
{
// This function is called by the default QNetworkAccessManager::createRequest(),
// which adds the cookies returned by this function to the request being sent.
CookieJar *that = const_cast<CookieJar*>(this);
if (!m_loaded)
that->load();
return QNetworkCookieJar::cookiesForUrl(url);
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
if (!m_loaded)
load();
QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
save(); //Save cookie permanently in setting file.
emit cookiesChanged();
return true;
}
#Musa's answer is good but it only saves one cookie. I recommend using the Qt folk's implementation from the old qmlviewer located here: http://code.qt.io/cgit/qt/qt.git/tree/tools/qml/qmlruntime.cpp?h=4.7#n438
Here's the code:
class PersistentCookieJar : public QNetworkCookieJar {
public:
PersistentCookieJar(QObject *parent) : QNetworkCookieJar(parent) { load(); }
~PersistentCookieJar() { save(); }
virtual QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const
{
QMutexLocker lock(&mutex);
return QNetworkCookieJar::cookiesForUrl(url);
}
virtual bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
QMutexLocker lock(&mutex);
return QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
}
private:
void save()
{
QMutexLocker lock(&mutex);
QList<QNetworkCookie> list = allCookies();
QByteArray data;
foreach (QNetworkCookie cookie, list) {
if (!cookie.isSessionCookie()) {
data.append(cookie.toRawForm());
data.append("\n");
}
}
QSettings settings;
settings.setValue("Cookies",data);
}
void load()
{
QMutexLocker lock(&mutex);
QSettings settings;
QByteArray data = settings.value("Cookies").toByteArray();
setAllCookies(QNetworkCookie::parseCookies(data));
}
mutable QMutex mutex;
};