/* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "reviewboardjobs.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace ReviewBoard; QByteArray ReviewBoard::urlToData(const KUrl& url) { QByteArray ret; if (url.isLocalFile()) { QFile f(url.toLocalFile()); Q_ASSERT(f.exists()); bool corr=f.open(QFile::ReadOnly | QFile::Text); Q_ASSERT(corr); Q_UNUSED(corr); ret = f.readAll(); } else { //TODO: add downloading the data } return ret; } namespace { static const QByteArray m_boundary = "----------" + KRandom::randomString( 42 + 13 ).toLatin1(); QByteArray multipartFormData(const QList >& values) { typedef QPair StrVar; QByteArray form_data; foreach(const StrVar& val, values) { QByteArray hstr("--"); hstr += m_boundary; hstr += "\r\n"; hstr += "Content-Disposition: form-data; name=\""; hstr += val.first.toLatin1(); hstr += "\""; //File if (val.second.type()==QVariant::Url) { KUrl path=val.second.toUrl(); hstr += "; filename=\"" + path.fileName().toLatin1() + "\""; const KMimeType::Ptr ptr = KMimeType::findByUrl(path); if (!ptr->name().isEmpty()) { hstr += "\r\nContent-Type: "; hstr += ptr->name().toLatin1().constData(); } } // hstr += "\r\n\r\n"; // append body form_data.append(hstr); if (val.second.type()==QVariant::Url) form_data += urlToData(val.second.toUrl()); else form_data += val.second.toByteArray(); form_data.append("\r\n"); //EOFILE } form_data += QByteArray("--" + m_boundary + "--\r\n"); return form_data; } } HttpCall::HttpCall(const KUrl& s, const QString& apiPath, const QList >& queryParameters, const QByteArray& post, bool multipart, QObject* parent) : KJob(parent), m_post(post), m_multipart(multipart) { m_requrl=s; m_requrl.addPath(apiPath); for(QList >::const_iterator i = queryParameters.begin(); i < queryParameters.end(); i++) { m_requrl.addQueryItem(i->first, i->second); } } void HttpCall::start() { QNetworkRequest r(m_requrl); if(m_requrl.hasUser()) { QByteArray head = "Basic " + m_requrl.userInfo().toLatin1().toBase64(); r.setRawHeader("Authorization", head); } if(m_multipart) { r.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data"); r.setHeader(QNetworkRequest::ContentLengthHeader, QString::number(m_post.size())); r.setRawHeader( "Content-Type", "multipart/form-data; boundary=" + m_boundary ); } if(m_post.isEmpty()) m_reply=m_manager.get(r); else m_reply=m_manager.post(r, m_post); connect(m_reply, SIGNAL(finished()), SLOT(finished())); qDebug() << "starting... requrl=" << m_requrl << "post=" << m_post; } QVariant HttpCall::result() const { Q_ASSERT(m_reply->isFinished()); return m_result; } void HttpCall::finished() { QJson::Parser parser; QByteArray receivedData = m_reply->readAll(); // qDebug() << "parsing..." << receivedData; bool ok; m_result = parser.parse(receivedData, &ok); if (!ok) { setError(1); setErrorText(i18n("JSON error: %1: %2", parser.errorLine(), parser.errorString())); } if (m_result.toMap().value("stat").toString()!="ok") { setError(2); setErrorText(i18n("Request Error: %1", m_result.toMap().value("err").toMap().value("msg").toString())); } emitResult(); } NewRequest::NewRequest(const KUrl& server, const QString& projectPath, QObject* parent) : KJob(parent), m_server(server), m_project(projectPath) { m_newreq = new HttpCall(m_server, "/api/review-requests/", QList >(), "repository="+projectPath.toLatin1(), false, this); connect(m_newreq, SIGNAL(finished(KJob*)), SLOT(done())); } void NewRequest::start() { m_newreq->start(); } QString NewRequest::requestId() const { return m_id; } void NewRequest::done() { if (m_newreq->error()) { qDebug() << "Could not create the new request" << m_newreq->errorString(); setError(2); setErrorText(i18n("Could not create the new request:\n%1", m_newreq->errorString())); } else { QVariant res = m_newreq->result(); m_id = res.toMap()["review_request"].toMap()["id"].toString(); Q_ASSERT(!m_id.isEmpty()); } emitResult(); } SubmitPatchRequest::SubmitPatchRequest(const KUrl& server, const KUrl& patch, const QString& basedir, const QString& id, QObject* parent) : KJob(parent), m_server(server), m_patch(patch), m_basedir(basedir), m_id(id) { QList > vals; vals += QPair("basedir", m_basedir); vals += QPair("path", qVariantFromValue(m_patch)); m_uploadpatch = new HttpCall(m_server, "/api/review-requests/"+m_id+"/diffs/", QList >(), multipartFormData(vals), true, this); connect(m_uploadpatch, SIGNAL(finished(KJob*)), SLOT(done())); } void SubmitPatchRequest::start() { m_uploadpatch->start(); } QString SubmitPatchRequest::requestId() const { return m_id; } void SubmitPatchRequest::done() { if (m_uploadpatch->error()) { qDebug() << "Could not upload the patch" << m_uploadpatch->errorString(); setError(3); setErrorText(i18n("Could not upload the patch")); } emitResult(); } ProjectsListRequest::ProjectsListRequest(const KUrl& server, QObject* parent) : KJob(parent), m_server(server) { } void ProjectsListRequest::start() { requestRepositoryList(0); } QVariantList ProjectsListRequest::repositories() const { return m_repositories; } void ProjectsListRequest::requestRepositoryList(int startIndex) { QList > repositoriesParameters; // In practice, the web API will return at most 200 repos per call, so just hardcode that value here repositoriesParameters << qMakePair("max-results", QLatin1String("200")); repositoriesParameters << qMakePair("start", QString("%1").arg(startIndex)); HttpCall* repositoriesCall = new HttpCall(m_server, "/api/repositories/", repositoriesParameters, "", false, this); connect(repositoriesCall, SIGNAL(finished(KJob*)), SLOT(done(KJob*))); repositoriesCall->start(); } void ProjectsListRequest::done(KJob* job) { // TODO error // TODO max iterations HttpCall* repositoriesCall = qobject_cast(job); QMap resultMap = repositoriesCall->result().toMap(); const int totalResults = repositoriesCall->result().toMap()["total_results"].toInt(); m_repositories << repositoriesCall->result().toMap()["repositories"].toList(); if (m_repositories.count() < totalResults) { requestRepositoryList(m_repositories.count()); } else { emitResult(); } } ReviewListRequest::ReviewListRequest(const KUrl& server, const QString& user, const QString& reviewStatus, QObject* parent) : KJob(parent), m_server(server), m_user(user), m_reviewStatus(reviewStatus) { } void ReviewListRequest::start() { requestReviewList(0); } QVariantList ReviewListRequest::reviews() const { return m_reviews; } void ReviewListRequest::requestReviewList(int startIndex) { QList > reviewParameters; // In practice, the web API will return at most 200 repos per call, so just hardcode that value here reviewParameters << qMakePair("max-results", QLatin1String("200")); reviewParameters << qMakePair("start", QString("%1").arg(startIndex)); reviewParameters << qMakePair("from-user", m_user); reviewParameters << qMakePair("status", m_reviewStatus); HttpCall* reviewsCall = new HttpCall(m_server, "/api/review-requests/", reviewParameters, "", false, this); connect(reviewsCall, SIGNAL(finished(KJob*)), SLOT(done(KJob*))); reviewsCall->start(); } void ReviewListRequest::done(KJob* job) { // TODO error // TODO max iterations if (job->error()) { qDebug() << "Could not get reviews list" << job->errorString(); setError(3); setErrorText(i18n("Could not get reviews list")); emitResult(); } HttpCall* reviewsCall = qobject_cast(job); QMap resultMap = reviewsCall->result().toMap(); const int totalResults = resultMap["total_results"].toInt(); m_reviews << resultMap["review_requests"].toList(); if (m_reviews.count() < totalResults) { requestReviewList(m_reviews.count()); } else { emitResult(); } }