Using QHttpMultiPart with QHttpPart Binary and Form Data - qt

I wrote a method to send a image and some form data to a Django Rest Framework Server. The following method works fine, but I would like to know if there is a way to send the form fields together in one QHttpPart, instead of creating one for each field?
Is there a better way to do this? Maybe with less code?
void MyNetwork::sendBinaryFile() {
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyfinished(QNetworkReply*)));
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart aluno, palavra_chave, latitude, latitude_ref, longitude, longitude_ref;
aluno.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"aluno\""));
aluno.setBody("some useful string");
palavra_chave.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"palavra_chave\""));
palavra_chave.setBody("some other useful string");
latitude.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"latitude\""));
latitude.setBody("0");
longitude.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"longitude\""));
longitude.setBody("0");
latitude_ref.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"latitude_ref\""));
latitude_ref.setBody("N");
longitude_ref.setHeader(QNetworkRequest::ContentDispositionHeader,QVariant("form-data; name=\"longitude_ref\""));
longitude_ref.setBody("E");
multiPart->append(aluno);
multiPart->append(palavra_chave);
multiPart->append(latitude);
multiPart->append(latitude_ref);
multiPart->append(longitude);
multiPart->append(longitude_ref);
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; filename=\"image.png\"; name=\"file\""));
QFile *file = new QFile("/path/image.png");
file->open(QIODevice::ReadOnly);
imagePart.setBodyDevice(file);
file->setParent(multiPart);
multiPart->append(imagePart);
QString sendPath = "http://localhost/api/images/";
QUrl url(sendPath);
QNetworkRequest request(url);
QString concatenated = "username:password";
QByteArray data = "Basic ";
data.append(concatenated.toLocal8Bit().toBase64());
request.setRawHeader("Authorization", data);
manager->post(request,multiPart);
}
void MyNetwork::replyfinished(QNetworkReply* reply)
{
qDebug() << "C++ - replyUpdateJsonFinished";
qDebug() << QString(reply->readAll());
qDebug() << QString(reply->errorString());
}

Because of the nature of multipart/form-data, each field that is sent must be sent in a different part. This is because each part in the request represents a different field, and is identified by the field name and contains the body that should be sent (in your case, mostly text).
You may have better luck creating a helper method for the text field that takes the field name and the content of it. That method would then be able to create a new part with the correct header (containing the field name) and the body (containing the data to send), without requiring you to repeat the same lines.

Related

How do I download a file from google drive using v3 rest api?

My Objective: To download a sqlite file from google drive using google drive v3 rest api
I am working on project that involves downloading a sqlite file which is stored in google drive. So far I have been in successfull in authorizing and getting the access token. I also had luck getting all the file names (sqlite files) stored in the google drive with their respective file id. Now I want to download the file using the file id. I followed the documentation, and send the request with file id. But all i am getting is
{
"kind": "drive#file",
"id": "1AmaXWbtmkvihy1g9yoYSSCssgL4fVh6t",
"name": "_db.sqlite",
"mimeType": "application/octet-stream"
}
This is my code:
bool File_Control::Download_File(const QString &FileName)
{
// Get FileID
QString FileID = m_Map.value(FileName, QString());
if(FileID.isEmpty()){
emit setMessage("Fatal Error: FileID is missing for FileName");
return false;
}
qDebug()<<"File ID "<<FileID;
// Now Prepare the request
QUrl url(tr("https://www.googleapis.com/drive/v3/files/%1").arg(FileID));
QUrlQuery query;
query.addQueryItem("alt","media");
url.setQuery(query.query());
QNetworkRequest request(url);
QString headerData = "Bearer " + m_Settings->get_Google_Drive_Settings
(Google_Drive::AccessTokenEnum);
request.setRawHeader("Authorization", headerData.toLocal8Bit());
QNetworkReply *reply = m_Account->get_Request(request);
// Now wait for the response
QTime t1 = QTime::currentTime().addMSecs(TIMEOUT);
while((t1>QTime::currentTime()) && (!reply->isFinished())){
QCoreApplication::processEvents();
}
if(reply->isFinished())
{
if(reply->error() != QNetworkReply::NoError){
emit setMessage("Error: "+reply->errorString());
delete reply;
return false;
}
else{
QByteArray array = reply->readAll();
qDebug()<<array<<reply->error();
delete reply;
return true;
}
}
else{
emit setMessage("Error: Timeout");
delete reply;
return false;
}
}
Am I missing something?
Edit 1:
m_Map -> QMap that stores file names (of files from drive) as key and file id as value.
m_Settings -> A helper object that helps in getting access_token which is stored in Windows Registry
Google_Drive -> A helper enum type.
m_Account -> Object that helps in authorising google account. This object contains my QNetworkManager so i made a function called get_Request to get my "get request"
Initially I din't set the alt to media, so that explains the reponse I have got in the first place. Then I have set the value as shown in the code.
The issue was, that I was trying to see the contents of QByteArray in the debugging console. Since the array contains binary data, Qt did not show any characters and I thought that Google has sent an empty packet.
To make the matter worse, I have read this guide and tried to reproduce it in my browser. So I have got the error code 400.
Eventually, I have thought about checking the size of the array and to my surprise it was the same as the size of the original file on the drive. Then I have saved the contents of the array to a local file and checked the SQLite file and everything was OK.

Qt, QNetworkAccess Manager download large files fail frequently

