I have been trying to serialize my tree structure and restore it back with not much of success.
Mimedata function is able to store the tree structure in the correct hierarchy.
How can i save the mime data to a file and load it back to QDataStream ?
QMimeData *TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeItem *> nodes;
foreach(const QModelIndex &index, indexes) {
TreeItem *node = getItem(index);
if (!nodes.contains(node))
nodes << node;
}
stream << QCoreApplication::applicationPid();
stream << nodes.count();
foreach(TreeItem *node, nodes) {
buildTree(node, stream);
}
mimeData->setData(s_treeNodeMimeType, data);
return mimeData;
}
You must implement the stream operators between QDataStream and QMimeData saving the formats and data associated with each format and the number of formats.
#include <QtWidgets>
QDataStream &operator<<(QDataStream &stream, const QMimeData &data)
{
const QStringList formats = data.formats();
stream << static_cast<qint32>(formats.size());
for(const QString& format : formats)
stream << format << data.data(format);
return stream;
}
QDataStream &operator>>(QDataStream &stream, QMimeData &data)
{
data.clear();
qint32 size;
QString format;
QByteArray formatData;
stream >> size;
while(size-->0){
stream >> format >> formatData;
data.setData(format,formatData);
}
return stream;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
const QString text = "Text";
const QString html = R"(<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>
)";
const QList<QUrl> urls = {
{"https://stackoverflow.com/questions/58073079/how-to-save-mimedata-to-a-file"},
{"https://doc.qt.io/qt-5/"}
};
const QString mimeType = "custom/custom-app";
const QByteArray data("stackoverflow");
const QString path = "datastream.dat";
{
QMimeData mimedata;
mimedata.setText(text);
mimedata.setHtml(html);
mimedata.setUrls(urls);
mimedata.setData(mimeType, data);
QFile file(path);
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << mimedata;
}
{
QMimeData mimedata;
QFile file(path);
file.open(QIODevice::ReadOnly);
QDataStream out(&file);
out >> mimedata;
Q_ASSERT(mimedata.text() == text);
Q_ASSERT(mimedata.html() == html);
Q_ASSERT(mimedata.urls() == urls);
Q_ASSERT(mimedata.data(mimeType) == data);
}
return 0;
}
Related
The following code use writeCharacters with small buffer size to writing the content of big file, but it seems it only works when writing the content to a xml file, but writeCharacters() will crash when writting the content of a big file to QBuffer, any solution? Thanks.
env: qt opensource 4.8.7; Visual Studio 2010; Windows 10;
big file: https://1drv.ms/u/s!Ap_EAuwC9QkXijbujBQcQk4Hat_O?e=KemgUY
#include <QtCore/QCoreApplication>
#include <QFile>
#include <QXmlStreamWriter>
#include <QBuffer>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QByteArray mContentBuffer;
QByteArray arr;
int pos, filesize;
QFile file("C:\\Work\\bigfile.xar"); //Szie: 300Mb
if(!file.open(QFile::ReadOnly))
{
return -1;
}
mContentBuffer = file.readAll();
file.close();
//QFile profile("C:\\Work\\profile.xml");
//if(!profile.open(QFile::WriteOnly|QFile::Truncate))
//{
// return -1;
//}
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QXmlStreamWriter stream(&buffer);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement("Profile");
//stream.writeTextElement("Content", mContentBuffer.toBase64());
stream.writeStartElement("Content");
pos = 0;
filesize = mContentBuffer.size();
while(pos<filesize){
arr = mContentBuffer.mid(pos, 2000000);
stream.writeCharacters(arr.toBase64());
pos+=arr.size();
}
stream.writeEndElement();
stream.writeEndElement(); // Profile
stream.writeEndDocument();
return 0;
}
Here is the goal for writing a big file into a QBuffer.
bool profileModified()
{
bool result = true;
QFile profile("C:\\Work\\profile.xml");
if(profile.exists())
{
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QXmlStreamWriter stream(&buffer);
exportProfile(stream);
profile.open(QFile::ReadOnly);
QByteArray profileArr = profile.readAll();
buffer.seek(0);
QByteArray bufferArr = buffer.buffer();
result = (array_compare(profileArr, bufferArr) != 0);
profile.close();
}
return result;
}
I'm working with QtWebKit where I'm displaying dynamically created HTML content, and I need to display the images that I retrieve from the database. For example, if I need to display an image from the resource, I add this line to the content I use in QWebView::setHtml:
<img src="qrc:/images/image.jpg"/>
That works great, WebView automatically finds the resource and displays it. Now I need to substitute this image with the content that I retrieve from the database, and that doesn't have a file in the filesystem. How to do that?
Can I manage the qrc namespace adding the content dynamically? Is that possible to add my own QRC handler that would receive and serve the requests from WebView? If not QRC, is there any other protocol that I can use to provide the content to the images in WebView?
I have full control over what I'm adding to the WebView using the setHtml method.
Update: I would like to solve the same problem for QWebEngineView as well.
QtWebkit
In the case of QtWebkit you must intercept the request using a QNetworkAccessManager and resend a custom QNetworkReply.
#include <QtWebKitWidgets>
class CustomReply : public QNetworkReply{
public:
explicit CustomReply(const QByteArray & content, const QByteArray & contentType, const QUrl & url):
QNetworkReply(), m_content(content){
offset = 0;
setUrl(url);
open(ReadOnly | Unbuffered);
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
setHeader(QNetworkRequest::ContentLengthHeader, QVariant(m_content.size()));
QTimer::singleShot(0, this, &CustomReply::dispatch);
}
bool isSequential() const{
return true;
}
qint64 bytesAvailable() const{
return m_content.size() - offset + QIODevice::bytesAvailable();
}
public slots:
void abort(){
}
protected:
qint64 readData(char *data, qint64 maxSize){
if (offset < m_content.size()) {
qint64 number = qMin(maxSize, m_content.size() - offset);
::memcpy(data, m_content.constData() + offset, number);
offset += number;
return number;
} else
return -1;
}
private:
void dispatch(){
emit readyRead();
emit finished();
}
QByteArray m_content;
qint64 offset;
};
class NetworkAccessManager: public QNetworkAccessManager{
public:
using QNetworkAccessManager::QNetworkAccessManager;
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData){
qDebug() << request.url();
if (request.url() == QUrl("qrc:/images/image.jpg")){
QImage image(150, 150, QImage::Format_RGB32);
image.fill(QColor("salmon"));
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPEG");
return new CustomReply(ba, "image/jpeg", request.url());
}
return QNetworkAccessManager::createRequest(op, request, outgoingData);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWebView view;
view.resize(640, 480);
view.show();
view.page()->setNetworkAccessManager(new NetworkAccessManager);
QString html = R"(<img src="qrc:/images/image.jpg">)";
view.setHtml(html);
return a.exec();
}
QtWebEngine
In QtWebEngine you must implement a QWebEngineUrlSchemeHandler but you cannot use the qrc, http or https schemas:
#include <QtWebEngineWidgets>
#define SCHEMENAME "so"
class Handler : public QWebEngineUrlSchemeHandler{
public:
void requestStarted(QWebEngineUrlRequestJob *job){
if(job->requestUrl() == QUrl("so:/images/image.jpg")){
QImage image(150, 150, QImage::Format_RGB32);
image.fill(QColor("salmon"));
QBuffer *buffer = new QBuffer;
buffer->open(QIODevice::WriteOnly);
image.save(buffer, "JPEG");
buffer->seek(0);
buffer->close();
job->reply("image/jpeg", buffer);
}
}
static void registerUrlScheme(){
QWebEngineUrlScheme webUiScheme(SCHEMENAME);
webUiScheme.setFlags(QWebEngineUrlScheme::SecureScheme |
QWebEngineUrlScheme::LocalScheme |
QWebEngineUrlScheme::LocalAccessAllowed);
QWebEngineUrlScheme::registerScheme(webUiScheme);
}
};
int main(int argc, char *argv[])
{
Handler::registerUrlScheme();
QApplication a(argc, argv);
QWebEngineView view;
Handler handler;
view.page()->profile()->installUrlSchemeHandler(SCHEMENAME, &handler);
view.resize(640, 480);
view.show();
QString html = R"(<img src="so:/images/image.jpg">)";
view.setHtml(html);
return a.exec();
}
I am saving the QAbstract tree model to a dat file
void saveTreeStructureToFile(const QModelIndexList &indexes , const std::string stdstrFilePath)
{
QMimeData *mimeData = new QMimeData;
QByteArray data; //a kind of RAW format for datas
QDataStream stream(&data, QIODevice::WriteOnly);
QList<TreeItem *> nodes;
foreach(const QModelIndex &index, indexes) {
TreeItem *node = getItem(index);
if (!nodes.contains(node))
nodes << node;
}
stream << nodes.count();
foreach(TreeItem *node, nodes) {
buildTree(node, stream);
}
mimeData->setData(s_treeNodeMimeType, data);
std::string st = stdstrFilePath.substr(0, stdstrFilePath.size() - 3);
st.append("dat");
const QString path = st.c_str();
QFile file(path);
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << *mimeData;
}
How can i prepend the size of total bytes to be written to the start of dat file.
You can retrieve the size of the data and append it to your data stream at the beginning, like:
out << data.size() << *mimeData;
I'm trying to upload some files to a server by using QHttpMultiPart. The following version file is just an example. For some reason, Qt will automatically add boundary into the files. However, what I truly uploaded are .tar.gz files and they will be considered damaged if such boundary were added.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QNetworkAccessManager *netManager = new QNetworkAccessManager();
HTTPFirmwareUpgrader upgrader(netManager,"http://mytest.com");
upgrader.upgradeCIU();
return app.exec();
}
void HTTPFirmwareUpgrader::upgradeCIU()
{
QString filename = "version";
QString filePath = QString("C:/Users/User/Desktop/HTTP/%1").arg(filename);
qDebug() << uploadFirmware(filename, filePath);
}
bool HTTPFirmwareUpgrader::uploadFirmware(const QString &filename, const QString &filePath)
{
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart myPart;
QFile *file = new QFile(filePath);
if(!file->exists())
qWarning() << "File DOES NOT exists";
file->open(QIODevice::ReadOnly);
myPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("form-data"));
myPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"version\"; filename=\"version\""));
myPart.setBodyDevice(file);
file->setParent(multiPart);
multiPart->append(ciu);
QByteArray resp = this->post(createUploadRequest(QString("filename=%1").arg(filename)),
multiPart, file);
qDebug() << "Upload Filrmware " << resp;
return resp != "";
}
QByteArray HTTPFirmwareUpgrader::post(QUrl url, QHttpMultiPart *multiPart, QFile *file)
{
QNetworkRequest request;
QEventLoop loop;
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("multipart/form-data"));
QNetworkReply *reply = m_manager->post(request, multiPart);
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
multiPart->setParent(reply);
reply->deleteLater();
return reply->readAll();
}
This is the original "version" file:
enter image description here
And this is the "version" file that transferred:
enter image description here
And if I do not set header for the request, the size of the transferred file will be 0.
Does anyone have any idea what should I do?
Problem solved!!!
Turns out I use the wrong way to upload the file. What I need to use is QIODevice instead of QMultipart.
I am trying to implement a persistence in a Qtwebkit based browser for section keeping. I have extended QNetworkCookieJar. I didn't override any file but only added a save and load function. The functions is called explisitly.
I am getting the following error. The code is taken from brower code in Qt5.1 example.
QVariant::save: unable to save type 'QList<QNetworkCookie>' (type id: 1038).
Am I doing any thing wrong.. Please Help...Attaching my implementation of QNetworkCookie is bellow
static const unsigned int JAR_VERSION = 23;
#if 1
QT_BEGIN_NAMESPACE
QDataStream &operator<<(QDataStream &stream, const QList<QNetworkCookie> &list)
{
stream << JAR_VERSION;
stream << quint32(list.size());
for (int i = 0; i < list.size(); ++i)
stream << list.at(i).toRawForm();
return stream;
}
QDataStream &operator>>(QDataStream &stream, QList<QNetworkCookie> &list)
{
list.clear();
quint32 version;
stream >> version;
if (version != JAR_VERSION)
return stream;
quint32 count;
stream >> count;
for(quint32 i = 0; i < count; ++i)
{
QByteArray value;
stream >> value;
QList<QNetworkCookie> newCookies = QNetworkCookie::parseCookies(value);
if (newCookies.count() == 0 && value.length() != 0) {
qWarning() << "CookieJar: Unable to parse saved cookie:" << value;
}
for (int j = 0; j < newCookies.count(); ++j)
list.append(newCookies.at(j));
if (stream.atEnd())
break;
}
return stream;
}
QT_END_NAMESPACE
#endif
CookieJar::CookieJar(QObject *parent)
: QNetworkCookieJar(parent)
{
load();
}
CookieJar::~CookieJar()
{
save();
}
void CookieJar::load()
{
QSettings cookieSettings("./cookies_dir/Browser/cookies.ini", QSettings::IniFormat);
setAllCookies(qvariant_cast<QList<QNetworkCookie> >(cookieSettings.value(QLatin1String("cookies"))));
}
void CookieJar::save()
{
// purgeOldCookies();
QString directory = "./cookies_dir/Browser";
if (!QFile::exists(directory)) {
QDir dir;
dir.mkpath(directory);
}
QSettings cookieSettings(directory + QLatin1String("/cookies.ini"), QSettings::IniFormat);
QList<QNetworkCookie> cookies = allCookies();
qWarning("\n\n+=======================================================+\n\n");
for (int i = cookies.count() - 1; i >= 0; --i) {
qWarning()<< cookies.at(i).domain();
qWarning()<< cookies.at(i).name();
if (cookies.at(i).isSessionCookie())
cookies.removeAt(i);
}
qWarning("\n\n+=======================================================+\n\n");
cookieSettings.setValue(QLatin1String("cookies"), QVariant::fromValue<QList<QNetworkCookie> >(cookies));
}
void CookieJar::purgeOldCookies()
{
QList<QNetworkCookie> cookies = allCookies();
if (cookies.isEmpty())
return;
int oldCount = cookies.count();
QDateTime now = QDateTime::currentDateTime();
for (int i = cookies.count() - 1; i >= 0; --i) {
if (!cookies.at(i).isSessionCookie() && cookies.at(i).expirationDate() < now)
cookies.removeAt(i);
}
if (oldCount == cookies.count())
return;
setAllCookies(cookies);
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
qWarning()<< url;
return QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
}
I have registered the CookieJar using bellow code.
jar = new CookieJar(this);
webView->page()->networkAccessManager()->setCookieJar(jar);
You must call qRegisterMetaTypeStreamOperators<QList<QNetworkCookie>>("QList<QNetworkCookie>") before saving or loading to QSettings.
You need to use qRegisterMetaTypeStreamOperators and defines stream operators.
Here is an example save QList< QNetworkCookie> type cookies. In the header declares stream operators :
class cookieJar: public QNetworkCookieJar
{
bool save(const QUrl& url);
bool load(const QUrl& url);
}
QDataStream &operator<<(QDataStream &out, const QNetworkCookie &cookie);
QDataStream &operator>>(QDataStream &in, QNetworkCookie &cookie);
In cpp file you do something like:
CookieJar::CookieJar(QObject* parent) :
QNetworkCookieJar(parent)
{
qRegisterMetaTypeStreamOperators<QNetworkCookie>("QNetworkCookie");
}
bool CookieJar::save(const QUrl &url)
{
QList<QNetworkCookie> cookies = cookiesForUrl(url);
QSettings cookieSettings;
for(auto cookie : cookies)
{
cookieSettings.setValue(cookie.name(), QVariant::fromValue(cookie));
}
}
bool CookieJar::load(const QUrl &url)
{
QSettings cookieSettings;
// empty list created here
QList<QNetworkCookie> cookies;
// retrieve all keys from cookies.ini
QStringList keys = cookieSettings.allKeys();
for(auto key: keys)
{
QNetworkCookie cookie = qvariant_cast<QNetworkCookie>(cookieSettings.value(key, QVariant()));
cookies.append(cookie);
}
setCookiesFromUrl(cookies, url);
return true;
}
QDataStream &operator<<(QDataStream &out, const QNetworkCookie &cookie)
{
out << cookie.toRawForm();
}
QDataStream &operator>>(QDataStream &in, QNetworkCookie &cookie)
{
QByteArray raw_data;
in >> raw_data;
QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(raw_data);
cookie = cookies.at(0);
}