File indexing completed on 2024-05-19 04:03:04
0001 /* 0002 SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "reviewboardjobs.h" 0008 #include "debug.h" 0009 0010 #include <QFile> 0011 #include <QJsonDocument> 0012 #include <QMimeDatabase> 0013 #include <QMimeType> 0014 #include <QNetworkReply> 0015 #include <QNetworkRequest> 0016 #include <QUrlQuery> 0017 0018 #include <KLocalizedString> 0019 #include <KRandom> 0020 0021 using namespace ReviewBoard; 0022 0023 QByteArray ReviewBoard::urlToData(const QUrl &url) 0024 { 0025 QByteArray ret; 0026 if (url.isLocalFile()) { 0027 QFile f(url.toLocalFile()); 0028 Q_ASSERT(f.exists()); 0029 bool corr = f.open(QFile::ReadOnly | QFile::Text); 0030 Q_ASSERT(corr); 0031 Q_UNUSED(corr); 0032 0033 ret = f.readAll(); 0034 0035 } else { 0036 // TODO: add downloading the data 0037 } 0038 return ret; 0039 } 0040 namespace 0041 { 0042 static const QByteArray m_boundary = "----------" + KRandom::randomString(42 + 13).toLatin1(); 0043 0044 QByteArray multipartFormData(const QList<QPair<QString, QVariant>> &values) 0045 { 0046 QByteArray form_data; 0047 for (const auto &val : values) { 0048 QByteArray hstr("--"); 0049 hstr += m_boundary; 0050 hstr += "\r\n"; 0051 hstr += "Content-Disposition: form-data; name=\""; 0052 hstr += val.first.toLatin1(); 0053 hstr += "\""; 0054 0055 // File 0056 if (val.second.userType() == QMetaType::QUrl) { 0057 QUrl path = val.second.toUrl(); 0058 hstr += "; filename=\"" + path.fileName().toLatin1() + "\""; 0059 const QMimeType mime = QMimeDatabase().mimeTypeForUrl(path); 0060 if (!mime.name().isEmpty()) { 0061 hstr += "\r\nContent-Type: "; 0062 hstr += mime.name().toLatin1(); 0063 } 0064 } 0065 // 0066 0067 hstr += "\r\n\r\n"; 0068 0069 // append body 0070 form_data.append(hstr); 0071 if (val.second.userType() == QMetaType::QUrl) 0072 form_data += urlToData(val.second.toUrl()); 0073 else 0074 form_data += val.second.toByteArray(); 0075 form_data.append("\r\n"); 0076 // EOFILE 0077 } 0078 0079 form_data += QByteArray("--" + m_boundary + "--\r\n"); 0080 0081 return form_data; 0082 } 0083 0084 QByteArray multipartFormData(const QVariantMap &values) 0085 { 0086 QList<QPair<QString, QVariant>> vals; 0087 for (QVariantMap::const_iterator it = values.constBegin(), itEnd = values.constEnd(); it != itEnd; ++it) { 0088 vals += qMakePair<QString, QVariant>(QString(it.key()), QVariant(it.value())); 0089 } 0090 return multipartFormData(vals); 0091 } 0092 0093 } 0094 0095 HttpCall::HttpCall(const QUrl &s, 0096 const QString &apiPath, 0097 const QList<QPair<QString, QString>> &queryParameters, 0098 Method method, 0099 const QByteArray &post, 0100 bool multipart, 0101 QObject *parent) 0102 : KJob(parent) 0103 , m_reply(nullptr) 0104 , m_post(post) 0105 , m_multipart(multipart) 0106 , m_method(method) 0107 { 0108 m_requrl = s; 0109 m_requrl.setPath(m_requrl.path() + QLatin1Char('/') + apiPath); 0110 QUrlQuery query; 0111 for (QList<QPair<QString, QString>>::const_iterator i = queryParameters.begin(); i < queryParameters.end(); i++) { 0112 query.addQueryItem(i->first, i->second); 0113 } 0114 m_requrl.setQuery(query); 0115 } 0116 0117 void HttpCall::start() 0118 { 0119 QNetworkRequest r(m_requrl); 0120 0121 if (!m_requrl.userName().isEmpty()) { 0122 QByteArray head = "Basic " + m_requrl.userInfo().toLatin1().toBase64(); 0123 r.setRawHeader("Authorization", head); 0124 } 0125 0126 if (m_multipart) { 0127 r.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("multipart/form-data")); 0128 r.setHeader(QNetworkRequest::ContentLengthHeader, QString::number(m_post.size())); 0129 r.setRawHeader("Content-Type", "multipart/form-data; boundary=" + m_boundary); 0130 } 0131 0132 switch (m_method) { 0133 case Get: 0134 m_reply = m_manager.get(r); 0135 break; 0136 case Post: 0137 m_reply = m_manager.post(r, m_post); 0138 break; 0139 case Put: 0140 m_reply = m_manager.put(r, m_post); 0141 break; 0142 } 0143 connect(m_reply, &QNetworkReply::finished, this, &HttpCall::onFinished); 0144 0145 // qCDebug(PLUGIN_REVIEWBOARD) << "starting... requrl=" << m_requrl << "post=" << m_post; 0146 } 0147 0148 QVariant HttpCall::result() const 0149 { 0150 Q_ASSERT(m_reply->isFinished()); 0151 return m_result; 0152 } 0153 0154 void HttpCall::onFinished() 0155 { 0156 const QByteArray receivedData = m_reply->readAll(); 0157 QJsonParseError error; 0158 QJsonDocument parser = QJsonDocument::fromJson(receivedData, &error); 0159 const QVariant output = parser.toVariant(); 0160 0161 if (error.error == 0) { 0162 m_result = output; 0163 } else { 0164 setError(1); 0165 setErrorText(i18n("JSON error: %1", error.errorString())); 0166 } 0167 0168 if (output.toMap().value(QStringLiteral("stat")).toString() != QLatin1String("ok")) { 0169 setError(2); 0170 setErrorText(i18n("Request Error: %1", output.toMap().value(QStringLiteral("err")).toMap().value(QStringLiteral("msg")).toString())); 0171 } 0172 0173 if (receivedData.size() > 10000) 0174 qCDebug(PLUGIN_REVIEWBOARD) << "parsing..." << receivedData.size(); 0175 else 0176 qCDebug(PLUGIN_REVIEWBOARD) << "parsing..." << receivedData; 0177 emitResult(); 0178 } 0179 0180 NewRequest::NewRequest(const QUrl &server, const QString &projectPath, QObject *parent) 0181 : ReviewRequest(server, QString(), parent) 0182 , m_project(projectPath) 0183 { 0184 m_newreq = new HttpCall(this->server(), QStringLiteral("/api/review-requests/"), {}, HttpCall::Post, "repository=" + projectPath.toLatin1(), false, this); 0185 connect(m_newreq, &HttpCall::finished, this, &NewRequest::done); 0186 } 0187 0188 void NewRequest::start() 0189 { 0190 m_newreq->start(); 0191 } 0192 0193 void NewRequest::done() 0194 { 0195 if (m_newreq->error()) { 0196 qCDebug(PLUGIN_REVIEWBOARD) << "Could not create the new request" << m_newreq->errorString(); 0197 setError(2); 0198 setErrorText(i18n("Could not create the new request:\n%1", m_newreq->errorString())); 0199 } else { 0200 QVariant res = m_newreq->result(); 0201 setRequestId(res.toMap()[QStringLiteral("review_request")].toMap()[QStringLiteral("id")].toString()); 0202 Q_ASSERT(!requestId().isEmpty()); 0203 } 0204 0205 emitResult(); 0206 } 0207 0208 SubmitPatchRequest::SubmitPatchRequest(const QUrl &server, const QUrl &patch, const QString &basedir, const QString &id, QObject *parent) 0209 : ReviewRequest(server, id, parent) 0210 , m_patch(patch) 0211 , m_basedir(basedir) 0212 { 0213 QList<QPair<QString, QVariant>> vals; 0214 vals += QPair<QString, QVariant>(QStringLiteral("basedir"), m_basedir); 0215 vals += QPair<QString, QVariant>(QStringLiteral("path"), QVariant::fromValue<QUrl>(m_patch)); 0216 0217 m_uploadpatch = new HttpCall(this->server(), 0218 QStringLiteral("/api/review-requests/") + requestId() + QStringLiteral("/diffs/"), 0219 {}, 0220 HttpCall::Post, 0221 multipartFormData(vals), 0222 true, 0223 this); 0224 connect(m_uploadpatch, &HttpCall::finished, this, &SubmitPatchRequest::done); 0225 } 0226 0227 void SubmitPatchRequest::start() 0228 { 0229 m_uploadpatch->start(); 0230 } 0231 0232 void SubmitPatchRequest::done() 0233 { 0234 if (m_uploadpatch->error()) { 0235 qCWarning(PLUGIN_REVIEWBOARD) << "Could not upload the patch" << m_uploadpatch->errorString(); 0236 setError(3); 0237 setErrorText(i18n("Could not upload the patch")); 0238 } 0239 0240 emitResult(); 0241 } 0242 0243 ProjectsListRequest::ProjectsListRequest(const QUrl &server, QObject *parent) 0244 : KJob(parent) 0245 , m_server(server) 0246 { 0247 } 0248 0249 void ProjectsListRequest::start() 0250 { 0251 requestRepositoryList(0); 0252 } 0253 0254 QVariantList ProjectsListRequest::repositories() const 0255 { 0256 return m_repositories; 0257 } 0258 0259 void ProjectsListRequest::requestRepositoryList(int startIndex) 0260 { 0261 QList<QPair<QString, QString>> repositoriesParameters; 0262 0263 // In practice, the web API will return at most 200 repos per call, so just hardcode that value here 0264 repositoriesParameters << qMakePair(QStringLiteral("max-results"), QStringLiteral("200")); 0265 repositoriesParameters << qMakePair(QStringLiteral("start"), QString::number(startIndex)); 0266 0267 HttpCall *repositoriesCall = new HttpCall(m_server, QStringLiteral("/api/repositories/"), repositoriesParameters, HttpCall::Get, QByteArray(), false, this); 0268 connect(repositoriesCall, &HttpCall::finished, this, &ProjectsListRequest::done); 0269 0270 repositoriesCall->start(); 0271 } 0272 0273 void ProjectsListRequest::done(KJob *job) 0274 { 0275 // TODO error 0276 // TODO max iterations 0277 HttpCall *repositoriesCall = qobject_cast<HttpCall *>(job); 0278 const QMap<QString, QVariant> resultMap = repositoriesCall->result().toMap(); 0279 const int totalResults = resultMap[QStringLiteral("total_results")].toInt(); 0280 m_repositories << resultMap[QStringLiteral("repositories")].toList(); 0281 0282 if (m_repositories.count() < totalResults) { 0283 requestRepositoryList(m_repositories.count()); 0284 } else { 0285 emitResult(); 0286 } 0287 } 0288 0289 ReviewListRequest::ReviewListRequest(const QUrl &server, const QString &user, const QString &reviewStatus, QObject *parent) 0290 : KJob(parent) 0291 , m_server(server) 0292 , m_user(user) 0293 , m_reviewStatus(reviewStatus) 0294 { 0295 } 0296 0297 void ReviewListRequest::start() 0298 { 0299 requestReviewList(0); 0300 } 0301 0302 QVariantList ReviewListRequest::reviews() const 0303 { 0304 return m_reviews; 0305 } 0306 0307 void ReviewListRequest::requestReviewList(int startIndex) 0308 { 0309 QList<QPair<QString, QString>> reviewParameters; 0310 0311 // In practice, the web API will return at most 200 repos per call, so just hardcode that value here 0312 reviewParameters << qMakePair(QStringLiteral("max-results"), QStringLiteral("200")); 0313 reviewParameters << qMakePair(QStringLiteral("start"), QString::number(startIndex)); 0314 reviewParameters << qMakePair(QStringLiteral("from-user"), m_user); 0315 reviewParameters << qMakePair(QStringLiteral("status"), m_reviewStatus); 0316 0317 HttpCall *reviewsCall = new HttpCall(m_server, QStringLiteral("/api/review-requests/"), reviewParameters, HttpCall::Get, QByteArray(), false, this); 0318 connect(reviewsCall, &HttpCall::finished, this, &ReviewListRequest::done); 0319 0320 reviewsCall->start(); 0321 } 0322 0323 void ReviewListRequest::done(KJob *job) 0324 { 0325 // TODO error 0326 // TODO max iterations 0327 if (job->error()) { 0328 qCDebug(PLUGIN_REVIEWBOARD) << "Could not get reviews list" << job->errorString(); 0329 setError(3); 0330 setErrorText(i18n("Could not get reviews list")); 0331 emitResult(); 0332 } 0333 0334 HttpCall *reviewsCall = qobject_cast<HttpCall *>(job); 0335 QMap<QString, QVariant> resultMap = reviewsCall->result().toMap(); 0336 const int totalResults = resultMap[QStringLiteral("total_results")].toInt(); 0337 0338 m_reviews << resultMap[QStringLiteral("review_requests")].toList(); 0339 0340 if (m_reviews.count() < totalResults) { 0341 requestReviewList(m_reviews.count()); 0342 } else { 0343 emitResult(); 0344 } 0345 } 0346 0347 UpdateRequest::UpdateRequest(const QUrl &server, const QString &id, const QVariantMap &newValues, QObject *parent) 0348 : ReviewRequest(server, id, parent) 0349 { 0350 m_req = new HttpCall(this->server(), 0351 QStringLiteral("/api/review-requests/") + id + QStringLiteral("/draft/"), 0352 {}, 0353 HttpCall::Put, 0354 multipartFormData(newValues), 0355 true, 0356 this); 0357 connect(m_req, &HttpCall::finished, this, &UpdateRequest::done); 0358 } 0359 0360 void UpdateRequest::start() 0361 { 0362 m_req->start(); 0363 } 0364 0365 void UpdateRequest::done() 0366 { 0367 if (m_req->error()) { 0368 qCWarning(PLUGIN_REVIEWBOARD) << "Could not set all metadata to the review" << m_req->errorString() << m_req->property("result"); 0369 setError(3); 0370 setErrorText(i18n("Could not set metadata")); 0371 } 0372 0373 emitResult(); 0374 } 0375 0376 #include "moc_reviewboardjobs.cpp"