I am using the following code to download some video files through LAN.
QString url = "http://192.168.1.100/disk/IPCAMERA/" + downloadlist[downloadID];
QNetworkRequest newRequest(url);
QString concatenated = "admin:admin";
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
newRequest.setRawHeader("Authorization", headerData.toLocal8Bit());
newRequest.setUrl(url);
reply = networkManager->get(newRequest);
connect(reply, SIGNAL(finished()), this, SLOT(refStateChanged()), Qt::UniqueConnection);
connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(replyDownloadProgress(qint64, qint64)));
However, the downloading frequently fails, when the reply finished signal is triggered, it does not actually finished downloading the file but only part of it. What could I do to deal with this?
It seems you should get the file size first
QNetworkReply* reply = manager->head(newRequest);
every reply is part of the Bytearray,you must add this connect:
connect(reply, SIGNAL(readyRead()),this, SLOT(SlotReadyRead()));
void ClassName::SlotReadyRead()
{
if (_file) //you must define QFile _file........
_file->write(_reply->readAll());
}
then, you will downloading the complete file.
Good luck!

Retrieve all the file paths from a QDropEvent

I implemented drag and drop in my software. Basically I've got a QTableView which contains file paths. The user must be able to just drag and drop files in the QTableView in order to add them.
I've already done the big of the job but I'm stuck in iterating all the paths contained in the QDropEvent object. I have to implement the dropEvent method.
void Generous::dropEvent(QDropEvent *dropEvent) {
QStringList filePathList;
// Way to iterate dropEvent and append each file path to filePathList.
addFilesToListView(filePathList);
}
How can I do that?
I would guess that a drag and drop using filepaths use the MIME type text/uri-list.
If this is true, you should probably be able to retrieve the data like this:
if (dropEvent->mimeData()->hasUrls())
{
foreach (QUrl url, dropEvent->mimeData()->urls())
{
filePathList << url.toLocalFile();
}
}
Anyway, since I am not sure, the best would be first to check what kind of info is stored inside the drop event, and see where and how you can extract the filepaths:
QStringList availableMimeTypes = dropEvent->mimeData()->formats();
qDebug() << "available MIME types:" << dropEvent->mimeData()->formats() << "\n";
foreach(QString mimeType, availableMimeTypes)
{
qDebug() << "data for MIME type" << mimeType << " :";
qDebug() << dropEvent->mimeData()->data(mimeType) << "\n";
}
You can also create a global list that will keep appending the filePaths on each dropevent. So at the end you will be having a complete list of paths.

Qt5 posting data to server using QUrl / QNetworkRequest

I have a piece of code that worked in 4.8 but now I need to port it to Qt5 (beta2)
This is what should happen:
I want to post some data to a webserver the url should look like this "http://server/actions.php"
Then my fields (a "Action" -string and a "data" string(json)) should be sent to the server using post. Not encoded in the url
QUrl params;
// The data to post
QVariantMap map;
map["Title"]="The title";
map["ProjectId"]="0";
map["Parent"]="0";
map["Location"]="North pole";
map["Creator"]="You";
map["Group"]="a group";
QByteArray data = Json::serialize(map); //the map is converted to json im a QByteArray
params.addEncodedQueryItem("Data",data);
params.addQueryItem("Action", "Update");
QNetworkRequest Request(QUrl("http://server.com/actions.php"));
Request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
NetManager->post(Request,params.encodedQuery());
Now, I might not be doing this right in the first place,(It worked in 4.8) but the real problem is that addEncodedQueryItem() and addQueryItem() are now gone since Qt5 and I don't know what I should replace them with.
I have read the new docs and see the new QUrlQuery but I could not figure out on my own how to use this in my case.
I faced similar problem and used code similiar to the following in Qt5
QUrl url;
QByteArray postData;
url.setUrl("http://myurl....");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
Qstring postKey = 'city';
QString postValue = 'Brisbane';
postData.append(postKey).append("=").append(postValue).append("&");
networkManager.post(request,postData);
Hope it might be useful to rewrite your code to send http post values using Qt5
Qt5 no longer has the QUrl::encodedQuery() method. Not sure, but from the documentation it might work using QUrl::query() method instead.
Hope it helps.
QUrlQuery() helps you encode POST data.
Example in PyQt 5.4:
params = QtCore.QUrlQuery()
params.addQueryItem("username", "Вагиф Plaît")
reply = QtNetwork.QNetworkAccessManager().post(request, params.toString(QtCore.QUrl().FullyEncoded))

Basic authentication with Qt (QNetworkAccessManager)

I was trying to perform basic authentication for Twitter from my Qt app. I use QNetworkAccessManager. But I couldn't find any help on this.
But I found a program called qsoapmanager which passes credentials in base64 through the header. Maybe I can do this with QNAM by setting header in QNetowrkRequest. But I failed to find a way.
In qsoapman source, header is set like this:
QHttpRequestHeader header;
header.setValue( "Authorization", QString( "Basic " ).append( auth.data() ) );
Can I do just that with QNAM/QNReq or is there a better way?
But if you want to do it by just setting the header value, here's how you can do that:
// HTTP Basic authentication header value: base64(username:password)
QString concatenated = username + ":" + password;
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
request.setRawHeader("Authorization", headerData.toLocal8Bit());
The recommended way is to connect to the authenticationRequired signal and set the credentials from there.
Just using qNetworkAccessManager normally but add
setRawHeader("Authorization", headerData.toLocal8Bit());
to your request.
Example:
//authentication
QString concatenated = "admin:admin"; //username:password
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
QNetworkRequest request=QNetworkRequest(QUrl("http://192.168.1.10/getinfo"));
request.setRawHeader("Authorization", headerData.toLocal8Bit());
networkAccessManager->get(request);
`

Resources