From 2b917de65b378fe458c2c6a9d77dcbed89b056cc Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Tue, 19 Apr 2016 20:41:59 +0000 Subject: [PATCH] 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 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. --- qt/AddData.cc | 18 +- qt/AddData.h | 3 +- qt/CMakeLists.txt | 2 + qt/FreeSpaceLabel.cc | 67 +++--- qt/FreeSpaceLabel.h | 6 +- qt/MainWindow.cc | 14 +- qt/MainWindow.h | 5 +- qt/RpcClient.cc | 188 +++++++++++------ qt/RpcClient.h | 43 ++-- qt/RpcQueue.cc | 88 ++++++++ qt/RpcQueue.h | 122 +++++++++++ qt/Session.cc | 484 +++++++++++++++++++++---------------------- qt/Session.h | 39 +--- qt/qtr.pro | 1 + 14 files changed, 672 insertions(+), 408 deletions(-) create mode 100644 qt/RpcQueue.cc create mode 100644 qt/RpcQueue.h diff --git a/qt/AddData.cc b/qt/AddData.cc index cd3f82274..14fe95815 100644 --- a/qt/AddData.cc +++ b/qt/AddData.cc @@ -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 (); + } +} diff --git a/qt/AddData.h b/qt/AddData.h index 0388a6b1a..734507c38 100644 --- a/qt/AddData.h +++ b/qt/AddData.h @@ -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; } diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index ec22f53fc..650f218b4 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -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 diff --git a/qt/FreeSpaceLabel.cc b/qt/FreeSpaceLabel.cc index 70784d6b2..db128bc46 100644 --- a/qt/FreeSpaceLabel.cc +++ b/qt/FreeSpaceLabel.cc @@ -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); + RpcQueue * q = new RpcQueue (); - 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)); + q->add ( + [this, &args] () + { + return mySession->exec ("free-space", &args); + }); - myTimer.start (); + 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 (); } diff --git a/qt/FreeSpaceLabel.h b/qt/FreeSpaceLabel.h index df6cbe76d..17d960279 100644 --- a/qt/FreeSpaceLabel.h +++ b/qt/FreeSpaceLabel.h @@ -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 - #include #include #include @@ -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; }; diff --git a/qt/MainWindow.cc b/qt/MainWindow.cc index 72dfe11c5..5562e088b 100644 --- a/qt/MainWindow.cc +++ b/qt/MainWindow.cc @@ -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 () { diff --git a/qt/MainWindow.h b/qt/MainWindow.h index 77376a03a..3189da3d4 100644 --- a/qt/MainWindow.h +++ b/qt/MainWindow.h @@ -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); diff --git a/qt/RpcClient.cc b/qt/RpcClient.cc index e1985e93b..cadc4803c 100644 --- a/qt/RpcClient.cc +++ b/qt/RpcClient.cc @@ -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 #include #include @@ -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"); - - 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 &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)); + reply->setProperty (REQUEST_FUTUREINTERFACE_PROPERTY_KEY, QVariant::fromValue (promise)); - 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))); + 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 &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 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 promise = reply->property (REQUEST_FUTUREINTERFACE_PROPERTY_KEY).value> (); + #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 ()); + + sendNetworkRequest (reply->property (REQUEST_DATA_PROPERTY_KEY).value (), 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 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; } diff --git a/qt/RpcClient.h b/qt/RpcClient.h index 9dd349e05..58a06d8db 100644 --- a/qt/RpcClient.h +++ b/qt/RpcClient.h @@ -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 +#include +#include +#include #include #include #include @@ -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); + +// The response future -- the RPC engine returns one for each request made. +typedef QFuture 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 &promise); + void sendLocalRequest (TrVariantPtr json, const QFutureInterface &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> myLocalRequests; + int64_t myNextTag; }; diff --git a/qt/RpcQueue.cc b/qt/RpcQueue.cc new file mode 100644 index 000000000..522b940e0 --- /dev/null +++ b/qt/RpcQueue.cc @@ -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 + +#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 ()); +} diff --git a/qt/RpcQueue.h b/qt/RpcQueue.h new file mode 100644 index 000000000..f6d6c8d1e --- /dev/null +++ b/qt/RpcQueue.h @@ -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 +#include + +#include +#include +#include +#include +#include + +#include "RpcClient.h" + +class RpcQueue: public QObject +{ + Q_OBJECT + + public: + explicit RpcQueue (QObject * parent = nullptr); + + void setTolerateErrors (bool tolerateErrors = true) { myTolerateErrors = tolerateErrors; } + + template + void add (Func func) + { + myQueue.enqueue (qMakePair (normalizeFunc (func), + ErrorHandlerFunction ())); + } + + template + 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 QueuedFunction; + + // Internally stored error handler function. Takes the last response future and returns nothing. + typedef std::function 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 ::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 ::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 ::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 ::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 ::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 ::type, void>::value>::type * = nullptr> + ErrorHandlerFunction normalizeErrorHandler (const Func& func) + { + return [func] (const RpcResponseFuture& r) { func (); }; + } + + private: + bool myTolerateErrors; + QFutureInterface myPromise; + QQueue> myQueue; + ErrorHandlerFunction myNextErrorHandler; + QFutureWatcher myFutureWatcher; +}; diff --git a/qt/Session.cc b/qt/Session.cc index 52d928a70..3db0fa484 100644 --- a/qt/Session.cc +++ b/qt/Session.cc @@ -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); } -} - -/*** -**** -***/ - -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 ("

