mirror of
https://github.com/transmission/transmission
synced 2024-12-23 08:13:27 +00:00
d7930984ef
There're places where manual intervention is still required as uncrustify is not ideal (unfortunately), but at least one may rely on it to do the right thing most of the time (e.g. when sending in a patch). The style itself is quite different from what we had before but making it uniform across all the codebase is the key. I also hope that it'll make the code more readable (YMMV) and less sensitive to further changes.
321 lines
8.1 KiB
C++
321 lines
8.1 KiB
C++
/*
|
|
* This file Copyright (C) 2014-2016 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
|
|
#include <QApplication>
|
|
#include <QHostAddress>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QNetworkRequest>
|
|
|
|
#include <event2/buffer.h>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/rpcimpl.h>
|
|
#include <libtransmission/utils.h> // tr_free
|
|
#include <libtransmission/version.h> // LONG_VERSION_STRING
|
|
|
|
#include "RpcClient.h"
|
|
|
|
// #define DEBUG_HTTP
|
|
|
|
#define REQUEST_DATA_PROPERTY_KEY "requestData"
|
|
#define REQUEST_FUTUREINTERFACE_PROPERTY_KEY "requestReplyFutureInterface"
|
|
|
|
namespace
|
|
{
|
|
|
|
void destroyVariant(tr_variant* json)
|
|
{
|
|
tr_variantFree(json);
|
|
tr_free(json);
|
|
}
|
|
|
|
TrVariantPtr createVariant()
|
|
{
|
|
return TrVariantPtr(tr_new0(tr_variant, 1), &destroyVariant);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
RpcClient::RpcClient(QObject* parent) :
|
|
QObject(parent),
|
|
mySession(nullptr),
|
|
myNAM(nullptr),
|
|
myNextTag(0)
|
|
{
|
|
qRegisterMetaType<TrVariantPtr>("TrVariantPtr");
|
|
}
|
|
|
|
void RpcClient::stop()
|
|
{
|
|
mySession = nullptr;
|
|
mySessionId.clear();
|
|
myUrl.clear();
|
|
|
|
if (myNAM != nullptr)
|
|
{
|
|
myNAM->deleteLater();
|
|
myNAM = nullptr;
|
|
}
|
|
}
|
|
|
|
void RpcClient::start(tr_session* session)
|
|
{
|
|
mySession = session;
|
|
}
|
|
|
|
void RpcClient::start(const QUrl& url)
|
|
{
|
|
myUrl = url;
|
|
}
|
|
|
|
bool RpcClient::isLocal() const
|
|
{
|
|
if (mySession != 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (QHostAddress(myUrl.host()).isLoopback())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const QUrl& RpcClient::url() const
|
|
{
|
|
return myUrl;
|
|
}
|
|
|
|
RpcResponseFuture RpcClient::exec(tr_quark method, tr_variant* args)
|
|
{
|
|
return exec(tr_quark_get_string(method, nullptr), args);
|
|
}
|
|
|
|
RpcResponseFuture RpcClient::exec(const char* method, tr_variant* args)
|
|
{
|
|
TrVariantPtr json = createVariant();
|
|
tr_variantInitDict(json.get(), 3);
|
|
tr_variantDictAddStr(json.get(), TR_KEY_method, method);
|
|
|
|
if (args != nullptr)
|
|
{
|
|
tr_variantDictSteal(json.get(), TR_KEY_arguments, args);
|
|
}
|
|
|
|
return sendRequest(json);
|
|
}
|
|
|
|
int64_t RpcClient::getNextTag()
|
|
{
|
|
return myNextTag++;
|
|
}
|
|
|
|
void RpcClient::sendNetworkRequest(TrVariantPtr json, const QFutureInterface<RpcResponse>& promise)
|
|
{
|
|
QNetworkRequest request;
|
|
request.setUrl(myUrl);
|
|
request.setRawHeader("User-Agent", (qApp->applicationName() + QLatin1Char('/') +
|
|
QString::fromUtf8(LONG_VERSION_STRING)).toUtf8());
|
|
request.setRawHeader("Content-Type", "application/json; charset=UTF-8");
|
|
|
|
if (!mySessionId.isEmpty())
|
|
{
|
|
request.setRawHeader(TR_RPC_SESSION_ID_HEADER, mySessionId.toUtf8());
|
|
}
|
|
|
|
size_t rawJsonDataLength;
|
|
char* rawJsonData = tr_variantToStr(json.get(), TR_VARIANT_FMT_JSON_LEAN, &rawJsonDataLength);
|
|
QByteArray jsonData(rawJsonData, rawJsonDataLength);
|
|
tr_free(rawJsonData);
|
|
|
|
QNetworkReply* reply = networkAccessManager()->post(request, jsonData);
|
|
reply->setProperty(REQUEST_DATA_PROPERTY_KEY, QVariant::fromValue(json));
|
|
reply->setProperty(REQUEST_FUTUREINTERFACE_PROPERTY_KEY, QVariant::fromValue(promise));
|
|
|
|
connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SIGNAL(dataReadProgress()));
|
|
connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(dataSendProgress()));
|
|
|
|
#ifdef DEBUG_HTTP
|
|
std::cerr << "sending " << "POST " << qPrintable(myUrl.path()) << std::endl;
|
|
|
|
for (const QByteArray& b : request.rawHeaderList())
|
|
{
|
|
std::cerr << b.constData() << ": " << request.rawHeader(b).constData() << std::endl;
|
|
}
|
|
|
|
std::cerr << "Body:\n" << jsonData.constData() << std::endl;
|
|
#endif
|
|
}
|
|
|
|
void RpcClient::sendLocalRequest(TrVariantPtr json, const QFutureInterface<RpcResponse>& promise, int64_t tag)
|
|
{
|
|
myLocalRequests.insert(tag, promise);
|
|
tr_rpc_request_exec_json(mySession, json.get(), localSessionCallback, this);
|
|
}
|
|
|
|
RpcResponseFuture RpcClient::sendRequest(TrVariantPtr json)
|
|
{
|
|
int64_t tag = getNextTag();
|
|
tr_variantDictAddInt(json.get(), TR_KEY_tag, tag);
|
|
|
|
QFutureInterface<RpcResponse> promise;
|
|
promise.setExpectedResultCount(1);
|
|
promise.setProgressRange(0, 1);
|
|
promise.setProgressValue(0);
|
|
promise.reportStarted();
|
|
|
|
if (mySession != nullptr)
|
|
{
|
|
sendLocalRequest(json, promise, tag);
|
|
}
|
|
else if (!myUrl.isEmpty())
|
|
{
|
|
sendNetworkRequest(json, promise);
|
|
}
|
|
|
|
return promise.future();
|
|
}
|
|
|
|
QNetworkAccessManager* RpcClient::networkAccessManager()
|
|
{
|
|
if (myNAM == 0)
|
|
{
|
|
myNAM = new QNetworkAccessManager();
|
|
|
|
connect(myNAM, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
|
|
|
|
connect(myNAM, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this,
|
|
SIGNAL(httpAuthenticationRequired()));
|
|
}
|
|
|
|
return myNAM;
|
|
}
|
|
|
|
void RpcClient::localSessionCallback(tr_session* s, tr_variant* response, void* vself)
|
|
{
|
|
Q_UNUSED(s);
|
|
|
|
RpcClient* self = static_cast<RpcClient*>(vself);
|
|
|
|
TrVariantPtr json = createVariant();
|
|
*json = *response;
|
|
tr_variantInitBool(response, false);
|
|
|
|
// this callback is invoked in the libtransmission thread, so we don't want
|
|
// to process the response here... let's push it over to the Qt thread.
|
|
QMetaObject::invokeMethod(self, "localRequestFinished", Qt::QueuedConnection, Q_ARG(TrVariantPtr, json));
|
|
}
|
|
|
|
void RpcClient::networkRequestFinished(QNetworkReply* reply)
|
|
{
|
|
reply->deleteLater();
|
|
|
|
QFutureInterface<RpcResponse> promise = reply->property(REQUEST_FUTUREINTERFACE_PROPERTY_KEY).
|
|
value<QFutureInterface<RpcResponse>>();
|
|
|
|
#ifdef DEBUG_HTTP
|
|
std::cerr << "http response header: " << std::endl;
|
|
|
|
for (const QByteArray& b : reply->rawHeaderList())
|
|
{
|
|
std::cerr << b.constData() << ": " << reply->rawHeader(b).constData() << std::endl;
|
|
}
|
|
|
|
std::cerr << "json:\n" << reply->peek(reply->bytesAvailable()).constData() << std::endl;
|
|
#endif
|
|
|
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409 &&
|
|
reply->hasRawHeader(TR_RPC_SESSION_ID_HEADER))
|
|
{
|
|
// we got a 409 telling us our session id has expired.
|
|
// update it and resubmit the request.
|
|
mySessionId = QString::fromUtf8(reply->rawHeader(TR_RPC_SESSION_ID_HEADER));
|
|
|
|
sendNetworkRequest(reply->property(REQUEST_DATA_PROPERTY_KEY).value<TrVariantPtr>(), promise);
|
|
return;
|
|
}
|
|
|
|
emit networkResponse(reply->error(), reply->errorString());
|
|
|
|
if (reply->error() != QNetworkReply::NoError)
|
|
{
|
|
RpcResponse result;
|
|
result.networkError = reply->error();
|
|
|
|
promise.setProgressValueAndText(1, reply->errorString());
|
|
promise.reportFinished(&result);
|
|
}
|
|
else
|
|
{
|
|
RpcResponse result;
|
|
|
|
const QByteArray jsonData = reply->readAll().trimmed();
|
|
TrVariantPtr json = createVariant();
|
|
|
|
if (tr_variantFromJson(json.get(), jsonData.constData(), jsonData.size()) == 0)
|
|
{
|
|
result = parseResponseData(*json);
|
|
}
|
|
|
|
promise.setProgressValue(1);
|
|
promise.reportFinished(&result);
|
|
}
|
|
}
|
|
|
|
void RpcClient::localRequestFinished(TrVariantPtr response)
|
|
{
|
|
int64_t tag = parseResponseTag(*response);
|
|
RpcResponse result = parseResponseData(*response);
|
|
QFutureInterface<RpcResponse> promise = myLocalRequests.take(tag);
|
|
|
|
promise.setProgressRange(0, 1);
|
|
promise.setProgressValue(1);
|
|
promise.reportFinished(&result);
|
|
}
|
|
|
|
int64_t RpcClient::parseResponseTag(tr_variant& json)
|
|
{
|
|
int64_t tag;
|
|
|
|
if (!tr_variantDictFindInt(&json, TR_KEY_tag, &tag))
|
|
{
|
|
tag = -1;
|
|
}
|
|
|
|
return tag;
|
|
}
|
|
|
|
RpcResponse RpcClient::parseResponseData(tr_variant& json)
|
|
{
|
|
RpcResponse ret;
|
|
|
|
const char* result;
|
|
|
|
if (tr_variantDictFindStr(&json, TR_KEY_result, &result, nullptr))
|
|
{
|
|
ret.result = QString::fromUtf8(result);
|
|
ret.success = std::strcmp(result, "success") == 0;
|
|
}
|
|
|
|
tr_variant* args;
|
|
|
|
if (tr_variantDictFindDict(&json, TR_KEY_arguments, &args))
|
|
{
|
|
ret.args = createVariant();
|
|
*ret.args = *args;
|
|
tr_variantInitBool(args, false);
|
|
}
|
|
|
|
return ret;
|
|
}
|