mirror of
https://github.com/transmission/transmission.git
synced 2026-05-08 09:39:08 +01:00
Refactor RPC requests code for proper queueing (patch by intelfx @ GH-10)
This refactoring is driven by the need to be able to do true queued RPC calls (where each successive call uses the result of the previous). Currently, such queueing of requests is done by assigning them special "magic" tag numbers, which are then intercepted in one big switch() statement and acted upon. This (aside from making code greatly unclear) effectively makes each such queue a singleton, because state passing is restricted to global variables. We refactor RpcClient to assign an unique tag to each remote call, and then abstract all the call<->response matching with Qt's future/promise mechanism. Finally, we introduce a "RPC request queue" class (RpcQueue) which is built on top of QFutureWatcher and C++11's <functional> library. This class maintains a queue of functions, where each function receives an RPC response, does necessary processing, performs another call and finally returns its future.
This commit is contained in:
+17
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2012-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2012-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -115,3 +115,19 @@ AddData::readableName () const
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString
|
||||
AddData::readableShortName () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case FILENAME:
|
||||
return QFileInfo (filename).fileName ();
|
||||
|
||||
case URL:
|
||||
return url.path ().split (QLatin1Char ('/')).last ();
|
||||
|
||||
default:
|
||||
return readableName ();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2012-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2012-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -33,6 +33,7 @@ class AddData
|
||||
|
||||
QByteArray toBase64 () const;
|
||||
QString readableName () const;
|
||||
QString readableShortName () const;
|
||||
|
||||
static bool isSupported (const QString& str) { return AddData (str).type != NONE; }
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ set(${PROJECT_NAME}_SOURCES
|
||||
PrefsDialog.cc
|
||||
RelocateDialog.cc
|
||||
RpcClient.cc
|
||||
RpcQueue.cc
|
||||
Session.cc
|
||||
SessionDialog.cc
|
||||
SqueezeLabel.cc
|
||||
@@ -112,6 +113,7 @@ set(${PROJECT_NAME}_HEADERS
|
||||
PrefsDialog.h
|
||||
RelocateDialog.h
|
||||
RpcClient.h
|
||||
RpcQueue.h
|
||||
Session.h
|
||||
SessionDialog.h
|
||||
Speed.h
|
||||
|
||||
+33
-38
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2013-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2013-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "Formatter.h"
|
||||
#include "FreeSpaceLabel.h"
|
||||
#include "RpcQueue.h"
|
||||
#include "Session.h"
|
||||
|
||||
namespace
|
||||
@@ -24,7 +25,6 @@ namespace
|
||||
FreeSpaceLabel::FreeSpaceLabel (QWidget * parent):
|
||||
QLabel (parent),
|
||||
mySession (nullptr),
|
||||
myTag (-1),
|
||||
myTimer (this)
|
||||
{
|
||||
myTimer.setSingleShot (true);
|
||||
@@ -39,14 +39,7 @@ FreeSpaceLabel::setSession (Session& session)
|
||||
if (mySession == &session)
|
||||
return;
|
||||
|
||||
if (mySession != nullptr)
|
||||
disconnect (mySession, nullptr, this, nullptr);
|
||||
|
||||
mySession = &session;
|
||||
|
||||
connect (mySession, SIGNAL (executed (int64_t, QString, tr_variant *)),
|
||||
this, SLOT (onSessionExecuted (int64_t, QString, tr_variant *)));
|
||||
|
||||
onTimer ();
|
||||
}
|
||||
|
||||
@@ -73,33 +66,35 @@ FreeSpaceLabel::onTimer ()
|
||||
tr_variantInitDict (&args, 1);
|
||||
tr_variantDictAddStr (&args, TR_KEY_path, myPath.toUtf8 ().constData());
|
||||
|
||||
myTag = mySession->getUniqueTag ();
|
||||
mySession->exec ("free-space", &args, myTag);
|
||||
}
|
||||
|
||||
void
|
||||
FreeSpaceLabel::onSessionExecuted (int64_t tag, const QString& result, tr_variant * arguments)
|
||||
{
|
||||
Q_UNUSED (result);
|
||||
|
||||
if (tag != myTag)
|
||||
return;
|
||||
|
||||
QString str;
|
||||
|
||||
// update the label
|
||||
int64_t bytes = -1;
|
||||
if (tr_variantDictFindInt (arguments, TR_KEY_size_bytes, &bytes) && bytes >= 0)
|
||||
setText (tr("%1 free").arg(Formatter::sizeToString (bytes)));
|
||||
else
|
||||
setText (QString ());
|
||||
|
||||
// update the tooltip
|
||||
size_t len = 0;
|
||||
const char * path = 0;
|
||||
tr_variantDictFindStr (arguments, TR_KEY_path, &path, &len);
|
||||
str = QString::fromUtf8 (path, len);
|
||||
setToolTip (QDir::toNativeSeparators (str));
|
||||
|
||||
myTimer.start ();
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this, &args] ()
|
||||
{
|
||||
return mySession->exec ("free-space", &args);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
QString str;
|
||||
|
||||
// update the label
|
||||
int64_t bytes = -1;
|
||||
if (tr_variantDictFindInt (r.args.get (), TR_KEY_size_bytes, &bytes) && bytes >= 0)
|
||||
setText (tr ("%1 free").arg (Formatter::sizeToString (bytes)));
|
||||
else
|
||||
setText (QString ());
|
||||
|
||||
// update the tooltip
|
||||
size_t len = 0;
|
||||
const char * path = 0;
|
||||
tr_variantDictFindStr (r.args.get (), TR_KEY_path, &path, &len);
|
||||
str = QString::fromUtf8 (path, len);
|
||||
setToolTip (QDir::toNativeSeparators (str));
|
||||
|
||||
myTimer.start ();
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
+1
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2013-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2013-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
@@ -34,12 +32,10 @@ class FreeSpaceLabel: public QLabel
|
||||
void setPath (const QString& folder);
|
||||
|
||||
private slots:
|
||||
void onSessionExecuted (int64_t tag, const QString& result, tr_variant * arguments);
|
||||
void onTimer ();
|
||||
|
||||
private:
|
||||
Session * mySession;
|
||||
int64_t myTag;
|
||||
QString myPath;
|
||||
QTimer myTimer;
|
||||
};
|
||||
|
||||
+4
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2009-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -285,8 +285,7 @@ MainWindow::MainWindow (Session& session, Prefs& prefs, TorrentModel& model, boo
|
||||
connect (&mySession, SIGNAL (dataReadProgress ()), this, SLOT (dataReadProgress ()));
|
||||
connect (&mySession, SIGNAL (dataSendProgress ()), this, SLOT (dataSendProgress ()));
|
||||
connect (&mySession, SIGNAL (httpAuthenticationRequired ()), this, SLOT (wrongAuthentication ()));
|
||||
connect (&mySession, SIGNAL (error (QNetworkReply::NetworkError)), this, SLOT (onError (QNetworkReply::NetworkError)));
|
||||
connect (&mySession, SIGNAL (errorMessage (QString)), this, SLOT (errorMessage(QString)));
|
||||
connect (&mySession, SIGNAL (networkResponse (QNetworkReply::NetworkError, QString)), this, SLOT (onNetworkResponse (QNetworkReply::NetworkError, QString)));
|
||||
|
||||
if (mySession.isServer ())
|
||||
{
|
||||
@@ -1376,13 +1375,14 @@ MainWindow::dataSendProgress ()
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::onError (QNetworkReply::NetworkError code)
|
||||
MainWindow::onNetworkResponse (QNetworkReply::NetworkError code, const QString& message)
|
||||
{
|
||||
const bool hadError = myNetworkError;
|
||||
const bool haveError = (code != QNetworkReply::NoError)
|
||||
&& (code != QNetworkReply::UnknownContentError);
|
||||
|
||||
myNetworkError = haveError;
|
||||
myErrorMessage = message;
|
||||
refreshTrayIconSoon();
|
||||
updateNetworkIcon();
|
||||
|
||||
@@ -1392,12 +1392,6 @@ MainWindow::onError (QNetworkReply::NetworkError code)
|
||||
myModel.clear();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::errorMessage (const QString& msg)
|
||||
{
|
||||
myErrorMessage = msg;
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::wrongAuthentication ()
|
||||
{
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2009-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -128,8 +128,7 @@ class MainWindow: public QMainWindow
|
||||
void toggleSpeedMode ();
|
||||
void dataReadProgress ();
|
||||
void dataSendProgress ();
|
||||
void onError (QNetworkReply::NetworkError);
|
||||
void errorMessage (const QString&);
|
||||
void onNetworkResponse (QNetworkReply::NetworkError code, const QString& message);
|
||||
void toggleWindows (bool doShow);
|
||||
void onSetPrefs ();
|
||||
void onSetPrefs (bool);
|
||||
|
||||
+127
-61
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2014-2015 Mnemosyne LLC
|
||||
* 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.
|
||||
@@ -7,6 +7,7 @@
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <QApplication>
|
||||
@@ -27,6 +28,7 @@
|
||||
// #define DEBUG_HTTP
|
||||
|
||||
#define REQUEST_DATA_PROPERTY_KEY "requestData"
|
||||
#define REQUEST_FUTUREINTERFACE_PROPERTY_KEY "requestReplyFutureInterface"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -47,12 +49,10 @@ namespace
|
||||
RpcClient::RpcClient (QObject * parent):
|
||||
QObject (parent),
|
||||
mySession (nullptr),
|
||||
myNAM (nullptr)
|
||||
myNAM (nullptr),
|
||||
myNextTag (0)
|
||||
{
|
||||
qRegisterMetaType<TrVariantPtr> ("TrVariantPtr");
|
||||
|
||||
connect (this, SIGNAL (responseReceived (TrVariantPtr)),
|
||||
this, SLOT (parseResponse (TrVariantPtr)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -105,64 +105,89 @@ RpcClient::url () const
|
||||
return myUrl;
|
||||
}
|
||||
|
||||
void
|
||||
RpcClient::exec (tr_quark method, tr_variant * args, int64_t tag)
|
||||
RpcResponseFuture
|
||||
RpcClient::exec (tr_quark method, tr_variant * args)
|
||||
{
|
||||
exec (tr_quark_get_string (method, nullptr), args, tag);
|
||||
return exec (tr_quark_get_string (method, nullptr), args);
|
||||
}
|
||||
|
||||
void
|
||||
RpcClient::exec (const char* method, tr_variant * args, int64_t tag)
|
||||
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 (tag >= 0)
|
||||
tr_variantDictAddInt (json.get (), TR_KEY_tag, tag);
|
||||
if (args != nullptr)
|
||||
tr_variantDictSteal (json.get (), TR_KEY_arguments, args);
|
||||
|
||||
sendRequest (json);
|
||||
return sendRequest (json);
|
||||
}
|
||||
|
||||
int64_t
|
||||
RpcClient::getNextTag ()
|
||||
{
|
||||
return myNextTag++;
|
||||
}
|
||||
|
||||
void
|
||||
RpcClient::sendRequest (TrVariantPtr json)
|
||||
RpcClient::sendNetworkRequest (TrVariantPtr json, const QFutureInterface<RpcResponse> &promise)
|
||||
{
|
||||
if (mySession != nullptr)
|
||||
{
|
||||
tr_rpc_request_exec_json (mySession, json.get (), localSessionCallback, this);
|
||||
}
|
||||
else if (!myUrl.isEmpty ())
|
||||
{
|
||||
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");
|
||||
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 ());
|
||||
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);
|
||||
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));
|
||||
connect (reply, SIGNAL (downloadProgress (qint64, qint64)), this, SIGNAL (dataReadProgress ()));
|
||||
connect (reply, SIGNAL (uploadProgress (qint64, qint64)), this, SIGNAL (dataSendProgress ()));
|
||||
connect (reply, SIGNAL (error (QNetworkReply::NetworkError)), this, SIGNAL (error (QNetworkReply::NetworkError)));
|
||||
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;
|
||||
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 *
|
||||
@@ -173,7 +198,7 @@ RpcClient::networkAccessManager ()
|
||||
myNAM = new QNetworkAccessManager ();
|
||||
|
||||
connect (myNAM, SIGNAL (finished (QNetworkReply *)),
|
||||
this, SLOT (onFinished (QNetworkReply *)));
|
||||
this, SLOT (networkRequestFinished (QNetworkReply * )));
|
||||
|
||||
connect (myNAM, SIGNAL (authenticationRequired (QNetworkReply *,QAuthenticator *)),
|
||||
this, SIGNAL (httpAuthenticationRequired ()));
|
||||
@@ -195,12 +220,16 @@ RpcClient::localSessionCallback (tr_session * s, tr_variant * response, void * v
|
||||
|
||||
// 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.
|
||||
self->responseReceived (json);
|
||||
QMetaObject::invokeMethod (self, "localRequestFinished", Qt::QueuedConnection, Q_ARG (TrVariantPtr, json));
|
||||
}
|
||||
|
||||
void
|
||||
RpcClient::onFinished (QNetworkReply * reply)
|
||||
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 ())
|
||||
@@ -217,40 +246,77 @@ RpcClient::onFinished (QNetworkReply * reply)
|
||||
// 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));
|
||||
sendRequest (reply->property (REQUEST_DATA_PROPERTY_KEY).value<TrVariantPtr> ());
|
||||
|
||||
sendNetworkRequest (reply->property (REQUEST_DATA_PROPERTY_KEY).value<TrVariantPtr> (), promise);
|
||||
return;
|
||||
}
|
||||
else if (reply->error () != QNetworkReply::NoError)
|
||||
|
||||
emit networkResponse (reply->error(), reply->errorString());
|
||||
|
||||
if (reply->error () != QNetworkReply::NoError)
|
||||
{
|
||||
emit errorMessage (reply->errorString ());
|
||||
RpcResponse result;
|
||||
result.networkError = reply->error ();
|
||||
|
||||
promise.setProgressValueAndText (1, reply->errorString ());
|
||||
promise.reportFinished (&result);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QByteArray jsonData = reply->readAll ().trimmed ();
|
||||
RpcResponse result;
|
||||
|
||||
const QByteArray jsonData = reply->readAll ().trimmed ();
|
||||
TrVariantPtr json = createVariant ();
|
||||
if (tr_variantFromJson (json.get (), jsonData.constData (), jsonData.size ()) == 0)
|
||||
parseResponse (json);
|
||||
result = parseResponseData (*json);
|
||||
|
||||
emit error (QNetworkReply::NoError);
|
||||
promise.setProgressValue (1);
|
||||
promise.reportFinished (&result);
|
||||
}
|
||||
|
||||
reply->deleteLater ();
|
||||
}
|
||||
|
||||
void
|
||||
RpcClient::parseResponse (TrVariantPtr json)
|
||||
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.get (), TR_KEY_tag, &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.get (), TR_KEY_result, &result, nullptr))
|
||||
result = nullptr;
|
||||
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.get (), TR_KEY_arguments, &args))
|
||||
args = nullptr;
|
||||
if (tr_variantDictFindDict (&json, TR_KEY_arguments, &args))
|
||||
{
|
||||
ret.args = createVariant ();
|
||||
*ret.args = *args;
|
||||
tr_variantInitBool (args, false);
|
||||
}
|
||||
|
||||
emit executed (tag, result == nullptr ? QString () : QString::fromUtf8 (result), args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
+31
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2014-2015 Mnemosyne LLC
|
||||
* 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.
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureInterface>
|
||||
#include <QHash>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
@@ -32,6 +35,19 @@ extern "C"
|
||||
struct tr_session;
|
||||
}
|
||||
|
||||
struct RpcResponse
|
||||
{
|
||||
QString result;
|
||||
TrVariantPtr args;
|
||||
bool success = false;
|
||||
QNetworkReply::NetworkError networkError = QNetworkReply::NoError;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE (QFutureInterface<RpcResponse>);
|
||||
|
||||
// The response future -- the RPC engine returns one for each request made.
|
||||
typedef QFuture<RpcResponse> RpcResponseFuture;
|
||||
|
||||
class RpcClient: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -47,34 +63,37 @@ class RpcClient: public QObject
|
||||
bool isLocal () const;
|
||||
const QUrl& url () const;
|
||||
|
||||
void exec (tr_quark method, tr_variant * args, int64_t tag = -1);
|
||||
void exec (const char* method, tr_variant * args, int64_t tag = -1);
|
||||
RpcResponseFuture exec (tr_quark method, tr_variant * args);
|
||||
RpcResponseFuture exec (const char * method, tr_variant * args);
|
||||
|
||||
signals:
|
||||
void httpAuthenticationRequired ();
|
||||
void dataReadProgress ();
|
||||
void dataSendProgress ();
|
||||
void error (QNetworkReply::NetworkError code);
|
||||
void errorMessage (const QString& message);
|
||||
void executed (int64_t tag, const QString& result, tr_variant * args);
|
||||
|
||||
// private
|
||||
void responseReceived (TrVariantPtr json);
|
||||
void networkResponse (QNetworkReply::NetworkError code, const QString& message);
|
||||
|
||||
private:
|
||||
void sendRequest (TrVariantPtr json);
|
||||
RpcResponseFuture sendRequest (TrVariantPtr json);
|
||||
QNetworkAccessManager * networkAccessManager ();
|
||||
int64_t getNextTag ();
|
||||
|
||||
void sendNetworkRequest (TrVariantPtr json, const QFutureInterface<RpcResponse> &promise);
|
||||
void sendLocalRequest (TrVariantPtr json, const QFutureInterface<RpcResponse> &promise, int64_t tag);
|
||||
int64_t parseResponseTag (tr_variant& response);
|
||||
RpcResponse parseResponseData (tr_variant& response);
|
||||
|
||||
static void localSessionCallback (tr_session * s, tr_variant * response, void * vself);
|
||||
|
||||
private slots:
|
||||
void onFinished (QNetworkReply * reply);
|
||||
void parseResponse (TrVariantPtr json);
|
||||
void networkRequestFinished (QNetworkReply *reply);
|
||||
void localRequestFinished (TrVariantPtr response);
|
||||
|
||||
private:
|
||||
tr_session * mySession;
|
||||
QString mySessionId;
|
||||
QUrl myUrl;
|
||||
QNetworkAccessManager * myNAM;
|
||||
QHash<int64_t, QFutureInterface<RpcResponse>> myLocalRequests;
|
||||
int64_t myNextTag;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file Copyright (C) 2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "RpcQueue.h"
|
||||
|
||||
RpcQueue::RpcQueue (QObject * parent):
|
||||
QObject (parent),
|
||||
myTolerateErrors (false)
|
||||
{
|
||||
connect (&myFutureWatcher, SIGNAL (finished ()), SLOT (stepFinished ()));
|
||||
}
|
||||
|
||||
RpcResponseFuture
|
||||
RpcQueue::future ()
|
||||
{
|
||||
return myPromise.future ();
|
||||
}
|
||||
|
||||
void
|
||||
RpcQueue::stepFinished ()
|
||||
{
|
||||
RpcResponse result;
|
||||
|
||||
if (myFutureWatcher.future ().isResultReadyAt (0))
|
||||
{
|
||||
result = myFutureWatcher.result ();
|
||||
RpcResponseFuture future = myFutureWatcher.future ();
|
||||
|
||||
// we can't handle network errors, abort queue and pass the error upwards
|
||||
if (result.networkError != QNetworkReply::NoError)
|
||||
{
|
||||
assert (!result.success);
|
||||
|
||||
myPromise.reportFinished (&result);
|
||||
deleteLater ();
|
||||
return;
|
||||
}
|
||||
|
||||
// call user-handler for ordinary errors
|
||||
if (!result.success && myNextErrorHandler)
|
||||
{
|
||||
myNextErrorHandler (future);
|
||||
}
|
||||
|
||||
// run next request, if we have one to run and there was no error (or if we tolerate errors)
|
||||
if ((result.success || myTolerateErrors) && !myQueue.isEmpty ())
|
||||
{
|
||||
runNext (future);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (!myNextErrorHandler);
|
||||
assert (myQueue.isEmpty ());
|
||||
|
||||
// one way or another, the last step returned nothing.
|
||||
// assume it is OK and ensure that we're not going to give an empty response object to any of the next steps.
|
||||
result.success = true;
|
||||
}
|
||||
|
||||
myPromise.reportFinished (&result);
|
||||
deleteLater ();
|
||||
}
|
||||
|
||||
void
|
||||
RpcQueue::runNext (const RpcResponseFuture& response)
|
||||
{
|
||||
assert (!myQueue.isEmpty ());
|
||||
|
||||
auto next = myQueue.dequeue ();
|
||||
myNextErrorHandler = next.second;
|
||||
myFutureWatcher.setFuture ((next.first) (response));
|
||||
}
|
||||
|
||||
void
|
||||
RpcQueue::run ()
|
||||
{
|
||||
runNext (RpcResponseFuture ());
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file Copyright (C) 2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <QFutureInterface>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QQueue>
|
||||
|
||||
#include "RpcClient.h"
|
||||
|
||||
class RpcQueue: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RpcQueue (QObject * parent = nullptr);
|
||||
|
||||
void setTolerateErrors (bool tolerateErrors = true) { myTolerateErrors = tolerateErrors; }
|
||||
|
||||
template <typename Func>
|
||||
void add (Func func)
|
||||
{
|
||||
myQueue.enqueue (qMakePair (normalizeFunc (func),
|
||||
ErrorHandlerFunction ()));
|
||||
}
|
||||
|
||||
template <typename Func, typename ErrorHandler>
|
||||
void add (Func func, ErrorHandler errorHandler)
|
||||
{
|
||||
myQueue.enqueue (qMakePair (normalizeFunc (func),
|
||||
normalizeErrorHandler (errorHandler)));
|
||||
}
|
||||
|
||||
RpcResponseFuture future ();
|
||||
|
||||
// The first function in queue is ran synchronously
|
||||
// (hence it may be e. g. a lambda capturing local variables by reference).
|
||||
void run ();
|
||||
|
||||
private:
|
||||
// Internally queued function. Takes the last response future, makes a
|
||||
// request and returns a new response future.
|
||||
typedef std::function<RpcResponseFuture (const RpcResponseFuture&)> QueuedFunction;
|
||||
|
||||
// Internally stored error handler function. Takes the last response future and returns nothing.
|
||||
typedef std::function<void (const RpcResponseFuture&)> ErrorHandlerFunction;
|
||||
|
||||
private slots:
|
||||
void stepFinished ();
|
||||
|
||||
private:
|
||||
void runNext (const RpcResponseFuture& response);
|
||||
|
||||
// These overloads convert various forms of input closures to what we store internally.
|
||||
|
||||
// normal closure, takes response and returns new future
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func (const RpcResponse&)>::type, RpcResponseFuture>::value>::type * = nullptr>
|
||||
QueuedFunction normalizeFunc (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture& r) { return func (r.result ()); };
|
||||
}
|
||||
|
||||
// closure without argument (first step), takes nothing and returns new future
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func ()>::type, RpcResponseFuture>::value>::type * = nullptr>
|
||||
QueuedFunction normalizeFunc (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture&) { return func (); };
|
||||
}
|
||||
|
||||
// closure without return value ("auxiliary"), takes response and returns nothing -- internally we reuse the last future
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func (const RpcResponse&)>::type, void>::value>::type * = nullptr>
|
||||
QueuedFunction normalizeFunc (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture& r) { func (r.result ()); return r; };
|
||||
}
|
||||
|
||||
// closure without argument and return value, takes nothing and returns nothing -- next function will also get nothing
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func ()>::type, void>::value>::type * = nullptr>
|
||||
QueuedFunction normalizeFunc (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture& r) { func (); return r; };
|
||||
}
|
||||
|
||||
// normal error handler, takes last response
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func (const RpcResponse&)>::type, void>::value>::type * = nullptr>
|
||||
ErrorHandlerFunction normalizeErrorHandler (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture& r) { func (r.result ()); };
|
||||
}
|
||||
|
||||
// error handler without an argument, takes nothing
|
||||
template <typename Func,
|
||||
typename std::enable_if<std::is_same<typename std::result_of<Func ()>::type, void>::value>::type * = nullptr>
|
||||
ErrorHandlerFunction normalizeErrorHandler (const Func& func)
|
||||
{
|
||||
return [func] (const RpcResponseFuture& r) { func (); };
|
||||
}
|
||||
|
||||
private:
|
||||
bool myTolerateErrors;
|
||||
QFutureInterface<RpcResponse> myPromise;
|
||||
QQueue<QPair<QueuedFunction, ErrorHandlerFunction>> myQueue;
|
||||
ErrorHandlerFunction myNextErrorHandler;
|
||||
QFutureWatcher<RpcResponse> myFutureWatcher;
|
||||
};
|
||||
+242
-250
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2009-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -28,29 +28,12 @@
|
||||
|
||||
#include "AddData.h"
|
||||
#include "Prefs.h"
|
||||
#include "RpcQueue.h"
|
||||
#include "Session.h"
|
||||
#include "SessionDialog.h"
|
||||
#include "Torrent.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum
|
||||
{
|
||||
TAG_SOME_TORRENTS,
|
||||
TAG_ALL_TORRENTS,
|
||||
TAG_SESSION_STATS,
|
||||
TAG_SESSION_INFO,
|
||||
TAG_BLOCKLIST_UPDATE,
|
||||
TAG_ADD_TORRENT,
|
||||
TAG_PORT_TEST,
|
||||
TAG_MAGNET_LINK,
|
||||
TAG_RENAME_PATH,
|
||||
|
||||
FIRST_UNIQUE_TAG
|
||||
};
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
@@ -69,59 +52,14 @@ namespace
|
||||
for (const tr_quark key: keys)
|
||||
tr_variantListAddQuark (list, key);
|
||||
}
|
||||
|
||||
// If this object is passed as "ids" (compared by address), then recently active torrents are queried.
|
||||
const QSet<int> recentlyActiveIds = QSet<int>() << -1;
|
||||
|
||||
// If this object is passed as "ids" (compared by being empty), then all torrents are queried.
|
||||
const QSet<int> allIds;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void
|
||||
FileAdded::executed (int64_t tag, const QString& result, tr_variant * arguments)
|
||||
{
|
||||
if (tag != myTag)
|
||||
return;
|
||||
|
||||
if (result == QLatin1String ("success"))
|
||||
{
|
||||
tr_variant * dup;
|
||||
const char * str;
|
||||
if (tr_variantDictFindDict (arguments, TR_KEY_torrent_duplicate, &dup) &&
|
||||
tr_variantDictFindStr (dup, TR_KEY_name, &str, NULL))
|
||||
{
|
||||
const QString myFilename = QFileInfo (myName).fileName ();
|
||||
const QString name = QString::fromUtf8 (str);
|
||||
QMessageBox::warning (qApp->activeWindow (),
|
||||
tr ("Add Torrent"),
|
||||
tr ("<p><b>Unable to add \"%1\".</b></p><p>It is a duplicate of \"%2\" which is already added.</p>").arg (myFilename).arg (name));
|
||||
}
|
||||
|
||||
if (!myDelFile.isEmpty ())
|
||||
{
|
||||
QFile file (myDelFile);
|
||||
file.setPermissions (QFile::ReadOwner | QFile::WriteOwner);
|
||||
file.remove ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString text = result;
|
||||
|
||||
for (int i=0, n=text.size (); i<n; ++i)
|
||||
if (!i || text[i-1].isSpace ())
|
||||
text[i] = text[i].toUpper ();
|
||||
|
||||
QMessageBox::warning (qApp->activeWindow (),
|
||||
tr ("Error Adding Torrent"),
|
||||
QString::fromLatin1 ("<p><b>%1</b></p><p>%2</p>").arg (text).arg (myName));
|
||||
}
|
||||
|
||||
deleteLater ();
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void
|
||||
Session::sessionSet (const tr_quark key, const QVariant& value)
|
||||
{
|
||||
@@ -133,7 +71,7 @@ Session::sessionSet (const tr_quark key, const QVariant& value)
|
||||
case QVariant::Int: tr_variantDictAddInt (&args, key, value.toInt ()); break;
|
||||
case QVariant::Double: tr_variantDictAddReal (&args, key, value.toDouble ()); break;
|
||||
case QVariant::String: tr_variantDictAddStr (&args, key, value.toString ().toUtf8 ().constData ()); break;
|
||||
default: assert ("unknown type");
|
||||
default: assert (false);
|
||||
}
|
||||
|
||||
exec ("session-set", &args);
|
||||
@@ -142,7 +80,25 @@ Session::sessionSet (const tr_quark key, const QVariant& value)
|
||||
void
|
||||
Session::portTest ()
|
||||
{
|
||||
exec ("port-test", nullptr, TAG_PORT_TEST);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this] ()
|
||||
{
|
||||
return exec ("port-test", nullptr);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
bool isOpen = false;
|
||||
if (r.success)
|
||||
tr_variantDictFindBool (r.args.get (), TR_KEY_port_is_open, &isOpen);
|
||||
|
||||
emit portTested (isOpen);
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -153,7 +109,28 @@ Session::copyMagnetLinkToClipboard (int torrentId)
|
||||
tr_variantListAddInt (tr_variantDictAddList (&args, TR_KEY_ids, 1), torrentId);
|
||||
tr_variantListAddStr (tr_variantDictAddList (&args, TR_KEY_fields, 1), "magnetLink");
|
||||
|
||||
exec (TR_KEY_torrent_get, &args, TAG_MAGNET_LINK);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this, &args] ()
|
||||
{
|
||||
return exec (TR_KEY_torrent_get, &args);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
tr_variant * torrents;
|
||||
tr_variant * child;
|
||||
const char * str;
|
||||
|
||||
if (tr_variantDictFindList (r.args.get (), TR_KEY_torrents, &torrents)
|
||||
&& (child = tr_variantListChild (torrents, 0))
|
||||
&& tr_variantDictFindStr (child, TR_KEY_magnetLink, &str, NULL))
|
||||
qApp->clipboard ()->setText (QString::fromUtf8 (str));
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -277,7 +254,6 @@ Session::updatePref (int key)
|
||||
Session::Session (const QString& configDir, Prefs& prefs):
|
||||
myConfigDir (configDir),
|
||||
myPrefs (prefs),
|
||||
nextUniqueTag (FIRST_UNIQUE_TAG),
|
||||
myBlocklistSize (-1),
|
||||
mySession (0)
|
||||
{
|
||||
@@ -290,14 +266,10 @@ Session::Session (const QString& configDir, Prefs& prefs):
|
||||
myCumulativeStats = myStats;
|
||||
|
||||
connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (updatePref (int)));
|
||||
|
||||
connect (&myRpc, SIGNAL (executed (int64_t, QString, tr_variant *)), this, SLOT (responseReceived (int64_t, QString, tr_variant *)));
|
||||
|
||||
connect (&myRpc, SIGNAL (httpAuthenticationRequired ()), this, SIGNAL (httpAuthenticationRequired ()));
|
||||
connect (&myRpc, SIGNAL (dataReadProgress ()), this, SIGNAL (dataReadProgress ()));
|
||||
connect (&myRpc, SIGNAL (dataSendProgress ()), this, SIGNAL (dataSendProgress ()));
|
||||
connect (&myRpc, SIGNAL (error (QNetworkReply::NetworkError)), this, SIGNAL (error (QNetworkReply::NetworkError)));
|
||||
connect (&myRpc, SIGNAL (errorMessage (QString)), this, SIGNAL (errorMessage (QString)));
|
||||
connect (&myRpc, SIGNAL (networkResponse (QNetworkReply::NetworkError, QString)), this, SIGNAL (networkResponse (QNetworkReply::NetworkError, QString)));
|
||||
}
|
||||
|
||||
Session::~Session ()
|
||||
@@ -387,7 +359,11 @@ namespace
|
||||
void
|
||||
addOptionalIds (tr_variant * args, const QSet<int>& ids)
|
||||
{
|
||||
if (!ids.isEmpty ())
|
||||
if (&ids == &recentlyActiveIds)
|
||||
{
|
||||
tr_variantDictAddStr (args, TR_KEY_ids, "recently-active");
|
||||
}
|
||||
else if (!ids.isEmpty ())
|
||||
{
|
||||
tr_variant * idList (tr_variantDictAddList (args, TR_KEY_ids, ids.size ()));
|
||||
for (const int i: ids)
|
||||
@@ -439,7 +415,7 @@ Session::torrentSet (const QSet<int>& ids, const tr_quark key, const QStringList
|
||||
for (const QString& str: value)
|
||||
tr_variantListAddStr (list, str.toUtf8 ().constData ());
|
||||
|
||||
exec(TR_KEY_torrent_set, &args);
|
||||
exec (TR_KEY_torrent_set, &args);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -489,36 +465,81 @@ Session::torrentRenamePath (const QSet<int>& ids, const QString& oldpath, const
|
||||
tr_variantDictAddStr (&args, TR_KEY_path, oldpath.toUtf8 ().constData ());
|
||||
tr_variantDictAddStr (&args, TR_KEY_name, newname.toUtf8 ().constData ());
|
||||
|
||||
exec ("torrent-rename-path", &args, TAG_RENAME_PATH);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this, &args] ()
|
||||
{
|
||||
return exec ("torrent-rename-path", &args);
|
||||
},
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
const char * path = "(unknown)";
|
||||
const char * name = "(unknown)";
|
||||
tr_variantDictFindStr (r.args.get (), TR_KEY_path, &path, nullptr);
|
||||
tr_variantDictFindStr (r.args.get (), TR_KEY_name, &name, nullptr);
|
||||
|
||||
QMessageBox * d = new QMessageBox (QMessageBox::Information,
|
||||
tr ("Error Renaming Path"),
|
||||
tr ("<p><b>Unable to rename \"%1\" as \"%2\": %3.</b></p> "
|
||||
"<p>Please correct the errors and try again.</p>")
|
||||
.arg (QString::fromUtf8 (path))
|
||||
.arg (QString::fromUtf8 (name))
|
||||
.arg (r.result),
|
||||
QMessageBox::Close,
|
||||
qApp->activeWindow ());
|
||||
connect (d, SIGNAL (rejected ()), d, SLOT (deleteLater ()));
|
||||
d->show ();
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
int64_t id = 0;
|
||||
|
||||
if (tr_variantDictFindInt (r.args.get (), TR_KEY_id, &id)
|
||||
&& id != 0)
|
||||
refreshTorrents (QSet<int> () << id,
|
||||
KeyList () << TR_KEY_fileStats << TR_KEY_files << TR_KEY_id << TR_KEY_name);
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::refreshTorrents (const QSet<int>& ids)
|
||||
Session::refreshTorrents (const QSet<int>& ids, const KeyList& keys)
|
||||
{
|
||||
if (ids.empty ())
|
||||
{
|
||||
refreshAllTorrents ();
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 2);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys ());
|
||||
addOptionalIds (&args, ids);
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 2);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), keys);
|
||||
addOptionalIds (&args, ids);
|
||||
|
||||
exec (TR_KEY_torrent_get, &args, TAG_SOME_TORRENTS);
|
||||
}
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this, &args] ()
|
||||
{
|
||||
return exec (TR_KEY_torrent_get, &args);
|
||||
});
|
||||
|
||||
const bool allTorrents = ids.empty ();
|
||||
q->add (
|
||||
[this, allTorrents] (const RpcResponse& r)
|
||||
{
|
||||
tr_variant * torrents;
|
||||
if (tr_variantDictFindList (r.args.get (), TR_KEY_torrents, &torrents))
|
||||
emit torrentsUpdated (torrents, allTorrents);
|
||||
if (tr_variantDictFindList (r.args.get (), TR_KEY_removed, &torrents))
|
||||
emit torrentsRemoved (torrents);
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::refreshExtraStats (const QSet<int>& ids)
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 3);
|
||||
addOptionalIds (&args, ids);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys () + getExtraStatKeys ());
|
||||
|
||||
exec (TR_KEY_torrent_get, &args, TAG_SOME_TORRENTS);
|
||||
refreshTorrents (ids, getStatKeys () + getExtraStatKeys ());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -528,9 +549,21 @@ Session::sendTorrentRequest (const char * request, const QSet<int>& ids)
|
||||
tr_variantInitDict (&args, 1);
|
||||
addOptionalIds (&args, ids);
|
||||
|
||||
exec (request, &args);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
refreshTorrents (ids);
|
||||
q->add (
|
||||
[this, request, &args] ()
|
||||
{
|
||||
return exec (request, &args);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this, ids] ()
|
||||
{
|
||||
refreshTorrents (ids, getStatKeys ());
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void Session::pauseTorrents (const QSet<int>& ids) { sendTorrentRequest ("torrent-stop", ids); }
|
||||
@@ -544,181 +577,97 @@ void Session::queueMoveBottom (const QSet<int>& ids) { sendTorrentRequest ("que
|
||||
void
|
||||
Session::refreshActiveTorrents ()
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 2);
|
||||
tr_variantDictAddStr (&args, TR_KEY_ids, "recently-active");
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys ());
|
||||
|
||||
exec (TR_KEY_torrent_get, &args, TAG_SOME_TORRENTS);
|
||||
refreshTorrents (recentlyActiveIds, getStatKeys ());
|
||||
}
|
||||
|
||||
void
|
||||
Session::refreshAllTorrents ()
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 1);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys ());
|
||||
|
||||
exec (TR_KEY_torrent_get, &args, TAG_ALL_TORRENTS);
|
||||
refreshTorrents (allIds, getStatKeys ());
|
||||
}
|
||||
|
||||
void
|
||||
Session::initTorrents (const QSet<int>& ids)
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 2);
|
||||
addOptionalIds (&args, ids);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys ()+getInfoKeys ());
|
||||
|
||||
exec ("torrent-get", &args, ids.isEmpty () ? TAG_ALL_TORRENTS : TAG_SOME_TORRENTS);
|
||||
refreshTorrents (ids, getStatKeys () + getInfoKeys ());
|
||||
}
|
||||
|
||||
void
|
||||
Session::refreshSessionStats ()
|
||||
{
|
||||
exec ("session-stats", nullptr, TAG_SESSION_STATS);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this] ()
|
||||
{
|
||||
return exec ("session-stats", nullptr);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
updateStats (r.args.get ());
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::refreshSessionInfo ()
|
||||
{
|
||||
exec ("session-get", nullptr, TAG_SESSION_INFO);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this] ()
|
||||
{
|
||||
return exec ("session-get", nullptr);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
updateInfo (r.args.get ());
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::updateBlocklist ()
|
||||
{
|
||||
exec ("blocklist-update", nullptr, TAG_BLOCKLIST_UPDATE);
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this] ()
|
||||
{
|
||||
return exec ("blocklist-update", nullptr);
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this] (const RpcResponse& r)
|
||||
{
|
||||
int64_t blocklistSize;
|
||||
if (tr_variantDictFindInt (r.args.get (), TR_KEY_blocklist_size, &blocklistSize))
|
||||
setBlocklistSize (blocklistSize);
|
||||
});
|
||||
|
||||
q->run ();
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void
|
||||
Session::exec (tr_quark method, tr_variant * args, int64_t tag)
|
||||
RpcResponseFuture
|
||||
Session::exec (tr_quark method, tr_variant * args)
|
||||
{
|
||||
myRpc.exec (method, args, tag);
|
||||
return myRpc.exec (method, args);
|
||||
}
|
||||
|
||||
void
|
||||
Session::exec (const char* method, tr_variant * args, int64_t tag)
|
||||
RpcResponseFuture
|
||||
Session::exec (const char * method, tr_variant * args)
|
||||
{
|
||||
myRpc.exec (method, args, tag);
|
||||
}
|
||||
|
||||
void
|
||||
Session::responseReceived (int64_t tag, const QString& result, tr_variant * args)
|
||||
{
|
||||
emit executed (tag, result, args);
|
||||
|
||||
if (tag < 0)
|
||||
return;
|
||||
|
||||
switch (tag)
|
||||
{
|
||||
case TAG_SOME_TORRENTS:
|
||||
case TAG_ALL_TORRENTS:
|
||||
if (args != nullptr)
|
||||
{
|
||||
tr_variant * torrents;
|
||||
if (tr_variantDictFindList (args, TR_KEY_torrents, &torrents))
|
||||
emit torrentsUpdated (torrents, tag==TAG_ALL_TORRENTS);
|
||||
if (tr_variantDictFindList (args, TR_KEY_removed, &torrents))
|
||||
emit torrentsRemoved (torrents);
|
||||
}
|
||||
break;
|
||||
|
||||
case TAG_SESSION_STATS:
|
||||
if (args != nullptr)
|
||||
updateStats (args);
|
||||
break;
|
||||
|
||||
case TAG_SESSION_INFO:
|
||||
if (args != nullptr)
|
||||
updateInfo (args);
|
||||
break;
|
||||
|
||||
case TAG_BLOCKLIST_UPDATE:
|
||||
{
|
||||
int64_t intVal = 0;
|
||||
if (args != nullptr)
|
||||
{
|
||||
if (tr_variantDictFindInt (args, TR_KEY_blocklist_size, &intVal))
|
||||
setBlocklistSize (intVal);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TAG_RENAME_PATH:
|
||||
{
|
||||
int64_t id = 0;
|
||||
if (result != QLatin1String ("success"))
|
||||
{
|
||||
const char * path = "";
|
||||
const char * name = "";
|
||||
tr_variantDictFindStr (args, TR_KEY_path, &path, 0);
|
||||
tr_variantDictFindStr (args, TR_KEY_name, &name, 0);
|
||||
const QString title = tr ("Error Renaming Path");
|
||||
const QString text = tr ("<p><b>Unable to rename \"%1\" as \"%2\": %3.</b></p> <p>Please correct the errors and try again.</p>").arg (QString::fromUtf8 (path)).arg (QString::fromUtf8 (name)).arg (result);
|
||||
QMessageBox * d = new QMessageBox (QMessageBox::Information, title, text,
|
||||
QMessageBox::Close,
|
||||
qApp->activeWindow ());
|
||||
connect (d, SIGNAL (rejected ()), d, SLOT (deleteLater ()));
|
||||
d->show ();
|
||||
}
|
||||
else if (tr_variantDictFindInt (args, TR_KEY_id, &id) && id)
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict (&args, 2);
|
||||
tr_variantDictAddInt (&args, TR_KEY_ids, id);
|
||||
addList (tr_variantDictAddList (&args, TR_KEY_fields, 0),
|
||||
KeyList () << TR_KEY_fileStats << TR_KEY_files << TR_KEY_id << TR_KEY_name);
|
||||
exec ("torrent-get", &args, TAG_SOME_TORRENTS);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TAG_PORT_TEST:
|
||||
{
|
||||
bool isOpen;
|
||||
if (args == nullptr ||
|
||||
!tr_variantDictFindBool (args, TR_KEY_port_is_open, &isOpen))
|
||||
isOpen = false;
|
||||
emit portTested (isOpen);
|
||||
break;
|
||||
}
|
||||
|
||||
case TAG_MAGNET_LINK:
|
||||
{
|
||||
tr_variant * torrents;
|
||||
tr_variant * child;
|
||||
const char * str;
|
||||
if (args != nullptr
|
||||
&& tr_variantDictFindList (args, TR_KEY_torrents, &torrents)
|
||||
&& ( (child = tr_variantListChild (torrents, 0)))
|
||||
&& tr_variantDictFindStr (child, TR_KEY_magnetLink, &str, NULL))
|
||||
qApp->clipboard ()->setText (QString::fromUtf8 (str));
|
||||
break;
|
||||
}
|
||||
|
||||
case TAG_ADD_TORRENT:
|
||||
{
|
||||
const char * str = "";
|
||||
if (result != QLatin1String ("success"))
|
||||
{
|
||||
QMessageBox * d = new QMessageBox (QMessageBox::Information,
|
||||
tr ("Add Torrent"),
|
||||
QString::fromUtf8 (str),
|
||||
QMessageBox::Close,
|
||||
qApp->activeWindow ());
|
||||
connect (d, SIGNAL (rejected ()), d, SLOT (deleteLater ()));
|
||||
d->show ();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return myRpc.exec (method, args);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -892,16 +841,59 @@ Session::addTorrent (const AddData& addMe, tr_variant * args, bool trashOriginal
|
||||
break;
|
||||
}
|
||||
|
||||
const int64_t tag = getUniqueTag ();
|
||||
RpcQueue * q = new RpcQueue ();
|
||||
|
||||
q->add (
|
||||
[this, args] ()
|
||||
{
|
||||
return exec ("torrent-add", args);
|
||||
},
|
||||
[this, addMe] (const RpcResponse& r)
|
||||
{
|
||||
QMessageBox * d = new QMessageBox (QMessageBox::Warning,
|
||||
tr ("Error Adding Torrent"),
|
||||
QString::fromLatin1 ("<p><b>%1</b></p><p>%2</p>")
|
||||
.arg (r.result)
|
||||
.arg (addMe.readableName ()),
|
||||
QMessageBox::Close,
|
||||
qApp->activeWindow ());
|
||||
connect (d, SIGNAL (rejected ()), d, SLOT (deleteLater ()));
|
||||
d->show ();
|
||||
});
|
||||
|
||||
q->add (
|
||||
[this, addMe] (const RpcResponse& r)
|
||||
{
|
||||
tr_variant * dup;
|
||||
const char * str;
|
||||
|
||||
if (tr_variantDictFindDict (r.args.get (), TR_KEY_torrent_duplicate, &dup) &&
|
||||
tr_variantDictFindStr (dup, TR_KEY_name, &str, NULL))
|
||||
{
|
||||
const QString name = QString::fromUtf8 (str);
|
||||
QMessageBox * d = new QMessageBox (QMessageBox::Warning,
|
||||
tr ("Add Torrent"),
|
||||
tr ("<p><b>Unable to add \"%1\".</b></p>"
|
||||
"<p>It is a duplicate of \"%2\" which is already added.</p>")
|
||||
.arg (addMe.readableShortName ())
|
||||
.arg (name),
|
||||
QMessageBox::Close,
|
||||
qApp->activeWindow ());
|
||||
connect (d, SIGNAL (rejected ()), d, SLOT (deleteLater ()));
|
||||
d->show ();
|
||||
}
|
||||
});
|
||||
|
||||
// maybe delete the source .torrent
|
||||
FileAdded * fileAdded = new FileAdded (tag, addMe.readableName ());
|
||||
if (trashOriginal && addMe.type == AddData::FILENAME)
|
||||
fileAdded->setFileToDelete (addMe.filename);
|
||||
connect (this, SIGNAL (executed (int64_t, QString, tr_variant *)),
|
||||
fileAdded, SLOT (executed (int64_t, QString, tr_variant *)));
|
||||
q->add (
|
||||
[this, addMe] ()
|
||||
{
|
||||
QFile original (addMe.filename);
|
||||
original.setPermissions (QFile::ReadOwner | QFile::WriteOwner);
|
||||
original.remove ();
|
||||
});
|
||||
|
||||
exec ("torrent-add", args, tag);
|
||||
q->run ();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+6
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
||||
* This file Copyright (C) 2009-2016 Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <libtransmission/quark.h>
|
||||
|
||||
#include "RpcClient.h"
|
||||
#include "Torrent.h"
|
||||
|
||||
class AddData;
|
||||
class Prefs;
|
||||
@@ -27,26 +28,6 @@ extern "C"
|
||||
struct tr_variant;
|
||||
}
|
||||
|
||||
class FileAdded: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FileAdded (int64_t tag, const QString& name): myTag (tag), myName (name) {}
|
||||
virtual ~FileAdded () {}
|
||||
|
||||
void setFileToDelete (const QString& file) { myDelFile = file; }
|
||||
|
||||
public slots:
|
||||
void executed (int64_t tag, const QString& result, tr_variant * arguments);
|
||||
|
||||
private:
|
||||
const int64_t myTag;
|
||||
const QString myName;
|
||||
|
||||
QString myDelFile;
|
||||
};
|
||||
|
||||
class Session: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -75,10 +56,8 @@ class Session: public QObject
|
||||
/** returns true if isServer () is true or if the remote address is the localhost */
|
||||
bool isLocal () const;
|
||||
|
||||
void exec (tr_quark method, tr_variant * args, int64_t tag = -1);
|
||||
void exec (const char * method, tr_variant * args, int64_t tag = -1);
|
||||
|
||||
int64_t getUniqueTag () { return nextUniqueTag++; }
|
||||
RpcResponseFuture exec (tr_quark method, tr_variant * args);
|
||||
RpcResponseFuture exec (const char * method, tr_variant * args);
|
||||
|
||||
void torrentSet (const QSet<int>& ids, const tr_quark key, bool val);
|
||||
void torrentSet (const QSet<int>& ids, const tr_quark key, int val);
|
||||
@@ -115,7 +94,6 @@ class Session: public QObject
|
||||
void refreshExtraStats (const QSet<int>& ids);
|
||||
|
||||
signals:
|
||||
void executed (int64_t tag, const QString& result, tr_variant * arguments);
|
||||
void sourceChanged ();
|
||||
void portTested (bool isOpen);
|
||||
void statsUpdated ();
|
||||
@@ -125,8 +103,7 @@ class Session: public QObject
|
||||
void torrentsRemoved (tr_variant * torrentList);
|
||||
void dataReadProgress ();
|
||||
void dataSendProgress ();
|
||||
void error (QNetworkReply::NetworkError);
|
||||
void errorMessage (const QString&);
|
||||
void networkResponse (QNetworkReply::NetworkError code, const QString& message);
|
||||
void httpAuthenticationRequired ();
|
||||
|
||||
private:
|
||||
@@ -138,18 +115,14 @@ class Session: public QObject
|
||||
void sessionSet (const tr_quark key, const QVariant& variant);
|
||||
void pumpRequests ();
|
||||
void sendTorrentRequest (const char * request, const QSet<int>& torrentIds);
|
||||
void refreshTorrents (const QSet<int>& torrentIds);
|
||||
void refreshTorrents (const QSet<int>& torrentIds, const Torrent::KeyList& keys);
|
||||
|
||||
static void updateStats (tr_variant * d, tr_session_stats * stats);
|
||||
|
||||
private slots:
|
||||
void responseReceived (int64_t tag, const QString& result, tr_variant * args);
|
||||
|
||||
private:
|
||||
QString const myConfigDir;
|
||||
Prefs& myPrefs;
|
||||
|
||||
int64_t nextUniqueTag;
|
||||
int64_t myBlocklistSize;
|
||||
tr_session * mySession;
|
||||
QStringList myIdleJSON;
|
||||
|
||||
@@ -100,6 +100,7 @@ SOURCES += AboutDialog.cc \
|
||||
PrefsDialog.cc \
|
||||
RelocateDialog.cc \
|
||||
RpcClient.cc \
|
||||
RpcQueue.cc \
|
||||
Session.cc \
|
||||
SessionDialog.cc \
|
||||
SqueezeLabel.cc \
|
||||
|
||||
Reference in New Issue
Block a user