Unable to add \"%1\".

It is a duplicate of \"%2\" which is already added.

").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 (); iactiveWindow (), - tr ("Error Adding Torrent"), - QString::fromLatin1 ("

%1

%2

").arg (text).arg (myName)); - } + // If this object is passed as "ids" (compared by address), then recently active torrents are queried. + const QSet recentlyActiveIds = QSet() << -1; - deleteLater (); + // If this object is passed as "ids" (compared by being empty), then all torrents are queried. + const QSet allIds; } -/*** -**** -***/ - 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& 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& 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& 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 ("

Unable to rename \"%1\" as \"%2\": %3.

" + "

Please correct the errors and try again.

") + .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 () << id, + KeyList () << TR_KEY_fileStats << TR_KEY_files << TR_KEY_id << TR_KEY_name); + }); + + q->run (); } void -Session::refreshTorrents (const QSet& ids) +Session::refreshTorrents (const QSet& ids, const KeyList& keys) { - if (ids.empty ()) + tr_variant args; + tr_variantInitDict (&args, 2); + addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), keys); + addOptionalIds (&args, ids); + + RpcQueue * q = new RpcQueue (); + + q->add ( + [this, &args] () { - refreshAllTorrents (); - } - else + return exec (TR_KEY_torrent_get, &args); + }); + + const bool allTorrents = ids.empty (); + q->add ( + [this, allTorrents] (const RpcResponse& r) { - tr_variant args; - tr_variantInitDict (&args, 2); - addList (tr_variantDictAddList (&args, TR_KEY_fields, 0), getStatKeys ()); - addOptionalIds (&args, ids); + 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); + }); - exec (TR_KEY_torrent_get, &args, TAG_SOME_TORRENTS); - } + q->run (); } void Session::refreshExtraStats (const QSet& 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& ids) tr_variantInitDict (&args, 1); addOptionalIds (&args, ids); - exec (request, &args); + RpcQueue * q = new RpcQueue (); + + q->add ( + [this, request, &args] () + { + return exec (request, &args); + }); + + q->add ( + [this, ids] () + { + refreshTorrents (ids, getStatKeys ()); + }); - refreshTorrents (ids); + q->run (); } void Session::pauseTorrents (const QSet& ids) { sendTorrentRequest ("torrent-stop", ids); } @@ -544,181 +577,97 @@ void Session::queueMoveBottom (const QSet& 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& 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 ("

Unable to rename \"%1\" as \"%2\": %3.

Please correct the errors and try again.

").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 ("

%1

%2

") + .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 ("

Unable to add \"%1\".

" + "

It is a duplicate of \"%2\" which is already added.

") + .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 diff --git a/qt/Session.h b/qt/Session.h index 08a1caa11..bbeec3dc9 100644 --- a/qt/Session.h +++ b/qt/Session.h @@ -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 #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& ids, const tr_quark key, bool val); void torrentSet (const QSet& ids, const tr_quark key, int val); @@ -115,7 +94,6 @@ class Session: public QObject void refreshExtraStats (const QSet& 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& torrentIds); - void refreshTorrents (const QSet& torrentIds); + void refreshTorrents (const QSet& 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; diff --git a/qt/qtr.pro b/qt/qtr.pro index 85fcd8b43..4598997f0 100644 --- a/qt/qtr.pro +++ b/qt/qtr.pro @@ -100,6 +100,7 @@ SOURCES += AboutDialog.cc \ PrefsDialog.cc \ RelocateDialog.cc \ RpcClient.cc \ + RpcQueue.cc \ Session.cc \ SessionDialog.cc \ SqueezeLabel.cc \ -- 2.40.0