File indexing completed on 2024-05-12 05:22:19
0001 /* 0002 * This file is part of LibKGAPI library 0003 * 0004 * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0007 */ 0008 0009 #include "fileabstractuploadjob.h" 0010 #include "debug.h" 0011 #include "utils.h" 0012 0013 #include <QNetworkReply> 0014 #include <QNetworkRequest> 0015 0016 #include <QCryptographicHash> 0017 #include <QFile> 0018 #include <QFileInfo> 0019 #include <QMimeDatabase> 0020 #include <QMimeType> 0021 #include <QUrlQuery> 0022 0023 using namespace KGAPI2; 0024 using namespace KGAPI2::Drive; 0025 0026 class Q_DECL_HIDDEN FileAbstractUploadJob::Private 0027 { 0028 public: 0029 Private(FileAbstractUploadJob *parent); 0030 void processNext(); 0031 QByteArray buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary); 0032 QByteArray readFile(const QString &filePath, QString &contentType); 0033 0034 void _k_uploadProgress(qint64 bytesSent, qint64 totalBytes); 0035 0036 int originalFilesCount = 0; 0037 QMap<QString, FilePtr> files; 0038 0039 QMap<QString, FilePtr> uploadedFiles; 0040 0041 File::SerializationOptions serializationOptions = File::NoOptions; 0042 0043 private: 0044 FileAbstractUploadJob *const q; 0045 }; 0046 0047 FileAbstractUploadJob::Private::Private(FileAbstractUploadJob *parent) 0048 : q(parent) 0049 { 0050 } 0051 0052 QByteArray FileAbstractUploadJob::Private::readFile(const QString &filePath, QString &contentType) 0053 { 0054 QFile file(filePath); 0055 if (!file.open(QIODevice::ReadOnly)) { 0056 qCWarning(KGAPIDebug) << "Failed to access" << filePath; 0057 return QByteArray(); 0058 } 0059 0060 if (contentType.isEmpty()) { 0061 const QMimeDatabase db; 0062 const QMimeType mime = db.mimeTypeForFileNameAndData(filePath, &file); 0063 contentType = mime.name(); 0064 qCDebug(KGAPIDebug) << "Determined content type" << contentType << "for" << filePath; 0065 } 0066 0067 file.reset(); 0068 QByteArray output = file.readAll(); 0069 0070 file.close(); 0071 0072 return output; 0073 } 0074 0075 QByteArray FileAbstractUploadJob::Private::buildMultipart(const QString &filePath, const FilePtr &metaData, QString &boundary) 0076 { 0077 QString fileContentType = metaData->mimeType(); 0078 QByteArray fileContent; 0079 0080 fileContent = readFile(filePath, fileContentType); 0081 if (fileContent.isEmpty()) { 0082 return QByteArray(); 0083 } 0084 0085 qCDebug(KGAPIDebug) << "Setting content type" << fileContentType << "for" << filePath; 0086 0087 // Wannabe implementation of RFC2387, i.e. multipart/related 0088 QByteArray body; 0089 QFileInfo finfo(filePath); 0090 const QByteArray md5 = QCryptographicHash::hash(finfo.fileName().toLatin1(), QCryptographicHash::Md5); 0091 boundary = QString::fromLatin1(md5.toHex()); 0092 0093 body += "--" + boundary.toLatin1() + '\n'; 0094 body += "Content-Type: application/json; charset=UTF-8\n"; 0095 body += '\n'; 0096 body += File::toJSON(metaData, q->serializationOptions()); 0097 body += '\n'; 0098 body += '\n'; 0099 body += "--" + boundary.toLatin1() + '\n'; 0100 body += "Content-Type: " + fileContentType.toLatin1() + '\n'; 0101 body += '\n'; 0102 body += fileContent; 0103 body += '\n'; 0104 body += "--" + boundary.toLatin1() + "--"; 0105 0106 return body; 0107 } 0108 0109 void FileAbstractUploadJob::Private::processNext() 0110 { 0111 if (files.isEmpty()) { 0112 q->emitFinished(); 0113 return; 0114 } 0115 0116 const QString filePath = files.cbegin().key(); 0117 if (!filePath.startsWith(QLatin1StringView("?=")) && !QFile::exists(filePath)) { 0118 qCWarning(KGAPIDebug) << filePath << "is not a valid file path"; 0119 processNext(); 0120 return; 0121 } 0122 0123 const FilePtr metaData = files.take(filePath); 0124 0125 QUrl url; 0126 if (filePath.startsWith(QLatin1StringView("?="))) { 0127 url = q->createUrl(QString(), metaData); 0128 } else { 0129 url = q->createUrl(filePath, metaData); 0130 } 0131 0132 q->updateUrl(url); 0133 QUrlQuery query(url); 0134 0135 QByteArray rawData; 0136 QString contentType; 0137 0138 // just to be sure 0139 query.removeQueryItem(QStringLiteral("uploadType")); 0140 if (metaData.isNull()) { 0141 query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("media")); 0142 0143 rawData = readFile(filePath, contentType); 0144 if (rawData.isEmpty()) { 0145 processNext(); 0146 return; 0147 } 0148 0149 } else if (!filePath.startsWith(QLatin1StringView("?="))) { 0150 query.addQueryItem(QStringLiteral("uploadType"), QStringLiteral("multipart")); 0151 0152 QString boundary; 0153 rawData = buildMultipart(filePath, metaData, boundary); 0154 0155 contentType = QStringLiteral("multipart/related; boundary=%1").arg(boundary); 0156 if (rawData.isEmpty()) { 0157 processNext(); 0158 return; 0159 } 0160 } else { 0161 rawData = File::toJSON(metaData, q->serializationOptions()); 0162 contentType = QStringLiteral("application/json"); 0163 } 0164 0165 url.setQuery(query); 0166 0167 QNetworkRequest request(url); 0168 request.setHeader(QNetworkRequest::ContentLengthHeader, rawData.length()); 0169 request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); 0170 request.setAttribute(QNetworkRequest::User, filePath); 0171 0172 q->enqueueRequest(request, rawData, contentType); 0173 } 0174 0175 void FileAbstractUploadJob::Private::_k_uploadProgress(qint64 bytesSent, qint64 totalBytes) 0176 { 0177 // Each file consists of 100 units, so if we have two files, one already 0178 // uploaded and the other one uploaded from 50%, the values are (150, 200) 0179 0180 int processedParts = (originalFilesCount - files.count() - 1) * 100; 0181 int currentFileParts = 100.0 * ((qreal)bytesSent / (qreal)totalBytes); 0182 0183 q->emitProgress(processedParts + currentFileParts, originalFilesCount * 100); 0184 } 0185 0186 FileAbstractUploadJob::FileAbstractUploadJob(const FilePtr &metadata, const AccountPtr &account, QObject *parent) 0187 : FileAbstractDataJob(account, parent) 0188 , d(new Private(this)) 0189 { 0190 d->files.insert(QStringLiteral("?=0"), metadata); 0191 d->originalFilesCount = 1; 0192 } 0193 0194 FileAbstractUploadJob::FileAbstractUploadJob(const FilesList &metadata, const AccountPtr &account, QObject *parent) 0195 : FileAbstractDataJob(account, parent) 0196 , d(new Private(this)) 0197 { 0198 int i = 0; 0199 for (const FilePtr &file : metadata) { 0200 d->files.insert(QStringLiteral("?=%1").arg(i), file); 0201 ++i; 0202 } 0203 d->originalFilesCount = d->files.count(); 0204 } 0205 0206 FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const AccountPtr &account, QObject *parent) 0207 : FileAbstractDataJob(account, parent) 0208 , d(new Private(this)) 0209 { 0210 d->files.insert(filePath, FilePtr()); 0211 d->originalFilesCount = 1; 0212 } 0213 0214 FileAbstractUploadJob::FileAbstractUploadJob(const QString &filePath, const FilePtr &metaData, const AccountPtr &account, QObject *parent) 0215 : FileAbstractDataJob(account, parent) 0216 , d(new Private(this)) 0217 { 0218 d->files.insert(filePath, metaData); 0219 d->originalFilesCount = 1; 0220 } 0221 0222 FileAbstractUploadJob::FileAbstractUploadJob(const QStringList &filePaths, const AccountPtr &account, QObject *parent) 0223 : FileAbstractDataJob(account, parent) 0224 , d(new Private(this)) 0225 { 0226 for (const QString &filePath : filePaths) { 0227 d->files.insert(filePath, FilePtr()); 0228 } 0229 d->originalFilesCount = d->files.count(); 0230 } 0231 0232 FileAbstractUploadJob::FileAbstractUploadJob(const QMap<QString, FilePtr> &files, const AccountPtr &account, QObject *parent) 0233 : FileAbstractDataJob(account, parent) 0234 , d(new Private(this)) 0235 { 0236 d->files = files; 0237 d->originalFilesCount = d->files.count(); 0238 } 0239 0240 FileAbstractUploadJob::~FileAbstractUploadJob() 0241 { 0242 delete d; 0243 } 0244 0245 void FileAbstractUploadJob::start() 0246 { 0247 d->processNext(); 0248 } 0249 0250 QMap<QString, FilePtr> FileAbstractUploadJob::files() const 0251 { 0252 return d->uploadedFiles; 0253 } 0254 0255 void FileAbstractUploadJob::dispatchRequest(QNetworkAccessManager *accessManager, 0256 const QNetworkRequest &request, 0257 const QByteArray &data, 0258 const QString &contentType) 0259 { 0260 Q_UNUSED(contentType) 0261 0262 QNetworkReply *reply = dispatch(accessManager, request, data); 0263 0264 connect(reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 totalBytes) { 0265 d->_k_uploadProgress(bytesSent, totalBytes); 0266 }); 0267 } 0268 0269 void FileAbstractUploadJob::handleReply(const QNetworkReply *reply, const QByteArray &rawData) 0270 { 0271 const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); 0272 ContentType ct = Utils::stringToContentType(contentType); 0273 if (ct == KGAPI2::JSON) { 0274 const QNetworkRequest request = reply->request(); 0275 const QString filePath = request.attribute(QNetworkRequest::User).toString(); 0276 0277 FilePtr file = File::fromJSON(rawData); 0278 0279 d->uploadedFiles.insert(filePath, file); 0280 } else { 0281 setError(KGAPI2::InvalidResponse); 0282 setErrorString(tr("Invalid response content type")); 0283 emitFinished(); 0284 return; 0285 } 0286 0287 d->processNext(); 0288 } 0289 0290 void FileAbstractUploadJob::setSerializationOptions(File::SerializationOptions options) 0291 { 0292 d->serializationOptions = options; 0293 } 0294 0295 File::SerializationOptions FileAbstractUploadJob::serializationOptions() const 0296 { 0297 return d->serializationOptions; 0298 } 0299 0300 #include "moc_fileabstractuploadjob.cpp"