From cc7d034fa67196ad4950d3785aff64e4c5765855 Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Wed, 30 Jan 2019 19:30:07 +0300 Subject: Connection: infinite sync loop logic by default --- lib/connection.cpp | 13 ++++++++++++- lib/connection.h | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/connection.cpp b/lib/connection.cpp index c582cf94..982145f7 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -133,6 +133,8 @@ Connection::Connection(const QUrl& server, QObject* parent) , d(std::make_unique(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line + // sync loop: + connect(this, &Connection::syncDone, this, &Connection::getNewEvents); } Connection::Connection(QObject* parent) @@ -250,7 +252,7 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); - + q->sync(); // initial sync after connection } void Connection::checkAndConnect(const QString& userId, @@ -406,6 +408,15 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } } +void Connection::getNewEvents() +{ + // Borrowed the logic from Quiark's code in Tensor + // to cache not too aggressively and not on the first sync. + if (++_saveStateCounter % 17 == 2) + saveState(); + sync(30*1000); +} + void Connection::stopSync() { if (d->syncJob) diff --git a/lib/connection.h b/lib/connection.h index 9e4121f4..dcc77824 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -678,6 +678,7 @@ namespace QMatrixClient * Completes loading sync data. */ void onSyncSuccess(SyncData &&data, bool fromCache = false); + void getNewEvents(); private: class Private; @@ -702,6 +703,8 @@ namespace QMatrixClient static room_factory_t _roomFactory; static user_factory_t _userFactory; + + int _saveStateCounter = 0; }; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Connection*) -- cgit v1.2.3 From 22dd5f1e8988b03a691487cdad164a82a36e7f8c Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sat, 2 Feb 2019 23:51:20 +0300 Subject: Connection: separated sync loop logic with delay control Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++------- lib/connection.h | 30 +++++++++++++++++++++++- 2 files changed, 90 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/connection.cpp b/lib/connection.cpp index 982145f7..e7f9e4b2 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -45,6 +45,7 @@ #include #include #include +#include using namespace QMatrixClient; @@ -133,8 +134,6 @@ Connection::Connection(const QUrl& server, QObject* parent) , d(std::make_unique(std::make_unique(server))) { d->q = this; // All d initialization should occur before this line - // sync loop: - connect(this, &Connection::syncDone, this, &Connection::getNewEvents); } Connection::Connection(QObject* parent) @@ -232,6 +231,11 @@ void Connection::doConnectToServer(const QString& user, const QString& password, }); } +void Connection::syncLoopIteration() +{ + sync(_syncLoopTimeout); +} + void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) @@ -252,7 +256,6 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); - q->sync(); // initial sync after connection } void Connection::checkAndConnect(const QString& userId, @@ -321,6 +324,15 @@ void Connection::sync(int timeout) }); } +void Connection::syncLoop(int timeout) +{ + _syncLoopTimeout = timeout; + connect(this, &Connection::syncDone, this, &Connection::getNewEventsOnSyncDone); + connect(this, &Connection::syncError, this, &Connection::getNewEventsOnSyncError); + _syncLoopElapsedTimer.start(); + sync(_syncLoopTimeout); // initial sync to start the loop +} + void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); for (auto&& roomData: data.takeRoomData()) @@ -410,11 +422,33 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { void Connection::getNewEvents() { - // Borrowed the logic from Quiark's code in Tensor - // to cache not too aggressively and not on the first sync. - if (++_saveStateCounter % 17 == 2) - saveState(); - sync(30*1000); + int delay = minSyncLoopDelayMs() - _syncLoopElapsedTimer.restart(); + if (delay<0) { + delay = 0; + } + QTimer::singleShot(delay, this, &Connection::syncLoopIteration); +} + +void Connection::getNewEventsOnSyncDone() +{ + if (_prevSyncLoopIterationDone) { + _syncLoopAttemptNumber++; + } else { + _syncLoopAttemptNumber = 0; + } + emit syncAttemptNumberChanged(_syncLoopAttemptNumber); + getNewEvents(); +} + +void Connection::getNewEventsOnSyncError() +{ + if (_prevSyncLoopIterationDone) { + _syncLoopAttemptNumber = 0; + } else { + _syncLoopAttemptNumber++; + } + emit syncAttemptNumberChanged(_syncLoopAttemptNumber); + getNewEvents(); } void Connection::stopSync() @@ -436,6 +470,15 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const return callApi(room->id(), "m.read", event->id()); } +void Connection::setMinSyncDelayMs(qint64 minSyncDelayMs) +{ + if (_minSyncLoopDelayMs == minSyncDelayMs) + return; + + _minSyncLoopDelayMs = minSyncDelayMs; + emit minSyncDelayMsChanged(_minSyncLoopDelayMs); +} + JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1100,6 +1143,16 @@ user_factory_t Connection::userFactory() return _userFactory; } +qint64 Connection::minSyncLoopDelayMs() const +{ + return _minSyncLoopDelayMs; +} + +uint Connection::syncLoopAttemptNumber() const +{ + return _syncLoopAttemptNumber; +} + room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); user_factory_t Connection::_userFactory = defaultUserFactory<>(); diff --git a/lib/connection.h b/lib/connection.h index dcc77824..ee7ad243 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -105,6 +106,8 @@ namespace QMatrixClient Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) + Q_PROPERTY(qint64 minSyncLoopDelayMs READ minSyncLoopDelayMs WRITE setMinSyncDelayMs NOTIFY minSyncDelayMsChanged) + Q_PROPERTY(uint syncLoopAttemptNumber READ syncLoopAttemptNumber NOTIFY syncAttemptNumberChanged) public: // Room ids, rather than room pointers, are used in the direct chat @@ -353,6 +356,11 @@ namespace QMatrixClient template static void setUserType() { setUserFactory(defaultUserFactory()); } + qint64 minSyncLoopDelayMs() const; + void setMinSyncDelayMs(qint64 minSyncLoopDelayMs); + + uint syncLoopAttemptNumber() const; + public slots: /** Set the homeserver base URL */ void setHomeserver(const QUrl& baseUrl); @@ -371,6 +379,15 @@ namespace QMatrixClient void logout(); void sync(int timeout = -1); + + /** Start sync loop with the minSyncLoopDelayMs value + where minSyncLoopDelayMs could be changed on the client + according to syncDone/syncError signals and + the syncLoopAttemptNumber counter. + The syncLoopAttemptNumber counter is resetting + after non-repeating syncDone/syncError events*/ + void syncLoop(int timeout = -1); + void stopSync(); QString nextBatchToken() const; @@ -645,6 +662,8 @@ namespace QMatrixClient void cacheStateChanged(); void lazyLoadingChanged(); void turnServersChanged(const QJsonObject& servers); + void minSyncDelayMsChanged(qint64 minSyncLoopDelayMs); + void syncAttemptNumberChanged(uint syncLoopAttemptNumber); protected: /** @@ -678,7 +697,11 @@ namespace QMatrixClient * Completes loading sync data. */ void onSyncSuccess(SyncData &&data, bool fromCache = false); + + protected slots: void getNewEvents(); + void getNewEventsOnSyncDone(); + void getNewEventsOnSyncError(); private: class Private; @@ -704,7 +727,12 @@ namespace QMatrixClient static room_factory_t _roomFactory; static user_factory_t _userFactory; - int _saveStateCounter = 0; + QElapsedTimer _syncLoopElapsedTimer; + int _syncLoopTimeout = -1; + qint64 _minSyncLoopDelayMs = 0; + void syncLoopIteration(); + uint _syncLoopAttemptNumber = 0; + bool _prevSyncLoopIterationDone = false; }; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Connection*) -- cgit v1.2.3 From bf6cd3d29052f9e79fee29c6a6646d95271a196a Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Mon, 4 Feb 2019 11:58:43 +0300 Subject: Connection: simplified sync loop logic without delays Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 58 +++--------------------------------------------------- lib/connection.h | 26 +----------------------- 2 files changed, 4 insertions(+), 80 deletions(-) (limited to 'lib') diff --git a/lib/connection.cpp b/lib/connection.cpp index e7f9e4b2..a9a8bba3 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -45,7 +45,6 @@ #include #include #include -#include using namespace QMatrixClient; @@ -256,6 +255,7 @@ void Connection::Private::connectWithToken(const QString& user, << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); emit q->connected(); + } void Connection::checkAndConnect(const QString& userId, @@ -327,10 +327,8 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { _syncLoopTimeout = timeout; - connect(this, &Connection::syncDone, this, &Connection::getNewEventsOnSyncDone); - connect(this, &Connection::syncError, this, &Connection::getNewEventsOnSyncError); - _syncLoopElapsedTimer.start(); - sync(_syncLoopTimeout); // initial sync to start the loop + connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration); + syncLoopIteration(); // initial sync to start the loop } void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { @@ -420,37 +418,6 @@ void Connection::onSyncSuccess(SyncData &&data, bool fromCache) { } } -void Connection::getNewEvents() -{ - int delay = minSyncLoopDelayMs() - _syncLoopElapsedTimer.restart(); - if (delay<0) { - delay = 0; - } - QTimer::singleShot(delay, this, &Connection::syncLoopIteration); -} - -void Connection::getNewEventsOnSyncDone() -{ - if (_prevSyncLoopIterationDone) { - _syncLoopAttemptNumber++; - } else { - _syncLoopAttemptNumber = 0; - } - emit syncAttemptNumberChanged(_syncLoopAttemptNumber); - getNewEvents(); -} - -void Connection::getNewEventsOnSyncError() -{ - if (_prevSyncLoopIterationDone) { - _syncLoopAttemptNumber = 0; - } else { - _syncLoopAttemptNumber++; - } - emit syncAttemptNumberChanged(_syncLoopAttemptNumber); - getNewEvents(); -} - void Connection::stopSync() { if (d->syncJob) @@ -470,15 +437,6 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const return callApi(room->id(), "m.read", event->id()); } -void Connection::setMinSyncDelayMs(qint64 minSyncDelayMs) -{ - if (_minSyncLoopDelayMs == minSyncDelayMs) - return; - - _minSyncLoopDelayMs = minSyncDelayMs; - emit minSyncDelayMsChanged(_minSyncLoopDelayMs); -} - JoinRoomJob* Connection::joinRoom(const QString& roomAlias, const QStringList& serverNames) { @@ -1143,16 +1101,6 @@ user_factory_t Connection::userFactory() return _userFactory; } -qint64 Connection::minSyncLoopDelayMs() const -{ - return _minSyncLoopDelayMs; -} - -uint Connection::syncLoopAttemptNumber() const -{ - return _syncLoopAttemptNumber; -} - room_factory_t Connection::_roomFactory = defaultRoomFactory<>(); user_factory_t Connection::_userFactory = defaultUserFactory<>(); diff --git a/lib/connection.h b/lib/connection.h index ee7ad243..c9ca3580 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -106,8 +105,6 @@ namespace QMatrixClient Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged) Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged) Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) - Q_PROPERTY(qint64 minSyncLoopDelayMs READ minSyncLoopDelayMs WRITE setMinSyncDelayMs NOTIFY minSyncDelayMsChanged) - Q_PROPERTY(uint syncLoopAttemptNumber READ syncLoopAttemptNumber NOTIFY syncAttemptNumberChanged) public: // Room ids, rather than room pointers, are used in the direct chat @@ -356,11 +353,6 @@ namespace QMatrixClient template static void setUserType() { setUserFactory(defaultUserFactory()); } - qint64 minSyncLoopDelayMs() const; - void setMinSyncDelayMs(qint64 minSyncLoopDelayMs); - - uint syncLoopAttemptNumber() const; - public slots: /** Set the homeserver base URL */ void setHomeserver(const QUrl& baseUrl); @@ -379,13 +371,6 @@ namespace QMatrixClient void logout(); void sync(int timeout = -1); - - /** Start sync loop with the minSyncLoopDelayMs value - where minSyncLoopDelayMs could be changed on the client - according to syncDone/syncError signals and - the syncLoopAttemptNumber counter. - The syncLoopAttemptNumber counter is resetting - after non-repeating syncDone/syncError events*/ void syncLoop(int timeout = -1); void stopSync(); @@ -662,8 +647,6 @@ namespace QMatrixClient void cacheStateChanged(); void lazyLoadingChanged(); void turnServersChanged(const QJsonObject& servers); - void minSyncDelayMsChanged(qint64 minSyncLoopDelayMs); - void syncAttemptNumberChanged(uint syncLoopAttemptNumber); protected: /** @@ -699,9 +682,7 @@ namespace QMatrixClient void onSyncSuccess(SyncData &&data, bool fromCache = false); protected slots: - void getNewEvents(); - void getNewEventsOnSyncDone(); - void getNewEventsOnSyncError(); + void syncLoopIteration(); private: class Private; @@ -727,12 +708,7 @@ namespace QMatrixClient static room_factory_t _roomFactory; static user_factory_t _userFactory; - QElapsedTimer _syncLoopElapsedTimer; int _syncLoopTimeout = -1; - qint64 _minSyncLoopDelayMs = 0; - void syncLoopIteration(); - uint _syncLoopAttemptNumber = 0; - bool _prevSyncLoopIterationDone = false; }; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Connection*) -- cgit v1.2.3 From 73c836239bfa35713ad76d5e205ce2f2dceffffd Mon Sep 17 00:00:00 2001 From: Alexey Andreyev Date: Sun, 10 Feb 2019 15:08:08 +0300 Subject: Connection: move syncLoopTimeout to Connection::Private Signed-off-by: Alexey Andreyev --- lib/connection.cpp | 5 +++-- lib/connection.h | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/connection.cpp b/lib/connection.cpp index a9a8bba3..63b0a31d 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -90,6 +90,7 @@ class Connection::Private DirectChatUsersMap directChatUsers; std::unordered_map accountData; QString userId; + int syncLoopTimeout = -1; SyncJob* syncJob = nullptr; @@ -232,7 +233,7 @@ void Connection::doConnectToServer(const QString& user, const QString& password, void Connection::syncLoopIteration() { - sync(_syncLoopTimeout); + sync(d->syncLoopTimeout); } void Connection::connectWithToken(const QString& userId, @@ -326,7 +327,7 @@ void Connection::sync(int timeout) void Connection::syncLoop(int timeout) { - _syncLoopTimeout = timeout; + d->syncLoopTimeout = timeout; connect(this, &Connection::syncDone, this, &Connection::syncLoopIteration); syncLoopIteration(); // initial sync to start the loop } diff --git a/lib/connection.h b/lib/connection.h index c9ca3580..45b691e1 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -707,8 +707,6 @@ namespace QMatrixClient static room_factory_t _roomFactory; static user_factory_t _userFactory; - - int _syncLoopTimeout = -1; }; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Connection*) -- cgit v1.2.3 From 1c83d54f705ad786e4a27aaab94e3a0af725a07c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Feb 2019 15:58:19 +0900 Subject: Omittable: disallow implicit conversion to value_type altogether Because it works, and fails, in surprising ways. And none of the code uses it, as of now. --- lib/util.h | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/util.h b/lib/util.h index 420b0984..596872e2 100644 --- a/lib/util.h +++ b/lib/util.h @@ -159,7 +159,6 @@ namespace QMatrixClient } value_type&& release() { _omitted = true; return std::move(_value); } - operator const value_type&() const & { return value(); } const value_type* operator->() const & { return &value(); } value_type* operator->() & { return &editValue(); } const value_type& operator*() const & { return value(); } -- cgit v1.2.3 From 52dcb27e1c19389bd833c93609910483ea3be549 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Feb 2019 07:55:15 +0900 Subject: RoomVersionsCapability: fix naming for 'default' parameter The same word is used as a predicate in push_rule.yaml and as a noun in capabilities.yaml; fortunately, GTAD gives some means to distinguish the two. --- lib/csapi/capabilities.cpp | 2 +- lib/csapi/capabilities.h | 2 +- lib/csapi/gtad.yaml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index a8e79f6b..bd92cf25 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -28,7 +28,7 @@ namespace QMatrixClient { static void fillFrom(const QJsonObject& jo, GetCapabilitiesJob::RoomVersionsCapability& result) { - fromJson(jo.value("default"_ls), result.isDefault); + fromJson(jo.value("default"_ls), result.defaultVersion); fromJson(jo.value("available"_ls), result.available); } }; diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h index e38483bc..40a2e6f7 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -33,7 +33,7 @@ namespace QMatrixClient struct RoomVersionsCapability { /// The default room version the server is using for new rooms. - QString isDefault; + QString defaultVersion; /// A detailed description of the room versions the server supports. QHash available; }; diff --git a/lib/csapi/gtad.yaml b/lib/csapi/gtad.yaml index 21a59a5c..a44f803a 100644 --- a/lib/csapi/gtad.yaml +++ b/lib/csapi/gtad.yaml @@ -5,7 +5,8 @@ analyzer: identifiers: signed: signedData unsigned: unsignedData - default: isDefault + PushRule/default: isDefault + default: defaultVersion # getCapabilities/RoomVersionsCapability origin_server_ts: originServerTimestamp # Instead of originServerTs start: begin # Because start() is a method in BaseJob m.upload.size: uploadSize -- cgit v1.2.3 From a2d9a7b865bfd93386844270849ab72b36a86fbe Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Feb 2019 16:50:36 +0900 Subject: csapi/capabilities.*: fix the definition As per https://github.com/matrix-org/matrix-doc/pull/1879. --- lib/csapi/capabilities.cpp | 28 +++++++++++++++++----------- lib/csapi/capabilities.h | 21 +++++++++++++++++---- 2 files changed, 34 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index bd92cf25..210423f5 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -32,13 +32,22 @@ namespace QMatrixClient fromJson(jo.value("available"_ls), result.available); } }; + + template <> struct JsonObjectConverter + { + static void fillFrom(QJsonObject jo, GetCapabilitiesJob::Capabilities& result) + { + fromJson(jo.take("m.change_password"_ls), result.changePassword); + fromJson(jo.take("m.room_versions"_ls), result.roomVersions); + fromJson(jo, result.additionalProperties); + } + }; } // namespace QMatrixClient class GetCapabilitiesJob::Private { public: - Omittable changePassword; - Omittable roomVersions; + Capabilities capabilities; }; QUrl GetCapabilitiesJob::makeRequestUrl(QUrl baseUrl) @@ -58,21 +67,18 @@ GetCapabilitiesJob::GetCapabilitiesJob() GetCapabilitiesJob::~GetCapabilitiesJob() = default; -const Omittable& GetCapabilitiesJob::changePassword() const -{ - return d->changePassword; -} - -const Omittable& GetCapabilitiesJob::roomVersions() const +const GetCapabilitiesJob::Capabilities& GetCapabilitiesJob::capabilities() const { - return d->roomVersions; + return d->capabilities; } BaseJob::Status GetCapabilitiesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - fromJson(json.value("m.change_password"_ls), d->changePassword); - fromJson(json.value("m.room_versions"_ls), d->roomVersions); + if (!json.contains("capabilities"_ls)) + return { JsonParseError, + "The key 'capabilities' not found in the response" }; + fromJson(json.value("capabilities"_ls), d->capabilities); return Success; } diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h index 40a2e6f7..39e2f4d1 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -6,6 +6,7 @@ #include "jobs/basejob.h" +#include #include "converters.h" #include @@ -38,6 +39,19 @@ namespace QMatrixClient QHash available; }; + /// Gets information about the server's supported feature set + /// and other relevant capabilities. + struct Capabilities + { + /// Capability to indicate if the user can change their password. + Omittable changePassword; + /// The room versions the server supports. + Omittable roomVersions; + /// The custom capabilities the server supports, using the + /// Java package naming convention. + QHash additionalProperties; + }; + // Construction/destruction explicit GetCapabilitiesJob(); @@ -54,10 +68,9 @@ namespace QMatrixClient // Result properties - /// Capability to indicate if the user can change their password. - const Omittable& changePassword() const; - /// The room versions the server supports. - const Omittable& roomVersions() const; + /// Gets information about the server's supported feature set + /// and other relevant capabilities. + const Capabilities& capabilities() const; protected: Status parseJson(const QJsonDocument& data) override; -- cgit v1.2.3