Kgapi

fileabstractuploadjob.cpp
1/*
2 * This file is part of LibKGAPI library
3 *
4 * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9#include "fileabstractuploadjob.h"
10#include "debug.h"
11#include "utils.h"
12
13#include <QNetworkReply>
14#include <QNetworkRequest>
15
16#include <QCryptographicHash>
17#include <QFile>
18#include <QFileInfo>
19#include <QMimeDatabase>
20#include <QMimeType>
21#include <QUrlQuery>
22
23using namespace KGAPI2;
24using namespace KGAPI2::Drive;
25
26class Q_DECL_HIDDEN FileAbstractUploadJob::Private
27{
28public:
29 Private(FileAbstractUploadJob *parent);
30 void processNext();
31 QByteArray buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary);
32 QByteArray readFile(const QString &filePath, QString &contentType);
33
34 void _k_uploadProgress(qint64 bytesSent, qint64 totalBytes);
35
36 int originalFilesCount = 0;
38
39 QMap<QString, FilePtr> uploadedFiles;
40
41 File::SerializationOptions serializationOptions = File::NoOptions;
42
43private:
44 FileAbstractUploadJob *const q;
45};
46
47FileAbstractUploadJob::Private::Private(FileAbstractUploadJob *parent)
48 : q(parent)
49{
50}
51
52QByteArray FileAbstractUploadJob::Private::readFile(const QString &filePath, QString &contentType)
53{
54 QFile file(filePath);
55 if (!file.open(QIODevice::ReadOnly)) {
56 qCWarning(KGAPIDebug) << "Failed to access" << filePath;
57 return QByteArray();
58 }
59
60 if (contentType.isEmpty()) {
61 const QMimeDatabase db;
62 const QMimeType mime = db.mimeTypeForFileNameAndData(filePath, &file);
63 contentType = mime.name();
64 qCDebug(KGAPIDebug) << "Determined content type" << contentType << "for" << filePath;
65 }
66
67 file.reset();
68 QByteArray output = file.readAll();
69
70 file.close();
71
72 return output;
73}
74
75QByteArray FileAbstractUploadJob::Private::buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary)
76{
77 QString fileContentType = metaData->mimeType();
78 QByteArray fileContent;
79
80 fileContent = readFile(filePath, fileContentType);
81 if (fileContent.isEmpty()) {
82 return QByteArray();
83 }
84
85 qCDebug(KGAPIDebug) << "Setting content type" << fileContentType << "for" << filePath;
86
87 // Wannabe implementation of RFC2387, i.e. multipart/related
88 QByteArray body;
89 QFileInfo finfo(filePath);
90 const QByteArray md5 = QCryptographicHash::hash(finfo.fileName().toLatin1(), QCryptographicHash::Md5);
91 boundary = QString::fromLatin1(md5.toHex());
92
93 body += "--" + boundary.toLatin1() + '\n';
94 body += "Content-Type: application/json; charset=UTF-8\n";
95 body += '\n';
96 body += File::toJSON(metaData, q->serializationOptions());
97 body += '\n';
98 body += '\n';
99 body += "--" + boundary.toLatin1() + '\n';
100 body += "Content-Type: " + fileContentType.toLatin1() + '\n';
101 body += '\n';
102 body += fileContent;
103 body += '\n';
104 body += "--" + boundary.toLatin1() + "--";
105
106 return body;
107}
108
109void FileAbstractUploadJob::Private::processNext()
110{
111 if (files.isEmpty()) {
112 q->emitFinished();
113 return;
114 }
115
116 const QString filePath = files.cbegin().key();
117 if (!filePath.startsWith(QLatin1StringView("?=")) && !QFile::exists(filePath)) {
118 qCWarning(KGAPIDebug) << filePath << "is not a valid file path";
119 processNext();
120 return;
121 }
122
123 const FilePtr metaData = files.take(filePath);
124
125 QUrl url;
126 if (filePath.startsWith(QLatin1StringView("?="))) {
127 url = q->createUrl(QString(), metaData);
128 } else {
129 url = q->createUrl(filePath, metaData);
130 }
131
132 q->updateUrl(url);
133 QUrlQuery query(url);
134
135 QByteArray rawData;
136 QString contentType;
137
138 // just to be sure
139 query.removeQueryItem(QStringLiteral("uploadType"));
140 if (metaData.isNull()) {
141 query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("media"));
142
143 rawData = readFile(filePath, contentType);
144 if (rawData.isEmpty()) {
145 processNext();
146 return;
147 }
148
149 } else if (!filePath.startsWith(QLatin1StringView("?="))) {
150 query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("multipart"));
151
152 QString boundary;
153 rawData = buildMultipart(filePath, metaData, boundary);
154
155 contentType = QStringLiteral("multipart/related; boundary=%1").arg(boundary);
156 if (rawData.isEmpty()) {
157 processNext();
158 return;
159 }
160 } else {
161 rawData = File::toJSON(metaData, q->serializationOptions());
162 contentType = QStringLiteral("application/json");
163 }
164
165 url.setQuery(query);
166
167 QNetworkRequest request(url);
168 request.setHeader(QNetworkRequest::ContentLengthHeader, rawData.length());
169 request.setHeader(QNetworkRequest::ContentTypeHeader, contentType);
170 request.setAttribute(QNetworkRequest::User, filePath);
171
172 q->enqueueRequest(request, rawData, contentType);
173}
174
175void FileAbstractUploadJob::Private::_k_uploadProgress(qint64 bytesSent, qint64 totalBytes)
176{
177 // Each file consists of 100 units, so if we have two files, one already
178 // uploaded and the other one uploaded from 50%, the values are (150, 200)
179
180 int processedParts = (originalFilesCount - files.count() - 1) * 100;
181 int currentFileParts = 100.0 * ((qreal)bytesSent / (qreal)totalBytes);
182
183 q->emitProgress(processedParts + currentFileParts, originalFilesCount * 100);
184}
185
186FileAbstractUploadJob::FileAbstractUploadJob(const FilePtr &metadata, const AccountPtr &account, QObject *parent)
187 : FileAbstractDataJob(account, parent)
188 , d(new Private(this))
189{
190 d->files.insert(QStringLiteral("?=0"), metadata);
191 d->originalFilesCount = 1;
192}
193
194FileAbstractUploadJob::FileAbstractUploadJob(const FilesList &metadata, const AccountPtr &account, QObject *parent)
195 : FileAbstractDataJob(account, parent)
196 , d(new Private(this))
197{
198 int i = 0;
199 for (const FilePtr &file : metadata) {
200 d->files.insert(QStringLiteral("?=%1").arg(i), file);
201 ++i;
202 }
203 d->originalFilesCount = d->files.count();
204}
205
206FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const AccountPtr &account, QObject *parent)
207 : FileAbstractDataJob(account, parent)
208 , d(new Private(this))
209{
210 d->files.insert(filePath, FilePtr());
211 d->originalFilesCount = 1;
212}
213
214FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const FilePtr &metaData, const AccountPtr &account, QObject *parent)
215 : FileAbstractDataJob(account, parent)
216 , d(new Private(this))
217{
218 d->files.insert(filePath, metaData);
219 d->originalFilesCount = 1;
220}
221
222FileAbstractUploadJob::FileAbstractUploadJob(const QStringList &filePaths, const AccountPtr &account, QObject *parent)
223 : FileAbstractDataJob(account, parent)
224 , d(new Private(this))
225{
226 for (const QString &filePath : filePaths) {
227 d->files.insert(filePath, FilePtr());
228 }
229 d->originalFilesCount = d->files.count();
230}
231
232FileAbstractUploadJob::FileAbstractUploadJob(const QMap<QString, FilePtr> &files, const AccountPtr &account, QObject *parent)
233 : FileAbstractDataJob(account, parent)
234 , d(new Private(this))
235{
236 d->files = files;
237 d->originalFilesCount = d->files.count();
238}
239
240FileAbstractUploadJob::~FileAbstractUploadJob()
241{
242 delete d;
243}
244
245void FileAbstractUploadJob::start()
246{
247 d->processNext();
248}
249
250QMap<QString, FilePtr> FileAbstractUploadJob::files() const
251{
252 return d->uploadedFiles;
253}
254
255void FileAbstractUploadJob::dispatchRequest(QNetworkAccessManager *accessManager,
256 const QNetworkRequest &request,
257 const QByteArray &data,
258 const QString &contentType)
259{
260 Q_UNUSED(contentType)
261
262 QNetworkReply *reply = dispatch(accessManager, request, data);
263
264 connect(reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 totalBytes) {
265 d->_k_uploadProgress(bytesSent, totalBytes);
266 });
267}
268
269void FileAbstractUploadJob::handleReply(const QNetworkReply *reply, const QByteArray &rawData)
270{
271 const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString();
272 ContentType ct = Utils::stringToContentType(contentType);
273 if (ct == KGAPI2::JSON) {
274 const QNetworkRequest request = reply->request();
275 const QString filePath = request.attribute(QNetworkRequest::User).toString();
276
277 FilePtr file = File::fromJSON(rawData);
278
279 d->uploadedFiles.insert(filePath, file);
280 } else {
282 setErrorString(tr("Invalid response content type"));
283 emitFinished();
284 return;
285 }
286
287 d->processNext();
288}
289
290void FileAbstractUploadJob::setSerializationOptions(File::SerializationOptions options)
291{
292 d->serializationOptions = options;
293}
294
295File::SerializationOptions FileAbstractUploadJob::serializationOptions() const
296{
297 return d->serializationOptions;
298}
299
300#include "moc_fileabstractuploadjob.cpp"
void setErrorString(const QString &errorString)
Set job error description to errorString.
Definition job.cpp:401
AccountPtr account() const
Returns account used to authenticate requests.
Definition job.cpp:436
virtual void emitFinished()
Emits Job::finished() signal.
Definition job.cpp:493
void setError(KGAPI2::Error error)
Set job error to error.
Definition job.cpp:386
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
A job to fetch a single map tile described by a StaticMapUrl.
Definition blog.h:16
@ InvalidResponse
LibKGAPI error - Google returned invalid response.
Definition types.h:183
ContentType
Definition types.h:210
bool readFile(const QString &sourceUrl, QString &sourceCode)
bool isEmpty() const const
qsizetype length() const const
QByteArray toHex(char separator) const const
QByteArray hash(QByteArrayView data, Algorithm method)
bool exists() const const
size_type count() const const
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const const
QVariant header(QNetworkRequest::KnownHeaders header) const const
QNetworkRequest request() const const
void uploadProgress(qint64 bytesSent, qint64 bytesTotal)
QVariant attribute(Attribute code, const QVariant &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isNull() const const
QString arg(Args &&... args) const const
const_iterator cbegin() const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
void setQuery(const QString &query, ParsingMode mode)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.