diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2019-06-24 07:21:13 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2019-06-24 07:21:13 +0900 |
commit | 63ae79c3e2820efc2ba60d33e2caf2d7b9b3c408 (patch) | |
tree | 2552f5049a6ef7ba0034483b25ca4ab33d1fcb13 /lib | |
parent | e083d327e6f6581210f8d077d8bbe1151e81e82c (diff) | |
parent | 93f0c8fe89f448d1d58caa757573f17102369471 (diff) | |
download | libquotient-63ae79c3e2820efc2ba60d33e2caf2d7b9b3c408.tar.gz libquotient-63ae79c3e2820efc2ba60d33e2caf2d7b9b3c408.zip |
Merge branch 'master' into clang-format
# Conflicts:
# CMakeLists.txt
# lib/avatar.cpp
# lib/connection.cpp
# lib/connection.h
# lib/connectiondata.cpp
# lib/csapi/account-data.cpp
# lib/csapi/account-data.h
# lib/csapi/capabilities.cpp
# lib/csapi/capabilities.h
# lib/csapi/content-repo.cpp
# lib/csapi/create_room.cpp
# lib/csapi/filter.cpp
# lib/csapi/joining.cpp
# lib/csapi/keys.cpp
# lib/csapi/list_joined_rooms.cpp
# lib/csapi/notifications.cpp
# lib/csapi/openid.cpp
# lib/csapi/presence.cpp
# lib/csapi/pushrules.cpp
# lib/csapi/registration.cpp
# lib/csapi/room_upgrades.cpp
# lib/csapi/room_upgrades.h
# lib/csapi/search.cpp
# lib/csapi/users.cpp
# lib/csapi/versions.cpp
# lib/csapi/whoami.cpp
# lib/csapi/{{base}}.cpp.mustache
# lib/events/accountdataevents.h
# lib/events/eventcontent.h
# lib/events/roommemberevent.cpp
# lib/events/stateevent.cpp
# lib/jobs/basejob.cpp
# lib/jobs/basejob.h
# lib/networkaccessmanager.cpp
# lib/networksettings.cpp
# lib/room.cpp
# lib/room.h
# lib/settings.cpp
# lib/settings.h
# lib/syncdata.cpp
# lib/user.cpp
# lib/user.h
# lib/util.cpp
Diffstat (limited to 'lib')
204 files changed, 12521 insertions, 11572 deletions
diff --git a/lib/application-service/definitions/location.cpp b/lib/application-service/definitions/location.cpp index 4da0e69d..2ab83ae9 100644 --- a/lib/application-service/definitions/location.cpp +++ b/lib/application-service/definitions/location.cpp @@ -7,15 +7,15 @@ using namespace QMatrixClient; void JsonObjectConverter<ThirdPartyLocation>::dumpTo( - QJsonObject& jo, const ThirdPartyLocation& pod) + QJsonObject& jo, const ThirdPartyLocation& pod) { addParam<>(jo, QStringLiteral("alias"), pod.alias); addParam<>(jo, QStringLiteral("protocol"), pod.protocol); addParam<>(jo, QStringLiteral("fields"), pod.fields); } -void JsonObjectConverter<ThirdPartyLocation>::fillFrom( - const QJsonObject& jo, ThirdPartyLocation& result) +void JsonObjectConverter<ThirdPartyLocation>::fillFrom(const QJsonObject& jo, + ThirdPartyLocation& result) { fromJson(jo.value("alias"_ls), result.alias); fromJson(jo.value("protocol"_ls), result.protocol); diff --git a/lib/application-service/definitions/location.h b/lib/application-service/definitions/location.h index f92fa8c5..caf28615 100644 --- a/lib/application-service/definitions/location.h +++ b/lib/application-service/definitions/location.h @@ -8,20 +8,26 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Data structures - - struct ThirdPartyLocation { - /// An alias for a matrix room. - QString alias; - /// The protocol ID that the third party location is a part of. - QString protocol; - /// Information used to identify this third party location. - QJsonObject fields; - }; - template <> struct JsonObjectConverter<ThirdPartyLocation> { - static void dumpTo(QJsonObject& jo, const ThirdPartyLocation& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyLocation& pod); - }; +namespace QMatrixClient +{ + +// Data structures + +struct ThirdPartyLocation +{ + /// An alias for a matrix room. + QString alias; + /// The protocol ID that the third party location is a part of. + QString protocol; + /// Information used to identify this third party location. + QJsonObject fields; +}; + +template <> +struct JsonObjectConverter<ThirdPartyLocation> +{ + static void dumpTo(QJsonObject& jo, const ThirdPartyLocation& pod); + static void fillFrom(const QJsonObject& jo, ThirdPartyLocation& pod); +}; } // namespace QMatrixClient diff --git a/lib/application-service/definitions/protocol.cpp b/lib/application-service/definitions/protocol.cpp index 4c148796..e87001fb 100644 --- a/lib/application-service/definitions/protocol.cpp +++ b/lib/application-service/definitions/protocol.cpp @@ -39,7 +39,7 @@ void JsonObjectConverter<ProtocolInstance>::fillFrom(const QJsonObject& jo, } void JsonObjectConverter<ThirdPartyProtocol>::dumpTo( - QJsonObject& jo, const ThirdPartyProtocol& pod) + QJsonObject& jo, const ThirdPartyProtocol& pod) { addParam<>(jo, QStringLiteral("user_fields"), pod.userFields); addParam<>(jo, QStringLiteral("location_fields"), pod.locationFields); @@ -48,8 +48,8 @@ void JsonObjectConverter<ThirdPartyProtocol>::dumpTo( addParam<>(jo, QStringLiteral("instances"), pod.instances); } -void JsonObjectConverter<ThirdPartyProtocol>::fillFrom( - const QJsonObject& jo, ThirdPartyProtocol& result) +void JsonObjectConverter<ThirdPartyProtocol>::fillFrom(const QJsonObject& jo, + ThirdPartyProtocol& result) { fromJson(jo.value("user_fields"_ls), result.userFields); fromJson(jo.value("location_fields"_ls), result.locationFields); diff --git a/lib/application-service/definitions/protocol.h b/lib/application-service/definitions/protocol.h index 66012a13..0d227851 100644 --- a/lib/application-service/definitions/protocol.h +++ b/lib/application-service/definitions/protocol.h @@ -6,74 +6,83 @@ #include "converters.h" -#include "converters.h" #include <QtCore/QHash> #include <QtCore/QJsonObject> #include <QtCore/QVector> -namespace QMatrixClient { - // Data structures +namespace QMatrixClient +{ + +// Data structures + +/// Definition of valid values for a field. +struct FieldType +{ + /// A regular expression for validation of a field's value. This may be + /// relativelycoarse to verify the value as the application service + /// providing this protocolmay apply additional validation or filtering. + QString regexp; + /// An placeholder serving as a valid example of the field value. + QString placeholder; +}; + +template <> +struct JsonObjectConverter<FieldType> +{ + static void dumpTo(QJsonObject& jo, const FieldType& pod); + static void fillFrom(const QJsonObject& jo, FieldType& pod); +}; + +struct ProtocolInstance +{ + /// A human-readable description for the protocol, such as the name. + QString desc; + /// An optional content URI representing the protocol. Overrides the one + /// providedat the higher level Protocol object. + QString icon; + /// Preset values for ``fields`` the client may use to search by. + QJsonObject fields; + /// A unique identifier across all instances. + QString networkId; +}; - /// Definition of valid values for a field. - struct FieldType { - /// A regular expression for validation of a field's value. This may be - /// relatively coarse to verify the value as the application service - /// providing this protocol may apply additional validation or - /// filtering. - QString regexp; - /// An placeholder serving as a valid example of the field value. - QString placeholder; - }; - template <> struct JsonObjectConverter<FieldType> { - static void dumpTo(QJsonObject& jo, const FieldType& pod); - static void fillFrom(const QJsonObject& jo, FieldType& pod); - }; +template <> +struct JsonObjectConverter<ProtocolInstance> +{ + static void dumpTo(QJsonObject& jo, const ProtocolInstance& pod); + static void fillFrom(const QJsonObject& jo, ProtocolInstance& pod); +}; - struct ProtocolInstance { - /// A human-readable description for the protocol, such as the name. - QString desc; - /// An optional content URI representing the protocol. Overrides the one - /// provided at the higher level Protocol object. - QString icon; - /// Preset values for ``fields`` the client may use to search by. - QJsonObject fields; - /// A unique identifier across all instances. - QString networkId; - }; - template <> struct JsonObjectConverter<ProtocolInstance> { - static void dumpTo(QJsonObject& jo, const ProtocolInstance& pod); - static void fillFrom(const QJsonObject& jo, ProtocolInstance& pod); - }; +struct ThirdPartyProtocol +{ + /// Fields which may be used to identify a third party user. These should + /// beordered to suggest the way that entities may be grouped, where + /// highergroupings are ordered first. For example, the name of a network + /// should besearched before the nickname of a user. + QStringList userFields; + /// Fields which may be used to identify a third party location. These + /// should beordered to suggest the way that entities may be grouped, where + /// highergroupings are ordered first. For example, the name of a network + /// should besearched before the name of a channel. + QStringList locationFields; + /// A content URI representing an icon for the third party protocol. + QString icon; + /// The type definitions for the fields defined in the ``user_fields`` and + /// ``location_fields``. Each entry in those arrays MUST have an entry here. + /// The``string`` key for this object is field name itself.May be an empty + /// object if no fields are defined. + QHash<QString, FieldType> fieldTypes; + /// A list of objects representing independent instances of + /// configuration.For example, multiple networks on IRC if multiple are + /// provided by thesame application service. + QVector<ProtocolInstance> instances; +}; - struct ThirdPartyProtocol { - /// Fields which may be used to identify a third party user. These - /// should be ordered to suggest the way that entities may be grouped, - /// where higher groupings are ordered first. For example, the name of a - /// network should be searched before the nickname of a user. - QStringList userFields; - /// Fields which may be used to identify a third party location. These - /// should be ordered to suggest the way that entities may be grouped, - /// where higher groupings are ordered first. For example, the name of a - /// network should be searched before the name of a channel. - QStringList locationFields; - /// A content URI representing an icon for the third party protocol. - QString icon; - /// The type definitions for the fields defined in the ``user_fields`` - /// and - /// ``location_fields``. Each entry in those arrays MUST have an entry - /// here. The - /// ``string`` key for this object is field name itself. - /// - /// May be an empty object if no fields are defined. - QHash<QString, FieldType> fieldTypes; - /// A list of objects representing independent instances of - /// configuration. For example, multiple networks on IRC if multiple are - /// provided by the same application service. - QVector<ProtocolInstance> instances; - }; - template <> struct JsonObjectConverter<ThirdPartyProtocol> { - static void dumpTo(QJsonObject& jo, const ThirdPartyProtocol& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyProtocol& pod); - }; +template <> +struct JsonObjectConverter<ThirdPartyProtocol> +{ + static void dumpTo(QJsonObject& jo, const ThirdPartyProtocol& pod); + static void fillFrom(const QJsonObject& jo, ThirdPartyProtocol& pod); +}; } // namespace QMatrixClient diff --git a/lib/application-service/definitions/user.h b/lib/application-service/definitions/user.h index 9b314db1..3fd099d0 100644 --- a/lib/application-service/definitions/user.h +++ b/lib/application-service/definitions/user.h @@ -8,20 +8,26 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Data structures - - struct ThirdPartyUser { - /// A Matrix User ID represting a third party user. - QString userid; - /// The protocol ID that the third party location is a part of. - QString protocol; - /// Information used to identify this third party location. - QJsonObject fields; - }; - template <> struct JsonObjectConverter<ThirdPartyUser> { - static void dumpTo(QJsonObject& jo, const ThirdPartyUser& pod); - static void fillFrom(const QJsonObject& jo, ThirdPartyUser& pod); - }; +namespace QMatrixClient +{ + +// Data structures + +struct ThirdPartyUser +{ + /// A Matrix User ID represting a third party user. + QString userid; + /// The protocol ID that the third party location is a part of. + QString protocol; + /// Information used to identify this third party location. + QJsonObject fields; +}; + +template <> +struct JsonObjectConverter<ThirdPartyUser> +{ + static void dumpTo(QJsonObject& jo, const ThirdPartyUser& pod); + static void fillFrom(const QJsonObject& jo, ThirdPartyUser& pod); +}; } // namespace QMatrixClient diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 5de43dd5..0e58a1ce 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -1,5 +1,3 @@ -#include <utility> - /****************************************************************************** * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> * @@ -21,6 +19,7 @@ #include "avatar.h" #include "connection.h" + #include "events/eventcontent.h" #include "jobs/mediathumbnailjob.h" @@ -35,8 +34,10 @@ using std::move; class Avatar::Private { - public: - explicit Private(QUrl url = {}) : _url(move(url)) {} +public: + explicit Private(QUrl url = {}) + : _url(move(url)) + {} ~Private() { if (isJobRunning(_thumbnailRequest)) @@ -64,9 +65,13 @@ class Avatar::Private mutable std::vector<get_callback_t> callbacks; }; -Avatar::Avatar() : d(std::make_unique<Private>()) {} +Avatar::Avatar() + : d(std::make_unique<Private>()) +{} -Avatar::Avatar(QUrl url) : d(std::make_unique<Private>(std::move(url))) {} +Avatar::Avatar(QUrl url) + : d(std::make_unique<Private>(std::move(url))) +{} Avatar::Avatar(Avatar&&) = default; @@ -135,9 +140,8 @@ QImage Avatar::Private::get(Connection* connection, QSize size, QObject::connect(_thumbnailRequest, &MediaThumbnailJob::success, _thumbnailRequest, [this] { _imageSource = Network; - _originalImage = - _thumbnailRequest->scaledThumbnail( - _requestedSize); + _originalImage = _thumbnailRequest->scaledThumbnail( + _requestedSize); _originalImage.save(localFile()); _scaledImages.clear(); for (const auto& n : callbacks) @@ -150,9 +154,9 @@ QImage Avatar::Private::get(Connection* connection, QSize size, if (p.first == size) return p.second; auto result = _originalImage.isNull() - ? QImage() - : _originalImage.scaled(size, Qt::KeepAspectRatio, - Qt::SmoothTransformation); + ? QImage() + : _originalImage.scaled(size, Qt::KeepAspectRatio, + Qt::SmoothTransformation); _scaledImages.emplace_back(size, result); return result; } @@ -184,7 +188,7 @@ bool Avatar::Private::checkUrl(const QUrl& url) const QString Avatar::Private::localFile() const { - static const auto cachePath = cacheLocation("avatars"); + static const auto cachePath = cacheLocation(QStringLiteral("avatars")); return cachePath % _url.authority() % '_' % _url.fileName() % ".png"; } diff --git a/lib/avatar.h b/lib/avatar.h index 2ca2c987..37991192 100644 --- a/lib/avatar.h +++ b/lib/avatar.h @@ -24,37 +24,38 @@ #include <functional> #include <memory> -namespace QMatrixClient { - class Connection; - - class Avatar - { - public: - explicit Avatar(); - explicit Avatar(QUrl url); - Avatar(Avatar&&); - ~Avatar(); - Avatar& operator=(Avatar&&); - - using get_callback_t = std::function<void()>; - using upload_callback_t = std::function<void(QString)>; - - QImage get(Connection* connection, int dimension, - get_callback_t callback) const; - QImage get(Connection* connection, int w, int h, - get_callback_t callback) const; - - bool upload(Connection* connection, const QString& fileName, - upload_callback_t callback) const; - bool upload(Connection* connection, QIODevice* source, - upload_callback_t callback) const; - - QString mediaId() const; - QUrl url() const; - bool updateUrl(const QUrl& newUrl); - - private: - class Private; - std::unique_ptr<Private> d; - }; +namespace QMatrixClient +{ +class Connection; + +class Avatar +{ +public: + explicit Avatar(); + explicit Avatar(QUrl url); + Avatar(Avatar&&); + ~Avatar(); + Avatar& operator=(Avatar&&); + + using get_callback_t = std::function<void()>; + using upload_callback_t = std::function<void(QString)>; + + QImage get(Connection* connection, int dimension, + get_callback_t callback) const; + QImage get(Connection* connection, int w, int h, + get_callback_t callback) const; + + bool upload(Connection* connection, const QString& fileName, + upload_callback_t callback) const; + bool upload(Connection* connection, QIODevice* source, + upload_callback_t callback) const; + + QString mediaId() const; + QUrl url() const; + bool updateUrl(const QUrl& newUrl); + +private: + class Private; + std::unique_ptr<Private> d; +}; } // namespace QMatrixClient diff --git a/lib/connection.cpp b/lib/connection.cpp index 2b1cc1b9..43681d12 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -17,7 +17,12 @@ */ #include "connection.h" + #include "connectiondata.h" +#include "room.h" +#include "settings.h" +#include "user.h" + #include "csapi/account-data.h" #include "csapi/capabilities.h" #include "csapi/joining.h" @@ -28,14 +33,12 @@ #include "csapi/room_send.h" #include "csapi/to_device.h" #include "csapi/voip.h" + #include "events/directchatevent.h" #include "events/eventloader.h" #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/syncjob.h" -#include "room.h" -#include "settings.h" -#include "user.h" #include <QtCore/QCoreApplication> #include <QtCore/QDir> @@ -66,21 +69,19 @@ HashT erase_if(HashT& hashMap, Pred pred) class Connection::Private { - public: +public: explicit Private(std::unique_ptr<ConnectionData>&& connection) : data(move(connection)) - { - } + {} Q_DISABLE_COPY(Private) - Private(Private&&) = delete; - Private operator=(Private&&) = delete; + DISABLE_MOVE(Private) Connection* q = nullptr; std::unique_ptr<ConnectionData> data; // A complex key below is a pair of room name and whether its // state is Invited. The spec mandates to keep Invited room state - // separately so we should, e.g., keep objects for Invite and - // Leave state of the same room. + // separately; specifically, we should keep objects for Invite and + // Leave state of the same room if the two happen to co-exist. QHash<QPair<QString, bool>, Room*> roomMap; // Mapping from aliases to room ids, as per the last sync QHash<QString, QString> roomAliasMap; @@ -90,6 +91,11 @@ class Connection::Private QMap<QString, User*> userMap; DirectChatsMap directChats; DirectChatUsersMap directChatUsers; + // The below two variables track local changes between sync completions. + // See also: + // https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + DirectChatsMap dcLocalAdditions; + DirectChatsMap dcLocalRemovals; std::unordered_map<QString, EventPtr> accountData; QString userId; int syncLoopTimeout = -1; @@ -101,21 +107,20 @@ class Connection::Private bool cacheState = true; bool cacheToBinary = - SettingsGroup("libqmatrixclient").value("cache_type").toString() - != "json"; + SettingsGroup("libqmatrixclient").value("cache_type").toString() + != "json"; bool lazyLoading = false; void connectWithToken(const QString& user, const QString& accessToken, const QString& deviceId); - void broadcastDirectChatUpdates(const DirectChatsMap& additions, - const DirectChatsMap& removals); - template <typename EventT> EventT* unpackAccountData() const + template <typename EventT> + EventT* unpackAccountData() const { const auto& eventIt = accountData.find(EventT::matrixTypeId()); return eventIt == accountData.end() - ? nullptr - : weakPtrCast<EventT>(eventIt->second); + ? nullptr + : weakPtrCast<EventT>(eventIt->second); } void packAndSendAccountData(EventPtr&& event) @@ -130,18 +135,24 @@ class Connection::Private void packAndSendAccountData(ContentT&& content) { packAndSendAccountData( - makeEvent<EventT>(std::forward<ContentT>(content))); + makeEvent<EventT>(std::forward<ContentT>(content))); + } + QString topLevelStatePath() const + { + return q->stateCacheDir().filePath("state.json"); } }; Connection::Connection(const QUrl& server, QObject* parent) - : QObject(parent), - d(std::make_unique<Private>(std::make_unique<ConnectionData>(server))) + : QObject(parent) + , d(new Private(std::make_unique<ConnectionData>(server))) { d->q = this; // All d initialization should occur before this line } -Connection::Connection(QObject* parent) : Connection({}, parent) {} +Connection::Connection(QObject* parent) + : Connection({}, parent) +{} Connection::~Connection() { @@ -156,20 +167,17 @@ void Connection::resolveServer(const QString& mxidOrDomain) // Try to parse as an FQID; if there's no @ part, assume it's a domain name. QRegularExpression parser( - "^(@.+?:)?" // Optional username (allow everything for - // compatibility) - "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 - // address - "(:\\d{1,5})?$", // Optional port - QRegularExpression::UseUnicodePropertiesOption); // Because asian - // digits + "^(@.+?:)?" // Optional username (allow everything for compatibility) + "(\\[[^]]+\\]|[^:@]+)" // Either IPv6 address or hostname/IPv4 address + "(:\\d{1,5})?$", // Optional port + QRegularExpression::UseUnicodePropertiesOption); // Because asian digits auto match = parser.match(mxidOrDomain); QUrl maybeBaseUrl = QUrl::fromUserInput(match.captured(2)); maybeBaseUrl.setScheme("https"); // Instead of the Qt-default "http" if (!match.hasMatch() || !maybeBaseUrl.isValid()) { emit resolveError(tr("%1 is not a valid homeserver address") - .arg(maybeBaseUrl.toString())); + .arg(maybeBaseUrl.toString())); return; } @@ -219,10 +227,10 @@ void Connection::doConnectToServer(const QString& user, const QString& password, const QString& deviceId) { auto loginJob = callApi<LoginJob>( - QStringLiteral("m.login.password"), - UserIdentifier { QStringLiteral("m.id.user"), - { { QStringLiteral("user"), user } } }, - password, /*token*/ "", deviceId, initialDeviceName); + QStringLiteral("m.login.password"), + UserIdentifier { QStringLiteral("m.id.user"), + { { QStringLiteral("user"), user } } }, + password, /*token*/ "", deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [this, loginJob] { d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); @@ -238,9 +246,8 @@ void Connection::connectWithToken(const QString& userId, const QString& accessToken, const QString& deviceId) { - checkAndConnect(userId, [=] { - d->connectWithToken(userId, accessToken, deviceId); - }); + checkAndConnect(userId, + [=] { d->connectWithToken(userId, accessToken, deviceId); }); } void Connection::reloadCapabilities() @@ -257,14 +264,12 @@ void Connection::reloadCapabilities() d->capabilities.roomVersions = { "1", { { "1", "stable" } } }; } else { qCDebug(MAIN) << "Room versions:" << defaultRoomVersion() - << "is default, full list:" - << availableRoomVersions(); + << "is default, full list:" << availableRoomVersions(); } Q_ASSERT(!d->capabilities.roomVersions.omitted()); emit capabilitiesLoaded(); for (auto* r : d->roomMap) - if (r->joinState() == JoinState::Join && r->successorId().isEmpty()) - r->checkVersion(); + r->checkVersion(); }); } @@ -283,6 +288,7 @@ void Connection::Private::connectWithToken(const QString& user, q->user(); // Creates a User object for the local user data->setToken(accessToken.toLatin1()); data->setDeviceId(deviceId); + q->setObjectName(userId % '/' % deviceId); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << userId << "from device" << deviceId; emit q->stateChanged(); @@ -299,24 +305,25 @@ void Connection::checkAndConnect(const QString& userId, } // Not good to go, try to fix the homeserver URL. if (userId.startsWith('@') && userId.indexOf(':') != -1) { - connectSingleShot(this, &Connection::homeserverChanged, this, - connectFn); + connectSingleShot(this, &Connection::homeserverChanged, this, connectFn); // NB: doResolveServer can emit resolveError, so this is a part of // checkAndConnect function contract. resolveServer(userId); } else emit resolveError(tr("%1 is an invalid homeserver URL") - .arg(d->data->baseUrl().toString())); + .arg(d->data->baseUrl().toString())); } void Connection::logout() { auto job = callApi<LogoutJob>(); - connect(job, &LogoutJob::success, this, [this] { - stopSync(); - d->data->setToken({}); - emit stateChanged(); - emit loggedOut(); + connect(job, &LogoutJob::finished, this, [job, this] { + if (job->status().good() || job->error() == BaseJob::ContentAccessError) { + stopSync(); + d->data->setToken({}); + emit stateChanged(); + emit loggedOut(); + } }); } @@ -328,8 +335,9 @@ void Connection::sync(int timeout) Filter filter; filter.room->timeline->limit = 100; filter.room->state->lazyLoadMembers = d->lazyLoading; - auto job = d->syncJob = callApi<SyncJob>( - BackgroundRequest, d->data->lastEvent(), filter, timeout); + auto job = d->syncJob = callApi<SyncJob>(BackgroundRequest, + d->data->lastEvent(), filter, + timeout); connect(job, &SyncJob::success, this, [this, job] { onSyncSuccess(job->takeData()); d->syncJob = nullptr; @@ -343,8 +351,8 @@ void Connection::sync(int timeout) connect(job, &SyncJob::failure, this, [this, job] { d->syncJob = nullptr; if (job->error() == BaseJob::ContentAccessError) { - qCWarning(SYNCJOB) << "Sync job failed with ContentAccessError - " - "login expired?"; + qCWarning(SYNCJOB) + << "Sync job failed with ContentAccessError - login expired?"; emit loginError(job->errorString(), job->rawDataSample()); } else emit syncError(job->errorString(), job->rawDataSample()); @@ -358,6 +366,19 @@ void Connection::syncLoop(int timeout) syncLoopIteration(); // initial sync to start the loop } +QJsonObject toJson(const Connection::DirectChatsMap& directChats) +{ + QJsonObject json; + for (auto it = directChats.begin(); it != directChats.end();) { + QJsonArray roomIds; + const auto* user = it.key(); + for (; it != directChats.end() && it.key() == user; ++it) + roomIds.append(*it); + json.insert(user->id(), roomIds); + } + return json; +} + void Connection::onSyncSuccess(SyncData&& data, bool fromCache) { d->data->setLastEvent(data.nextBatch()); @@ -367,8 +388,8 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) d->roomIdsToForget.removeAt(forgetIdx); if (roomData.joinState == JoinState::Leave) { qDebug(MAIN) - << "Room" << roomData.roomId - << "has been forgotten, ignoring /sync response for it"; + << "Room" << roomData.roomId + << "has been forgotten, ignoring /sync response for it"; continue; } qWarning(MAIN) << "Room" << roomData.roomId @@ -383,64 +404,92 @@ void Connection::onSyncSuccess(SyncData&& data, bool fromCache) emit loadedRoomState(r); if (!d->capabilities.roomVersions.omitted()) r->checkVersion(); - // Otherwise, the version will be checked in - // reloadCapabilities() + // Otherwise, the version will be checked in reloadCapabilities() } } // Let UI update itself after updating each room QCoreApplication::processEvents(); } - for (auto&& accountEvent : data.takeAccountData()) { - if (is<DirectChatEvent>(*accountEvent)) { - const auto usersToDCs = ptrCast<DirectChatEvent>(move(accountEvent)) - ->usersToDirectChats(); - DirectChatsMap removals = - erase_if(d->directChats, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.key()->id(), it.value()); + // After running this loop, the account data events not saved in + // d->accountData (see the end of the loop body) are auto-cleaned away + for (auto& eventPtr : data.takeAccountData()) { + visit( + *eventPtr, + [this](const DirectChatEvent& dce) { + // See + // https://github.com/QMatrixClient/libqmatrixclient/wiki/Handling-direct-chat-events + const auto& usersToDCs = dce.usersToDirectChats(); + DirectChatsMap remoteRemovals = + erase_if(d->directChats, [&usersToDCs, this](auto it) { + return !(usersToDCs.contains(it.key()->id(), it.value()) + || d->dcLocalAdditions.contains(it.key(), + it.value())); }); - erase_if(d->directChatUsers, [&usersToDCs](auto it) { - return !usersToDCs.contains(it.value()->id(), it.key()); - }); - if (MAIN().isDebugEnabled()) - for (auto it = removals.begin(); it != removals.end(); ++it) - qCDebug(MAIN) + erase_if(d->directChatUsers, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.value(), it.key()); + }); + // Remove from dcLocalRemovals what the server already has. + erase_if(d->dcLocalRemovals, [&remoteRemovals](auto it) { + return remoteRemovals.contains(it.key(), it.value()); + }); + if (MAIN().isDebugEnabled()) + for (auto it = remoteRemovals.begin(); + it != remoteRemovals.end(); ++it) { + qCDebug(MAIN) << it.value() << "is no more a direct chat with" << it.key()->id(); - - DirectChatsMap additions; - for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { - if (auto* u = user(it.key())) { - if (!d->directChats.contains(u, it.value())) { - Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); - additions.insert(u, it.value()); - d->directChats.insert(u, it.value()); - d->directChatUsers.insert(it.value(), u); - qCDebug(MAIN) << "Marked room" << it.value() - << "as a direct chat with" << u->id(); } - } else - qCWarning(MAIN) - << "Couldn't get a user object for" << it.key(); - } - if (!additions.isEmpty() || !removals.isEmpty()) - emit directChatsListChanged(additions, removals); - continue; - } - if (is<IgnoredUsersEvent>(*accountEvent)) - qCDebug(MAIN) << "Users ignored by" << d->userId << "updated:" - << QStringList::fromSet(ignoredUsers()).join(','); - - auto& currentData = d->accountData[accountEvent->matrixType()]; - // A polymorphic event-specific comparison might be a bit more - // efficient; maaybe do it another day - if (!currentData - || currentData->contentJson() != accountEvent->contentJson()) { - currentData = std::move(accountEvent); - qCDebug(MAIN) << "Updated account data of type" - << currentData->matrixType(); - emit accountDataChanged(currentData->matrixType()); - } + DirectChatsMap remoteAdditions; + for (auto it = usersToDCs.begin(); it != usersToDCs.end(); ++it) { + if (auto* u = user(it.key())) { + if (!d->directChats.contains(u, it.value()) + && !d->dcLocalRemovals.contains(u, it.value())) { + Q_ASSERT(!d->directChatUsers.contains(it.value(), u)); + remoteAdditions.insert(u, it.value()); + d->directChats.insert(u, it.value()); + d->directChatUsers.insert(it.value(), u); + qCDebug(MAIN) << "Marked room" << it.value() + << "as a direct chat with" << u->id(); + } + } else + qCWarning(MAIN) + << "Couldn't get a user object for" << it.key(); + } + // Remove from dcLocalAdditions what the server already has. + erase_if(d->dcLocalAdditions, [&remoteAdditions](auto it) { + return remoteAdditions.contains(it.key(), it.value()); + }); + if (!remoteAdditions.isEmpty() || !remoteRemovals.isEmpty()) + emit directChatsListChanged(remoteAdditions, remoteRemovals); + }, + // catch-all, passing eventPtr for a possible take-over + [this, &eventPtr](const Event& accountEvent) { + if (is<IgnoredUsersEvent>(accountEvent)) + qCDebug(MAIN) + << "Users ignored by" << d->userId << "updated:" + << QStringList::fromSet(ignoredUsers()).join(','); + + auto& currentData = d->accountData[accountEvent.matrixType()]; + // A polymorphic event-specific comparison might be a bit more + // efficient; maaybe do it another day + if (!currentData + || currentData->contentJson() != accountEvent.contentJson()) { + currentData = std::move(eventPtr); + qCDebug(MAIN) << "Updated account data of type" + << currentData->matrixType(); + emit accountDataChanged(currentData->matrixType()); + } + }); + } + if (!d->dcLocalAdditions.isEmpty() || !d->dcLocalRemovals.isEmpty()) { + qDebug(MAIN) << "Sending updated direct chats to the server:" + << d->dcLocalRemovals.size() << "removal(s)," + << d->dcLocalAdditions.size() << "addition(s)"; + callApi<SetAccountDataJob>(d->userId, QStringLiteral("m.direct"), + toJson(d->directChats)); + d->dcLocalAdditions.clear(); + d->dcLocalRemovals.clear(); } } @@ -498,7 +547,7 @@ inline auto splitMediaId(const QString& mediaId) auto idParts = mediaId.split('/'); Q_ASSERT_X(idParts.size() == 2, __FUNCTION__, ("'" + mediaId + "' doesn't look like 'serverName/localMediaId'") - .toLatin1()); + .toLatin1()); return idParts; } @@ -511,8 +560,7 @@ MediaThumbnailJob* Connection::getThumbnail(const QString& mediaId, requestedSize); } -MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, - QSize requestedSize, +MediaThumbnailJob* Connection::getThumbnail(const QUrl& url, QSize requestedSize, RunningPolicy policy) const { return getThumbnail(url.authority() + url.path(), requestedSize, policy); @@ -531,10 +579,9 @@ Connection::uploadContent(QIODevice* contentSource, const QString& filename, { auto contentType = overrideContentType; if (contentType.isEmpty()) { - contentType = - QMimeDatabase() - .mimeTypeForFileNameAndData(filename, contentSource) - .name(); + contentType = QMimeDatabase() + .mimeTypeForFileNameAndData(filename, contentSource) + .name(); contentSource->open(QIODevice::ReadOnly); } return callApi<UploadContentJob>(contentSource, filename, contentType); @@ -584,13 +631,23 @@ Connection::createRoom(RoomVisibility visibility, const QString& alias, const QJsonObject& creationContent) { invites.removeOne(d->userId); // The creator is by definition in the room - auto job = callApi<CreateRoomJob>( - visibility == PublishRoom ? QStringLiteral("public") - : QStringLiteral("private"), - alias, name, topic, invites, invite3pids, roomVersion, - creationContent, initialState, presetName, isDirect); - connect(job, &BaseJob::success, this, [this, job] { - emit createdRoom(provideRoom(job->roomId(), JoinState::Join)); + auto job = callApi<CreateRoomJob>(visibility == PublishRoom + ? QStringLiteral("public") + : QStringLiteral("private"), + alias, name, topic, invites, invite3pids, + roomVersion, creationContent, + initialState, presetName, isDirect); + connect(job, &BaseJob::success, this, [this, job, invites, isDirect] { + auto* room = provideRoom(job->roomId(), JoinState::Join); + if (!room) { + Q_ASSERT_X(room, "Connection::createRoom", + "Failed to create a room"); + return; + } + emit createdRoom(room); + if (isDirect) + for (const auto& i : invites) + addToDirectChats(room, user(i)); }); return job; } @@ -600,9 +657,9 @@ void Connection::requestDirectChat(const QString& userId) if (auto* u = user(userId)) requestDirectChat(u); else - qCCritical(MAIN) << "Connection::requestDirectChat: Couldn't get a " - "user object for" - << userId; + qCCritical(MAIN) + << "Connection::requestDirectChat: Couldn't get a user object for" + << userId; } void Connection::requestDirectChat(User* u) @@ -617,8 +674,8 @@ void Connection::doInDirectChat(const QString& userId, doInDirectChat(u, operation); else qCCritical(MAIN) - << "Connection::doInDirectChat: Couldn't get a user object for" - << userId; + << "Connection::doInDirectChat: Couldn't get a user object for" + << userId; } void Connection::doInDirectChat(User* u, @@ -626,8 +683,8 @@ void Connection::doInDirectChat(User* u, { Q_ASSERT(u); const auto& userId = u->id(); - // There can be more than one DC; find the first valid, and delete invalid - // (left/forgotten) ones along the way. + // There can be more than one DC; find the first valid (existing and + // not left), and delete inexistent (forgotten?) ones along the way. DirectChatsMap removals; for (auto it = d->directChats.find(u); it != d->directChats.end() && it.key() == u; ++it) { @@ -648,8 +705,8 @@ void Connection::doInDirectChat(User* u, connect(j, &BaseJob::success, this, [this, roomId, userId, operation] { qCDebug(MAIN) - << "Joined the already invited direct chat with" - << userId << "as" << roomId; + << "Joined the already invited direct chat with" + << userId << "as" << roomId; operation(room(roomId, JoinState::Join)); }); return; @@ -663,6 +720,8 @@ void Connection::doInDirectChat(User* u, << roomId << "is not valid and will be discarded"; // Postpone actual deletion until we finish iterating d->directChats. removals.insert(it.key(), it.value()); + // Add to the list of updates to send to the server upon the next sync. + d->dcLocalRemovals.insert(it.key(), it.value()); } if (!removals.isEmpty()) { for (auto it = removals.cbegin(); it != removals.cend(); ++it) { @@ -670,7 +729,7 @@ void Connection::doInDirectChat(User* u, d->directChatUsers.remove(it.value(), const_cast<User*>(it.key())); // FIXME } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } auto j = createDirectChat(userId); @@ -685,8 +744,8 @@ CreateRoomJob* Connection::createDirectChat(const QString& userId, const QString& topic, const QString& name) { - return createRoom(UnpublishRoom, "", name, topic, { userId }, - "trusted_private_chat", {}, true); + return createRoom(UnpublishRoom, {}, name, topic, { userId }, + QStringLiteral("trusted_private_chat"), {}, true); } ForgetRoomJob* Connection::forgetRoom(const QString& id) @@ -733,18 +792,18 @@ Connection::sendToDevices(const QString& eventType, { QHash<QString, QHash<QString, QJsonObject>> json; json.reserve(int(eventsMap.size())); - std::for_each( - eventsMap.begin(), eventsMap.end(), - [&json](const auto& userTodevicesToEvents) { - auto& jsonUser = json[userTodevicesToEvents.first]; - const auto& devicesToEvents = userTodevicesToEvents.second; - std::for_each(devicesToEvents.begin(), devicesToEvents.end(), - [&jsonUser](const auto& deviceToEvents) { - jsonUser.insert( - deviceToEvents.first, - deviceToEvents.second.contentJson()); - }); - }); + std::for_each(eventsMap.begin(), eventsMap.end(), + [&json](const auto& userTodevicesToEvents) { + auto& jsonUser = json[userTodevicesToEvents.first]; + const auto& devicesToEvents = userTodevicesToEvents.second; + std::for_each(devicesToEvents.begin(), + devicesToEvents.end(), + [&jsonUser](const auto& deviceToEvents) { + jsonUser.insert( + deviceToEvents.first, + deviceToEvents.second.contentJson()); + }); + }); return callApi<SendToDeviceJob>(BackgroundRequest, eventType, generateTxnId(), json); } @@ -760,6 +819,8 @@ SendMessageJob* Connection::sendMessage(const QString& roomId, QUrl Connection::homeserver() const { return d->data->baseUrl(); } +QString Connection::domain() const { return d->userId.section(':', 1); } + Room* Connection::room(const QString& roomId, JoinStates states) const { Room* room = d->roomMap.value({ roomId, false }, nullptr); @@ -794,19 +855,18 @@ void Connection::updateRoomAliases(const QString& roomId, { for (const auto& a : previousRoomAliases) if (d->roomAliasMap.remove(a) == 0) - qCWarning(MAIN) - << "Alias" << a << "is not found (already deleted?)"; + qCWarning(MAIN) << "Alias" << a << "is not found (already deleted?)"; for (const auto& a : roomAliases) { auto& mappedId = d->roomAliasMap[a]; if (!mappedId.isEmpty()) { if (mappedId == roomId) - qCDebug(MAIN) << "Alias" << a << "is already mapped to room" - << roomId; + qCDebug(MAIN) + << "Alias" << a << "is already mapped to room" << roomId; else qCWarning(MAIN) - << "Alias" << a << "will be force-remapped from room" - << mappedId << "to" << roomId; + << "Alias" << a << "will be force-remapped from room" + << mappedId << "to" << roomId; } mappedId = roomId; } @@ -901,7 +961,8 @@ QHash<QString, QVector<Room*>> Connection::tagsToRooms() const { QHash<QString, QVector<Room*>> result; for (auto* r : qAsConst(d->roomMap)) { - for (const auto& tagName : r->tagNames()) + const auto& tagNames = r->tagNames(); + for (const auto& tagName : tagNames) result[tagName].push_back(r); } for (auto it = result.begin(); it != result.end(); ++it) @@ -914,10 +975,12 @@ QHash<QString, QVector<Room*>> Connection::tagsToRooms() const QStringList Connection::tagNames() const { QStringList tags({ FavouriteTag }); - for (auto* r : qAsConst(d->roomMap)) - for (const auto& tag : r->tagNames()) + for (auto* r : qAsConst(d->roomMap)) { + const auto& tagNames = r->tagNames(); + for (const auto& tag : tagNames) if (tag != LowPriorityTag && !tags.contains(tag)) tags.push_back(tag); + } tags.push_back(LowPriorityTag); return tags; } @@ -925,8 +988,7 @@ QStringList Connection::tagNames() const QVector<Room*> Connection::roomsWithTag(const QString& tagName) const { QVector<Room*> rooms; - std::copy_if(d->roomMap.begin(), d->roomMap.end(), - std::back_inserter(rooms), + std::copy_if(d->roomMap.begin(), d->roomMap.end(), std::back_inserter(rooms), [&tagName](Room* r) { return r->tags().contains(tagName); }); return rooms; } @@ -936,27 +998,6 @@ Connection::DirectChatsMap Connection::directChats() const return d->directChats; } -QJsonObject toJson(const Connection::DirectChatsMap& directChats) -{ - QJsonObject json; - for (auto it = directChats.begin(); it != directChats.end();) { - QJsonArray roomIds; - const auto* user = it.key(); - for (; it != directChats.end() && it.key() == user; ++it) - roomIds.append(*it); - json.insert(user->id(), roomIds); - } - return json; -} - -void Connection::Private::broadcastDirectChatUpdates( - const DirectChatsMap& additions, const DirectChatsMap& removals) -{ - q->callApi<SetAccountDataJob>(userId, QStringLiteral("m.direct"), - toJson(directChats)); - emit q->directChatsListChanged(additions, removals); -} - void Connection::addToDirectChats(const Room* room, User* user) { Q_ASSERT(room != nullptr && user != nullptr); @@ -965,8 +1006,8 @@ void Connection::addToDirectChats(const Room* room, User* user) Q_ASSERT(!d->directChatUsers.contains(room->id(), user)); d->directChats.insert(user, room->id()); d->directChatUsers.insert(room->id(), user); - DirectChatsMap additions { { user, room->id() } }; - d->broadcastDirectChatUpdates(additions, {}); + d->dcLocalAdditions.insert(user, room->id()); + emit directChatsListChanged({ { user, room->id() } }, {}); } void Connection::removeFromDirectChats(const QString& roomId, User* user) @@ -978,16 +1019,17 @@ void Connection::removeFromDirectChats(const QString& roomId, User* user) DirectChatsMap removals; if (user != nullptr) { - removals.insert(user, roomId); d->directChats.remove(user, roomId); d->directChatUsers.remove(roomId, user); + removals.insert(user, roomId); + d->dcLocalRemovals.insert(user, roomId); } else { - removals = erase_if(d->directChats, [&roomId](auto it) { - return it.value() == roomId; - }); + removals = erase_if(d->directChats, + [&roomId](auto it) { return it.value() == roomId; }); d->directChatUsers.remove(roomId); + d->dcLocalRemovals += removals; } - d->broadcastDirectChatUpdates({}, removals); + emit directChatsListChanged({}, removals); } bool Connection::isDirectChat(const QString& roomId) const @@ -1094,6 +1136,9 @@ Room* Connection::provideRoom(const QString& id, Omittable<JoinState> joinState) else if (joinState == JoinState::Leave) emit leftRoom(room, prevInvite); if (prevInvite) { + const auto dcUsers = prevInvite->directChatUsers(); + for (auto* u : dcUsers) + addToDirectChats(room, u); qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); emit prevInvite->beforeDestruction(prevInvite); @@ -1141,7 +1186,8 @@ void Connection::saveRoomState(Room* r) const if (!d->cacheState) return; - QFile outRoomFile { stateCachePath() % SyncData::fileNameForRoom(r->id()) }; + QFile outRoomFile { stateCacheDir().filePath( + SyncData::fileNameForRoom(r->id())) }; if (outRoomFile.open(QFile::WriteOnly)) { QJsonDocument json { r->toJson() }; auto data = d->cacheToBinary ? json.toBinaryData() @@ -1162,7 +1208,7 @@ void Connection::saveState() const QElapsedTimer et; et.start(); - QFile outFile { stateCachePath() % "state.json" }; + QFile outFile { d->topLevelStatePath() }; if (!outFile.open(QFile::WriteOnly)) { qCWarning(MAIN) << "Error opening" << outFile.fileName() << ":" << outFile.errorString(); @@ -1174,36 +1220,37 @@ void Connection::saveState() const QJsonObject rootObj { { QStringLiteral("cache_version"), QJsonObject { - { QStringLiteral("major"), SyncData::cacheVersion().first }, - { QStringLiteral("minor"), - SyncData::cacheVersion().second } } } + { QStringLiteral("major"), SyncData::cacheVersion().first }, + { QStringLiteral("minor"), SyncData::cacheVersion().second } } } }; { QJsonObject rooms; QJsonObject inviteRooms; - for (const auto* i : roomMap()) // Pass on rooms in Leave state + const auto& rs = roomMap(); // Pass on rooms in Leave state + for (const auto* i : rs) (i->joinState() == JoinState::Invite ? inviteRooms : rooms) - .insert(i->id(), QJsonValue::Null); + .insert(i->id(), QJsonValue::Null); QJsonObject roomObj; if (!rooms.isEmpty()) - roomObj.insert("join", rooms); + roomObj.insert(QStringLiteral("join"), rooms); if (!inviteRooms.isEmpty()) - roomObj.insert("invite", inviteRooms); + roomObj.insert(QStringLiteral("invite"), inviteRooms); - rootObj.insert("next_batch", d->data->lastEvent()); - rootObj.insert("rooms", roomObj); + rootObj.insert(QStringLiteral("next_batch"), d->data->lastEvent()); + rootObj.insert(QStringLiteral("rooms"), roomObj); } { - QJsonArray accountDataEvents { basicEventJson( - QStringLiteral("m.direct"), toJson(d->directChats)) }; + QJsonArray accountDataEvents { + basicEventJson(QStringLiteral("m.direct"), toJson(d->directChats)) + }; for (const auto& e : d->accountData) accountDataEvents.append( - basicEventJson(e.first, e.second->contentJson())); + basicEventJson(e.first, e.second->contentJson())); - rootObj.insert("account_data", - QJsonObject { { QStringLiteral("events"), - accountDataEvents } }); + rootObj.insert(QStringLiteral("account_data"), + QJsonObject { + { QStringLiteral("events"), accountDataEvents } }); } QJsonDocument json { rootObj }; @@ -1223,7 +1270,7 @@ void Connection::loadState() QElapsedTimer et; et.start(); - SyncData sync { stateCachePath() % "state.json" }; + SyncData sync { d->topLevelStatePath() }; if (sync.nextBatch().isEmpty()) // No token means no cache by definition return; @@ -1235,12 +1282,16 @@ void Connection::loadState() // 1. Do initial sync on failed rooms without saving the nextBatch token // 2. Do the sync across all rooms as normal onSyncSuccess(std::move(sync), true); - qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" - << et; + qCDebug(PROFILER) << "*** Cached state for" << userId() << "loaded in" << et; } QString Connection::stateCachePath() const { + return stateCacheDir().path() % '/'; +} + +QDir Connection::stateCacheDir() const +{ auto safeUserId = userId(); safeUserId.replace(':', '_'); return cacheLocation(safeUserId); @@ -1274,7 +1325,7 @@ void Connection::getTurnServers() } const QString Connection::SupportedRoomVersion::StableTag = - QStringLiteral("stable"); + QStringLiteral("stable"); QString Connection::defaultRoomVersion() const { @@ -1302,8 +1353,7 @@ inline bool roomVersionLess(const Connection::SupportedRoomVersion& v1, return ok1 && ok2 ? vNum1 < vNum2 : v1.id < v2.id; } -QVector<Connection::SupportedRoomVersion> -Connection::availableRoomVersions() const +QVector<Connection::SupportedRoomVersion> Connection::availableRoomVersions() const { Q_ASSERT(!d->capabilities.roomVersions.omitted()); QVector<SupportedRoomVersion> result; @@ -1313,9 +1363,8 @@ Connection::availableRoomVersions() const result.push_back({ it.key(), it.value() }); // Put stable versions over unstable; within each group, // sort numeric versions as numbers, the rest as strings. - const auto mid = - std::partition(result.begin(), result.end(), - std::mem_fn(&SupportedRoomVersion::isStable)); + const auto mid = std::partition(result.begin(), result.end(), + std::mem_fn(&SupportedRoomVersion::isStable)); std::sort(result.begin(), mid, roomVersionLess); std::sort(mid, result.end(), roomVersionLess); diff --git a/lib/connection.h b/lib/connection.h index 50f7b71b..a13def10 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -18,754 +18,768 @@ #pragma once -#include "csapi/create_room.h" -#include "events/accountdataevents.h" #include "joinstate.h" #include "qt_connection_util.h" +#include "csapi/create_room.h" + +#include "events/accountdataevents.h" + +#include <QtCore/QDir> #include <QtCore/QObject> #include <QtCore/QSize> #include <QtCore/QUrl> #include <functional> -#include <memory> - -namespace QMatrixClient { - class Room; - class User; - class ConnectionData; - class RoomEvent; - - class SyncJob; - class SyncData; - class RoomMessagesJob; - class PostReceiptJob; - class ForgetRoomJob; - class MediaThumbnailJob; - class JoinRoomJob; - class UploadContentJob; - class GetContentJob; - class DownloadFileJob; - class SendToDeviceJob; - class SendMessageJob; - class LeaveRoomJob; - - class Connection; - - using room_factory_t = - std::function<Room*(Connection*, const QString&, JoinState)>; - using user_factory_t = std::function<User*(Connection*, const QString&)>; - - /** The default factory to create room objects - * - * Just a wrapper around operator new. - * \sa Connection::setRoomFactory, Connection::setRoomType + +namespace QMatrixClient +{ +class Room; +class User; +class ConnectionData; +class RoomEvent; + +class SyncJob; +class SyncData; +class RoomMessagesJob; +class PostReceiptJob; +class ForgetRoomJob; +class MediaThumbnailJob; +class JoinRoomJob; +class UploadContentJob; +class GetContentJob; +class DownloadFileJob; +class SendToDeviceJob; +class SendMessageJob; +class LeaveRoomJob; + +class Connection; + +using room_factory_t = + std::function<Room*(Connection*, const QString&, JoinState)>; +using user_factory_t = std::function<User*(Connection*, const QString&)>; + +/** The default factory to create room objects + * + * Just a wrapper around operator new. + * \sa Connection::setRoomFactory, Connection::setRoomType + */ +template <typename T = Room> +static inline room_factory_t defaultRoomFactory() +{ + return [](Connection* c, const QString& id, JoinState js) { + return new T(c, id, js); + }; +} + +/** The default factory to create user objects + * + * Just a wrapper around operator new. + * \sa Connection::setUserFactory, Connection::setUserType + */ +template <typename T = User> +static inline user_factory_t defaultUserFactory() +{ + return [](Connection* c, const QString& id) { return new T(id, c); }; +} + +/** Enumeration with flags defining the network job running policy + * So far only background/foreground flags are available. + * + * \sa Connection::callApi + */ +enum RunningPolicy +{ + ForegroundRequest = 0x0, + BackgroundRequest = 0x1 +}; + +class Connection : public QObject +{ + Q_OBJECT + + Q_PROPERTY(User* localUser READ user NOTIFY stateChanged) + Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged) + Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged) + Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged) + Q_PROPERTY(QString defaultRoomVersion READ defaultRoomVersion NOTIFY + capabilitiesLoaded) + Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY + homeserverChanged) + Q_PROPERTY(QString domain READ domain NOTIFY homeserverChanged) + Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY + cacheStateChanged) + Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY + lazyLoadingChanged) + +public: + // Room ids, rather than room pointers, are used in the direct chat + // map types because the library keeps Invite rooms separate from + // rooms in Join and Leave state; and direct chats in account data + // are stored with no regard to their state. + using DirectChatsMap = QMultiHash<const User*, QString>; + using DirectChatUsersMap = QMultiHash<QString, User*>; + using IgnoredUsersList = IgnoredUsersEvent::content_type; + + using UsersToDevicesToEvents = + std::unordered_map<QString, std::unordered_map<QString, const Event&>>; + + enum RoomVisibility + { + PublishRoom, + UnpublishRoom + }; // FIXME: Should go inside CreateRoomJob + + explicit Connection(QObject* parent = nullptr); + explicit Connection(const QUrl& server, QObject* parent = nullptr); + virtual ~Connection(); + + /** Get all Invited and Joined rooms + * \return a hashmap from a composite key - room name and whether + * it's an Invite rather than Join - to room pointers */ - template <typename T = Room> - static inline room_factory_t defaultRoomFactory() + QHash<QPair<QString, bool>, Room*> roomMap() const; + + /** Check whether the account has data of the given type + * Direct chats map is not supported by this method _yet_. + */ + bool hasAccountData(const QString& type) const; + + /** Get a generic account data event of the given type + * This returns an account data event of the given type + * stored on the server. Direct chats map cannot be retrieved + * using this method _yet_; use directChats() instead. + */ + const EventPtr& accountData(const QString& type) const; + + /** Get a generic account data event of the given type + * This returns an account data event of the given type + * stored on the server. Direct chats map cannot be retrieved + * using this method _yet_; use directChats() instead. + */ + template <typename EventT> + const typename EventT::content_type accountData() const { - return [](Connection* c, const QString& id, JoinState js) { - return new T(c, id, js); - }; + if (const auto& eventPtr = accountData(EventT::matrixTypeId())) + return eventPtr->content(); + return {}; } - /** The default factory to create user objects + /** Get account data as a JSON object + * This returns the content part of the account data event + * of the given type. Direct chats map cannot be retrieved using + * this method _yet_; use directChats() instead. + */ + Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const; + + /** Set a generic account data event of the given type */ + void setAccountData(EventPtr&& event); + + Q_INVOKABLE void setAccountData(const QString& type, + const QJsonObject& content); + + /** Get all Invited and Joined rooms grouped by tag + * \return a hashmap from tag name to a vector of room pointers, + * sorted by their order in the tag - details are at + * https://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#id95 + */ + QHash<QString, QVector<Room*>> tagsToRooms() const; + + /** Get all room tags known on this connection */ + QStringList tagNames() const; + + /** Get the list of rooms with the specified tag */ + QVector<Room*> roomsWithTag(const QString& tagName) const; + + /** Mark the room as a direct chat with the user + * This function marks \p room as a direct chat with \p user. + * Emits the signal synchronously, without waiting to complete + * synchronisation with the server. * - * Just a wrapper around operator new. - * \sa Connection::setUserFactory, Connection::setUserType + * \sa directChatsListChanged */ - template <typename T = User> - static inline user_factory_t defaultUserFactory() - { - return [](Connection* c, const QString& id) { return new T(id, c); }; - } + void addToDirectChats(const Room* room, User* user); + + /** Unmark the room from direct chats + * This function removes the room id from direct chats either for + * a specific \p user or for all users if \p user in nullptr. + * The room id is used to allow removal of, e.g., ids of forgotten + * rooms; a Room object need not exist. Emits the signal + * immediately, without waiting to complete synchronisation with + * the server. + * + * \sa directChatsListChanged + */ + void removeFromDirectChats(const QString& roomId, User* user = nullptr); + + /** Check whether the room id corresponds to a direct chat */ + bool isDirectChat(const QString& roomId) const; - /** Enumeration with flags defining the network job running policy - * So far only background/foreground flags are available. + /** Get the whole map from users to direct chat rooms */ + DirectChatsMap directChats() const; + + /** Retrieve the list of users the room is a direct chat with + * @return The list of users for which this room is marked as + * a direct chat; an empty list if the room is not a direct chat + */ + QList<User*> directChatUsers(const Room* room) const; + + /** Check whether a particular user is in the ignore list */ + Q_INVOKABLE bool isIgnored(const User* user) const; + + /** Get the whole list of ignored users */ + Q_INVOKABLE IgnoredUsersList ignoredUsers() const; + + /** Add the user to the ignore list + * The change signal is emitted synchronously, without waiting + * to complete synchronisation with the server. * - * \sa Connection::callApi + * \sa ignoredUsersListChanged */ - enum RunningPolicy { ForegroundRequest = 0x0, BackgroundRequest = 0x1 }; + Q_INVOKABLE void addToIgnoredUsers(const User* user); - class Connection : public QObject + /** Remove the user from the ignore list */ + /** Similar to adding, the change signal is emitted synchronously. + * + * \sa ignoredUsersListChanged + */ + Q_INVOKABLE void removeFromIgnoredUsers(const User* user); + + /** Get the full list of users known to this account */ + QMap<QString, User*> users() const; + + /** Get the base URL of the homeserver to connect to */ + QUrl homeserver() const; + /** Get the domain name used for ids/aliases on the server */ + QString domain() const; + /** Find a room by its id and a mask of applicable states */ + Q_INVOKABLE Room* room(const QString& roomId, + JoinStates states = JoinState::Invite + | JoinState::Join) const; + /** Find a room by its alias and a mask of applicable states */ + Q_INVOKABLE Room* roomByAlias(const QString& roomAlias, + JoinStates states = JoinState::Invite + | JoinState::Join) const; + /** Update the internal map of room aliases to IDs */ + /// This is used for internal bookkeeping of rooms. Do NOT use + /// it to try change aliases, use Room::setAliases instead + void updateRoomAliases(const QString& roomId, + const QStringList& previousRoomAliases, + const QStringList& roomAliases); + Q_INVOKABLE Room* invitation(const QString& roomId) const; + Q_INVOKABLE User* user(const QString& userId); + const User* user() const; + User* user(); + QString userId() const; + QString deviceId() const; + QByteArray accessToken() const; + Q_INVOKABLE SyncJob* syncJob() const; + Q_INVOKABLE int millisToReconnect() const; + + [[deprecated("Use accessToken() instead")]] Q_INVOKABLE QString token() const; + Q_INVOKABLE void getTurnServers(); + + struct SupportedRoomVersion { - Q_OBJECT - - /** Whether or not the rooms state should be cached locally - * \sa loadState(), saveState() - */ - Q_PROPERTY(User* localUser READ user NOTIFY stateChanged) - Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged) - Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged) - Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged) - Q_PROPERTY(QString defaultRoomVersion READ defaultRoomVersion NOTIFY - capabilitiesLoaded) - 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) - - public: - // Room ids, rather than room pointers, are used in the direct chat - // map types because the library keeps Invite rooms separate from - // rooms in Join and Leave state; and direct chats in account data - // are stored with no regard to their state. - using DirectChatsMap = QMultiHash<const User*, QString>; - using DirectChatUsersMap = QMultiHash<QString, User*>; - using IgnoredUsersList = IgnoredUsersEvent::content_type; - - using UsersToDevicesToEvents = - std::unordered_map<QString, - std::unordered_map<QString, const Event&>>; - - enum RoomVisibility { - PublishRoom, - UnpublishRoom - }; // FIXME: Should go inside CreateRoomJob - - explicit Connection(QObject* parent = nullptr); - explicit Connection(const QUrl& server, QObject* parent = nullptr); - virtual ~Connection(); - - /** Get all Invited and Joined rooms - * \return a hashmap from a composite key - room name and whether - * it's an Invite rather than Join - to room pointers - */ - QHash<QPair<QString, bool>, Room*> roomMap() const; - - /** Check whether the account has data of the given type - * Direct chats map is not supported by this method _yet_. - */ - bool hasAccountData(const QString& type) const; - - /** Get a generic account data event of the given type - * This returns an account data event of the given type - * stored on the server. Direct chats map cannot be retrieved - * using this method _yet_; use directChats() instead. - */ - const EventPtr& accountData(const QString& type) const; - - /** Get a generic account data event of the given type - * This returns an account data event of the given type - * stored on the server. Direct chats map cannot be retrieved - * using this method _yet_; use directChats() instead. - */ - template <typename EventT> - const typename EventT::content_type accountData() const - { - if (const auto& eventPtr = accountData(EventT::matrixTypeId())) - return eventPtr->content(); - return {}; - } + QString id; + QString status; - /** Get account data as a JSON object - * This returns the content part of the account data event - * of the given type. Direct chats map cannot be retrieved using - * this method _yet_; use directChats() instead. - */ - Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const; - - /** Set a generic account data event of the given type */ - void setAccountData(EventPtr&& event); - - Q_INVOKABLE void setAccountData(const QString& type, - const QJsonObject& content); - - /** Get all Invited and Joined rooms grouped by tag - * \return a hashmap from tag name to a vector of room pointers, - * sorted by their order in the tag - details are at - * https://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#id95 - */ - QHash<QString, QVector<Room*>> tagsToRooms() const; - - /** Get all room tags known on this connection */ - QStringList tagNames() const; - - /** Get the list of rooms with the specified tag */ - QVector<Room*> roomsWithTag(const QString& tagName) const; - - /** Mark the room as a direct chat with the user - * This function marks \p room as a direct chat with \p user. - * Emits the signal synchronously, without waiting to complete - * synchronisation with the server. - * - * \sa directChatsListChanged - */ - void addToDirectChats(const Room* room, User* user); - - /** Unmark the room from direct chats - * This function removes the room id from direct chats either for - * a specific \p user or for all users if \p user in nullptr. - * The room id is used to allow removal of, e.g., ids of forgotten - * rooms; a Room object need not exist. Emits the signal - * immediately, without waiting to complete synchronisation with - * the server. - * - * \sa directChatsListChanged - */ - void removeFromDirectChats(const QString& roomId, User* user = nullptr); - - /** Check whether the room id corresponds to a direct chat */ - bool isDirectChat(const QString& roomId) const; - - /** Get the whole map from users to direct chat rooms */ - DirectChatsMap directChats() const; - - /** Retrieve the list of users the room is a direct chat with - * @return The list of users for which this room is marked as - * a direct chat; an empty list if the room is not a direct chat - */ - QList<User*> directChatUsers(const Room* room) const; - - /** Check whether a particular user is in the ignore list */ - bool isIgnored(const User* user) const; - - /** Get the whole list of ignored users */ - IgnoredUsersList ignoredUsers() const; - - /** Add the user to the ignore list - * The change signal is emitted synchronously, without waiting - * to complete synchronisation with the server. - * - * \sa ignoredUsersListChanged - */ - void addToIgnoredUsers(const User* user); - - /** Remove the user from the ignore list */ - /** Similar to adding, the change signal is emitted synchronously. - * - * \sa ignoredUsersListChanged - */ - void removeFromIgnoredUsers(const User* user); - - /** Get the full list of users known to this account */ - QMap<QString, User*> users() const; - - QUrl homeserver() const; - /** Find a room by its id and a mask of applicable states */ - Q_INVOKABLE Room* room(const QString& roomId, - JoinStates states = JoinState::Invite - | JoinState::Join) const; - /** Find a room by its alias and a mask of applicable states */ - Q_INVOKABLE Room* roomByAlias(const QString& roomAlias, - JoinStates states = JoinState::Invite - | JoinState::Join) const; - /** Update the internal map of room aliases to IDs */ - /// This is used for internal bookkeeping of rooms. Do NOT use - /// it to try change aliases, use Room::setAliases instead - void updateRoomAliases(const QString& roomId, - const QStringList& previousRoomAliases, - const QStringList& roomAliases); - Q_INVOKABLE Room* invitation(const QString& roomId) const; - Q_INVOKABLE User* user(const QString& userId); - const User* user() const; - User* user(); - QString userId() const; - QString deviceId() const; - QByteArray accessToken() const; - Q_INVOKABLE SyncJob* syncJob() const; - Q_INVOKABLE int millisToReconnect() const; - - [[deprecated("Use accessToken() instead")]] Q_INVOKABLE QString - token() const; - Q_INVOKABLE void getTurnServers(); - - struct SupportedRoomVersion { - QString id; - QString status; - - static const QString StableTag; // "stable", as of CS API 0.5 - bool isStable() const { return status == StableTag; } - - friend QDebug operator<<(QDebug dbg, const SupportedRoomVersion& v) - { - QDebugStateSaver _(dbg); - return dbg.nospace() << v.id << '/' << v.status; - } - }; - - /// Get the room version recommended by the server - /** Only works after server capabilities have been loaded. - * \sa loadingCapabilities */ - QString defaultRoomVersion() const; - /// Get the room version considered stable by the server - /** Only works after server capabilities have been loaded. - * \sa loadingCapabilities */ - QStringList stableRoomVersions() const; - /// Get all room versions supported by the server - /** Only works after server capabilities have been loaded. - * \sa loadingCapabilities */ - QVector<SupportedRoomVersion> availableRoomVersions() const; - - /** - * Call this before first sync to load from previously saved file. - * - * \param fromFile A local path to read the state from. Uses QUrl - * to be QML-friendly. Empty parameter means using a path - * defined by stateCachePath(). - */ - Q_INVOKABLE void loadState(); - /** - * This method saves the current state of rooms (but not messages - * in them) to a local cache file, so that it could be loaded by - * loadState() on a next run of the client. - * - * \param toFile A local path to save the state to. Uses QUrl to be - * QML-friendly. Empty parameter means using a path defined by - * stateCachePath(). - */ - Q_INVOKABLE void saveState() const; - - /// This method saves the current state of a single room. - void saveRoomState(Room* r) const; - - /** - * The default path to store the cached room state, defined as - * follows: - * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) - * + _safeUserId + "_state.json" where `_safeUserId` is userId() with - * `:` (colon) replaced with - * `_` (underscore) - * /see loadState(), saveState() - */ - Q_INVOKABLE QString stateCachePath() const; - - bool cacheState() const; - void setCacheState(bool newValue); - - bool lazyLoading() const; - void setLazyLoading(bool newValue); - - /** Start a job of a specified type with specified arguments and policy - * - * This is a universal method to start a job of a type passed - * as a template parameter. The policy allows to fine-tune the way - * the job is executed - as of this writing it means a choice - * between "foreground" and "background". - * - * \param runningPolicy controls how the job is executed - * \param jobArgs arguments to the job constructor - * - * \sa BaseJob::isBackground. - * QNetworkRequest::BackgroundRequestAttribute - */ - template <typename JobT, typename... JobArgTs> - JobT* callApi(RunningPolicy runningPolicy, JobArgTs&&... jobArgs) const - { - auto job = new JobT(std::forward<JobArgTs>(jobArgs)...); - connect(job, &BaseJob::failure, this, &Connection::requestFailed); - job->start(connectionData(), runningPolicy & BackgroundRequest); - return job; - } + static const QString StableTag; // "stable", as of CS API 0.5 + bool isStable() const { return status == StableTag; } - /** Start a job of a specified type with specified arguments - * - * This is an overload that calls the job with "foreground" policy. - */ - template <typename JobT, typename... JobArgTs> - JobT* callApi(JobArgTs&&... jobArgs) const + friend QDebug operator<<(QDebug dbg, const SupportedRoomVersion& v) { - return callApi<JobT>(ForegroundRequest, - std::forward<JobArgTs>(jobArgs)...); + QDebugStateSaver _(dbg); + return dbg.nospace() << v.id << '/' << v.status; } + }; - /** Generate a new transaction id. Transaction id's are unique within - * a single Connection object - */ - Q_INVOKABLE QByteArray generateTxnId() const; + /// Get the room version recommended by the server + /** Only works after server capabilities have been loaded. + * \sa loadingCapabilities */ + QString defaultRoomVersion() const; + /// Get the room version considered stable by the server + /** Only works after server capabilities have been loaded. + * \sa loadingCapabilities */ + QStringList stableRoomVersions() const; + /// Get all room versions supported by the server + /** Only works after server capabilities have been loaded. + * \sa loadingCapabilities */ + QVector<SupportedRoomVersion> availableRoomVersions() const; + + /** + * Call this before first sync to load from previously saved file. + * + * \param fromFile A local path to read the state from. Uses QUrl + * to be QML-friendly. Empty parameter means saving to the directory + * defined by stateCachePath() / stateCacheDir(). + */ + Q_INVOKABLE void loadState(); + /** + * This method saves the current state of rooms (but not messages + * in them) to a local cache file, so that it could be loaded by + * loadState() on a next run of the client. + * + * \param toFile A local path to save the state to. Uses QUrl to be + * QML-friendly. Empty parameter means saving to the directory + * defined by stateCachePath() / stateCacheDir(). + */ + Q_INVOKABLE void saveState() const; + + /// This method saves the current state of a single room. + void saveRoomState(Room* r) const; + + /// Get the default directory path to save the room state to + /** \sa stateCacheDir */ + Q_INVOKABLE QString stateCachePath() const; + + /// Get the default directory to save the room state to + /** + * This function returns the default directory to store the cached + * room state, defined as follows: + * \code + * QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) + + * _safeUserId + "_state.json" \endcode where `_safeUserId` is userId() with + * `:` (colon) replaced by + * `_` (underscore), as colons are reserved characters on Windows. + * \sa loadState, saveState, stateCachePath + */ + QDir stateCacheDir() const; - /// Set a room factory function - static void setRoomFactory(room_factory_t f); + /** Whether or not the rooms state should be cached locally + * \sa loadState(), saveState() + */ + bool cacheState() const; + void setCacheState(bool newValue); - /// Set a user factory function - static void setUserFactory(user_factory_t f); + bool lazyLoading() const; + void setLazyLoading(bool newValue); - /// Get a room factory function - static room_factory_t roomFactory(); + /** Start a job of a specified type with specified arguments and policy + * + * This is a universal method to start a job of a type passed + * as a template parameter. The policy allows to fine-tune the way + * the job is executed - as of this writing it means a choice + * between "foreground" and "background". + * + * \param runningPolicy controls how the job is executed + * \param jobArgs arguments to the job constructor + * + * \sa BaseJob::isBackground. QNetworkRequest::BackgroundRequestAttribute + */ + template <typename JobT, typename... JobArgTs> + JobT* callApi(RunningPolicy runningPolicy, JobArgTs&&... jobArgs) const + { + auto job = new JobT(std::forward<JobArgTs>(jobArgs)...); + connect(job, &BaseJob::failure, this, &Connection::requestFailed); + job->start(connectionData(), runningPolicy & BackgroundRequest); + return job; + } + + /** Start a job of a specified type with specified arguments + * + * This is an overload that calls the job with "foreground" policy. + */ + template <typename JobT, typename... JobArgTs> + JobT* callApi(JobArgTs&&... jobArgs) const + { + return callApi<JobT>(ForegroundRequest, + std::forward<JobArgTs>(jobArgs)...); + } - /// Get a user factory function - static user_factory_t userFactory(); + /** Generate a new transaction id. Transaction id's are unique within + * a single Connection object + */ + Q_INVOKABLE QByteArray generateTxnId() const; - /// Set the room factory to default with the overriden room type - template <typename T> static void setRoomType() - { - setRoomFactory(defaultRoomFactory<T>()); - } + /// Set a room factory function + static void setRoomFactory(room_factory_t f); - /// Set the user factory to default with the overriden user type - template <typename T> static void setUserType() - { - setUserFactory(defaultUserFactory<T>()); - } + /// Set a user factory function + static void setUserFactory(user_factory_t f); - public slots: - /** Set the homeserver base URL */ - void setHomeserver(const QUrl& baseUrl); - - /** Determine and set the homeserver from domain or MXID */ - void resolveServer(const QString& mxidOrDomain); - - void connectToServer(const QString& user, const QString& password, - const QString& initialDeviceName, - const QString& deviceId = {}); - void connectWithToken(const QString& userId, const QString& accessToken, - const QString& deviceId); - /// Explicitly request capabilities from the server - void reloadCapabilities(); - - /// Find out if capabilites are still loading from the server - bool loadingCapabilities() const; - - /** @deprecated Use stopSync() instead */ - void disconnectFromServer() { stopSync(); } - void logout(); - - void sync(int timeout = -1); - void syncLoop(int timeout = -1); - - void stopSync(); - QString nextBatchToken() const; - - virtual MediaThumbnailJob* - getThumbnail(const QString& mediaId, QSize requestedSize, - RunningPolicy policy = BackgroundRequest) const; - MediaThumbnailJob* - getThumbnail(const QUrl& url, QSize requestedSize, - RunningPolicy policy = BackgroundRequest) const; - MediaThumbnailJob* - getThumbnail(const QUrl& url, int requestedWidth, int requestedHeight, - RunningPolicy policy = BackgroundRequest) const; - - // QIODevice* should already be open - UploadContentJob* - uploadContent(QIODevice* contentSource, const QString& filename = {}, - const QString& overrideContentType = {}) const; - UploadContentJob* uploadFile(const QString& fileName, - const QString& overrideContentType = {}); - GetContentJob* getContent(const QString& mediaId) const; - GetContentJob* getContent(const QUrl& url) const; - // If localFilename is empty, a temporary file will be created - DownloadFileJob* downloadFile(const QUrl& url, - const QString& localFilename = {}) const; - - /** - * \brief Create a room (generic method) - * This method allows to customize room entirely to your liking, - * providing all the attributes the original CS API provides. - */ - CreateRoomJob* - createRoom(RoomVisibility visibility, const QString& alias, - const QString& name, const QString& topic, - QStringList invites, const QString& presetName = {}, - const QString& roomVersion = {}, bool isDirect = false, - const QVector<CreateRoomJob::StateEvent>& initialState = {}, - const QVector<CreateRoomJob::Invite3pid>& invite3pids = {}, - const QJsonObject& creationContent = {}); - - /** Get a direct chat with a single user - * This method may return synchronously or asynchoronously depending - * on whether a direct chat room with the respective person exists - * already. - * - * \sa directChatAvailable - */ - void requestDirectChat(const QString& userId); - - /** Get a direct chat with a single user - * This method may return synchronously or asynchoronously depending - * on whether a direct chat room with the respective person exists - * already. - * - * \sa directChatAvailable - */ - void requestDirectChat(User* u); - - /** Run an operation in a direct chat with the user - * This method may return synchronously or asynchoronously depending - * on whether a direct chat room with the respective person exists - * already. Instead of emitting a signal it executes the passed - * function object with the direct chat room as its parameter. - */ - void doInDirectChat(const QString& userId, - const std::function<void(Room*)>& operation); - - /** Run an operation in a direct chat with the user - * This method may return synchronously or asynchoronously depending - * on whether a direct chat room with the respective person exists - * already. Instead of emitting a signal it executes the passed - * function object with the direct chat room as its parameter. - */ - void doInDirectChat(User* u, - const std::function<void(Room*)>& operation); - - /** Create a direct chat with a single user, optional name and topic - * A room will always be created, unlike in requestDirectChat. - * It is advised to use requestDirectChat as a default way of getting - * one-on-one with a person, and only use createDirectChat when - * a new creation is explicitly desired. - */ - CreateRoomJob* createDirectChat(const QString& userId, - const QString& topic = {}, - const QString& name = {}); - - virtual JoinRoomJob* joinRoom(const QString& roomAlias, - const QStringList& serverNames = {}); - - /** Sends /forget to the server and also deletes room locally. - * This method is in Connection, not in Room, since it's a - * room lifecycle operation, and Connection is an acting room manager. - * It ensures that the local user is not a member of a room (running - * /leave, if necessary) then issues a /forget request and if that one - * doesn't fail deletion of the local Room object is ensured. \param id - * - the room id to forget \return - the ongoing /forget request to the - * server; note that the success() signal of this request is connected - * to deleteLater() of a respective room so by the moment this finishes, - * there might be no Room object anymore. - */ - ForgetRoomJob* forgetRoom(const QString& id); - - SendToDeviceJob* - sendToDevices(const QString& eventType, - const UsersToDevicesToEvents& eventsMap) const; - - /** \deprecated This method is experimental and may be removed any time - */ - SendMessageJob* sendMessage(const QString& roomId, - const RoomEvent& event) const; - - /** \deprecated Do not use this directly, use Room::leaveRoom() instead - */ - virtual LeaveRoomJob* leaveRoom(Room* room); - - // Old API that will be abolished any time soon. DO NOT USE. - - /** @deprecated Use callApi<PostReceiptJob>() or Room::postReceipt() - * instead */ - virtual PostReceiptJob* postReceipt(Room* room, RoomEvent* event) const; - signals: - /** - * @deprecated - * This was a signal resulting from a successful resolveServer(). - * Since Connection now provides setHomeserver(), the HS URL - * may change even without resolveServer() invocation. Use - * homeserverChanged() instead of resolved(). You can also use - * connectToServer and connectWithToken without the HS URL set in - * advance (i.e. without calling resolveServer), as they now trigger - * server name resolution from MXID if the server URL is not valid. - */ - void resolved(); - void resolveError(QString error); - - void homeserverChanged(QUrl baseUrl); - void capabilitiesLoaded(); - - void connected(); - void reconnected(); //< \deprecated Use connected() instead - void loggedOut(); - /** Login data or state have changed - * - * This is a common change signal for userId, deviceId and - * accessToken - these properties normally only change at - * a successful login and logout and are constant at other times. - */ - void stateChanged(); - void loginError(QString message, QString details); - - /** A network request (job) failed - * - * @param request - the pointer to the failed job - */ - void requestFailed(BaseJob* request); - - /** A network request (job) failed due to network problems - * - * This is _only_ emitted when the job will retry on its own; - * once it gives up, requestFailed() will be emitted. - * - * @param message - message about the network problem - * @param details - raw error details, if any available - * @param retriesTaken - how many retries have already been taken - * @param nextRetryInMilliseconds - when the job will retry again - */ - void networkError(QString message, QString details, int retriesTaken, - int nextRetryInMilliseconds); - - void syncDone(); - void syncError(QString message, QString details); - - void newUser(User* user); - - /** - * \group Signals emitted on room transitions - * - * Note: Rooms in Invite state are always stored separately from - * rooms in Join/Leave state, because of special treatment of - * invite_state in Matrix CS API (see The Spec on /sync for details). - * Therefore, objects below are: r - room in Join/Leave state; - * i - room in Invite state - * - * 1. none -> Invite: newRoom(r), invitedRoom(r,nullptr) - * 2. none -> Join: newRoom(r), joinedRoom(r,nullptr) - * 3. none -> Leave: newRoom(r), leftRoom(r,nullptr) - * 4. Invite -> Join: - * newRoom(r), joinedRoom(r,i), aboutToDeleteRoom(i) - * 4a. Leave and Invite -> Join: - * joinedRoom(r,i), aboutToDeleteRoom(i) - * 5. Invite -> Leave: - * newRoom(r), leftRoom(r,i), aboutToDeleteRoom(i) - * 5a. Leave and Invite -> Leave: - * leftRoom(r,i), aboutToDeleteRoom(i) - * 6. Join -> Leave: leftRoom(r) - * 7. Leave -> Invite: newRoom(i), invitedRoom(i,r) - * 8. Leave -> Join: joinedRoom(r) - * The following transitions are only possible via forgetRoom() - * so far; if a room gets forgotten externally, sync won't tell - * about it: - * 9. any -> none: as any -> Leave, then aboutToDeleteRoom(r) - */ - - /** A new room object has been created */ - void newRoom(Room* room); - - /** A room invitation is seen for the first time - * - * If the same room is in Left state, it's passed in prev. Beware - * that initial sync will trigger this signal for all rooms in - * Invite state. - */ - void invitedRoom(Room* room, Room* prev); - - /** A joined room is seen for the first time - * - * It's not the same as receiving a room in "join" section of sync - * response (rooms will be there even after joining); it's also - * not (exactly) the same as actual joining action of a user (all - * rooms coming in initial sync will trigger this signal too). If - * this room was in Invite state before, the respective object is - * passed in prev (and it will be deleted shortly afterwards). - */ - void joinedRoom(Room* room, Room* prev); - - /** A room has just been left - * - * If this room has been in Invite state (as in case of rejecting - * an invitation), the respective object will be passed in prev - * (and will be deleted shortly afterwards). Note that, similar - * to invitedRoom and joinedRoom, this signal is triggered for all - * Left rooms upon initial sync (not only those that were left - * right before the sync). - */ - void leftRoom(Room* room, Room* prev); - - /** The room object is about to be deleted */ - void aboutToDeleteRoom(Room* room); - - /** The room has just been created by createRoom or requestDirectChat - * - * This signal is not emitted in usual room state transitions, - * only as an outcome of room creation operations invoked by - * the client. - * \note requestDirectChat doesn't necessarily create a new chat; - * use directChatAvailable signal if you just need to obtain - * a direct chat room. - */ - void createdRoom(Room* room); - - /** The first sync for the room has been completed - * - * This signal is emitted after the room has been synced the first - * time. This is the right signal to connect to if you need to - * access the room state (name, aliases, members); state transition - * signals (newRoom, joinedRoom etc.) come earlier, when the room - * has just been created. - */ - void loadedRoomState(Room* room); - - /** Account data (except direct chats) have changed */ - void accountDataChanged(QString type); - - /** The direct chat room is ready for using - * This signal is emitted upon any successful outcome from - * requestDirectChat. - */ - void directChatAvailable(Room* directChat); - - /** The list of direct chats has changed - * This signal is emitted every time when the mapping of users - * to direct chat rooms is changed (because of either local updates - * or a different list arrived from the server). - */ - void directChatsListChanged(DirectChatsMap additions, - DirectChatsMap removals); - - void ignoredUsersListChanged(IgnoredUsersList additions, - IgnoredUsersList removals); - - void cacheStateChanged(); - void lazyLoadingChanged(); - void turnServersChanged(const QJsonObject& servers); - - protected: - /** - * @brief Access the underlying ConnectionData class - */ - const ConnectionData* connectionData() const; - - /** Get a Room object for the given id in the given state - * - * Use this method when you need a Room object in the local list - * of rooms, with the given state. Note that this does not interact - * with the server; in particular, does not automatically create - * rooms on the server. This call performs necessary join state - * transitions; e.g., if it finds a room in Invite but - * `joinState == JoinState::Join` then the Invite room object - * will be deleted and a new room object with Join state created. - * In contrast, switching between Join and Leave happens within - * the same object. - * \param roomId room id (not alias!) - * \param joinState desired (target) join state of the room; if - * omitted, any state will be found and return unchanged, or a - * new Join room created. - * @return a pointer to a Room object with the specified id and the - * specified state; nullptr if roomId is empty or if roomFactory() - * failed to create a Room object. - */ - Room* provideRoom(const QString& roomId, - Omittable<JoinState> joinState = none); - - /** - * Completes loading sync data. - */ - void onSyncSuccess(SyncData&& data, bool fromCache = false); - - protected slots: - void syncLoopIteration(); - - private: - class Private; - std::unique_ptr<Private> d; - - /** - * A single entry for functions that need to check whether the - * homeserver is valid before running. May either execute connectFn - * synchronously or asynchronously (if tryResolve is true and - * a DNS lookup is initiated); in case of errors, emits resolveError - * if the homeserver URL is not valid and cannot be resolved from - * userId. - * - * @param userId - fully-qualified MXID to resolve HS from - * @param connectFn - a function to execute once the HS URL is good - */ - void checkAndConnect(const QString& userId, - std::function<void()> connectFn); - void doConnectToServer(const QString& user, const QString& password, - const QString& initialDeviceName, - const QString& deviceId = {}); - - static room_factory_t _roomFactory; - static user_factory_t _userFactory; - }; + /// Get a room factory function + static room_factory_t roomFactory(); + + /// Get a user factory function + static user_factory_t userFactory(); + + /// Set the room factory to default with the overriden room type + template <typename T> + static void setRoomType() + { + setRoomFactory(defaultRoomFactory<T>()); + } + + /// Set the user factory to default with the overriden user type + template <typename T> + static void setUserType() + { + setUserFactory(defaultUserFactory<T>()); + } + +public slots: + /** Set the homeserver base URL */ + void setHomeserver(const QUrl& baseUrl); + + /** Determine and set the homeserver from domain or MXID */ + void resolveServer(const QString& mxidOrDomain); + + void connectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId = {}); + void connectWithToken(const QString& userId, const QString& accessToken, + const QString& deviceId); + /// Explicitly request capabilities from the server + void reloadCapabilities(); + + /// Find out if capabilites are still loading from the server + bool loadingCapabilities() const; + + /** @deprecated Use stopSync() instead */ + void disconnectFromServer() { stopSync(); } + void logout(); + + void sync(int timeout = -1); + void syncLoop(int timeout = -1); + + void stopSync(); + QString nextBatchToken() const; + + virtual MediaThumbnailJob* + getThumbnail(const QString& mediaId, QSize requestedSize, + RunningPolicy policy = BackgroundRequest) const; + MediaThumbnailJob* + getThumbnail(const QUrl& url, QSize requestedSize, + RunningPolicy policy = BackgroundRequest) const; + MediaThumbnailJob* + getThumbnail(const QUrl& url, int requestedWidth, int requestedHeight, + RunningPolicy policy = BackgroundRequest) const; + + // QIODevice* should already be open + UploadContentJob* + uploadContent(QIODevice* contentSource, const QString& filename = {}, + const QString& overrideContentType = {}) const; + UploadContentJob* uploadFile(const QString& fileName, + const QString& overrideContentType = {}); + GetContentJob* getContent(const QString& mediaId) const; + GetContentJob* getContent(const QUrl& url) const; + // If localFilename is empty, a temporary file will be created + DownloadFileJob* downloadFile(const QUrl& url, + const QString& localFilename = {}) const; + + /** + * \brief Create a room (generic method) + * This method allows to customize room entirely to your liking, + * providing all the attributes the original CS API provides. + */ + CreateRoomJob* + createRoom(RoomVisibility visibility, const QString& alias, + const QString& name, const QString& topic, QStringList invites, + const QString& presetName = {}, const QString& roomVersion = {}, + bool isDirect = false, + const QVector<CreateRoomJob::StateEvent>& initialState = {}, + const QVector<CreateRoomJob::Invite3pid>& invite3pids = {}, + const QJsonObject& creationContent = {}); + + /** Get a direct chat with a single user + * This method may return synchronously or asynchoronously depending + * on whether a direct chat room with the respective person exists + * already. + * + * \sa directChatAvailable + */ + void requestDirectChat(const QString& userId); + + /** Get a direct chat with a single user + * This method may return synchronously or asynchoronously depending + * on whether a direct chat room with the respective person exists + * already. + * + * \sa directChatAvailable + */ + void requestDirectChat(User* u); + + /** Run an operation in a direct chat with the user + * This method may return synchronously or asynchoronously depending + * on whether a direct chat room with the respective person exists + * already. Instead of emitting a signal it executes the passed + * function object with the direct chat room as its parameter. + */ + void doInDirectChat(const QString& userId, + const std::function<void(Room*)>& operation); + + /** Run an operation in a direct chat with the user + * This method may return synchronously or asynchoronously depending + * on whether a direct chat room with the respective person exists + * already. Instead of emitting a signal it executes the passed + * function object with the direct chat room as its parameter. + */ + void doInDirectChat(User* u, const std::function<void(Room*)>& operation); + + /** Create a direct chat with a single user, optional name and topic + * A room will always be created, unlike in requestDirectChat. + * It is advised to use requestDirectChat as a default way of getting + * one-on-one with a person, and only use createDirectChat when + * a new creation is explicitly desired. + */ + CreateRoomJob* createDirectChat(const QString& userId, + const QString& topic = {}, + const QString& name = {}); + + virtual JoinRoomJob* joinRoom(const QString& roomAlias, + const QStringList& serverNames = {}); + + /** Sends /forget to the server and also deletes room locally. + * This method is in Connection, not in Room, since it's a + * room lifecycle operation, and Connection is an acting room manager. + * It ensures that the local user is not a member of a room (running /leave, + * if necessary) then issues a /forget request and if that one doesn't fail + * deletion of the local Room object is ensured. + * \param id - the room id to forget + * \return - the ongoing /forget request to the server; note that the + * success() signal of this request is connected to deleteLater() + * of a respective room so by the moment this finishes, there might be no + * Room object anymore. + */ + ForgetRoomJob* forgetRoom(const QString& id); + + SendToDeviceJob* sendToDevices(const QString& eventType, + const UsersToDevicesToEvents& eventsMap) const; + + /** \deprecated This method is experimental and may be removed any time */ + SendMessageJob* sendMessage(const QString& roomId, + const RoomEvent& event) const; + + /** \deprecated Do not use this directly, use Room::leaveRoom() instead */ + virtual LeaveRoomJob* leaveRoom(Room* room); + + // Old API that will be abolished any time soon. DO NOT USE. + + /** @deprecated Use callApi<PostReceiptJob>() or Room::postReceipt() instead + */ + virtual PostReceiptJob* postReceipt(Room* room, RoomEvent* event) const; +signals: + /** + * @deprecated + * This was a signal resulting from a successful resolveServer(). + * Since Connection now provides setHomeserver(), the HS URL + * may change even without resolveServer() invocation. Use + * homeserverChanged() instead of resolved(). You can also use + * connectToServer and connectWithToken without the HS URL set in + * advance (i.e. without calling resolveServer), as they now trigger + * server name resolution from MXID if the server URL is not valid. + */ + void resolved(); + void resolveError(QString error); + + void homeserverChanged(QUrl baseUrl); + void capabilitiesLoaded(); + + void connected(); + void reconnected(); //< \deprecated Use connected() instead + void loggedOut(); + /** Login data or state have changed + * + * This is a common change signal for userId, deviceId and + * accessToken - these properties normally only change at + * a successful login and logout and are constant at other times. + */ + void stateChanged(); + void loginError(QString message, QString details); + + /** A network request (job) failed + * + * @param request - the pointer to the failed job + */ + void requestFailed(BaseJob* request); + + /** A network request (job) failed due to network problems + * + * This is _only_ emitted when the job will retry on its own; + * once it gives up, requestFailed() will be emitted. + * + * @param message - message about the network problem + * @param details - raw error details, if any available + * @param retriesTaken - how many retries have already been taken + * @param nextRetryInMilliseconds - when the job will retry again + */ + void networkError(QString message, QString details, int retriesTaken, + int nextRetryInMilliseconds); + + void syncDone(); + void syncError(QString message, QString details); + + void newUser(User* user); + + /** + * \group Signals emitted on room transitions + * + * Note: Rooms in Invite state are always stored separately from + * rooms in Join/Leave state, because of special treatment of + * invite_state in Matrix CS API (see The Spec on /sync for details). + * Therefore, objects below are: r - room in Join/Leave state; + * i - room in Invite state + * + * 1. none -> Invite: newRoom(r), invitedRoom(r,nullptr) + * 2. none -> Join: newRoom(r), joinedRoom(r,nullptr) + * 3. none -> Leave: newRoom(r), leftRoom(r,nullptr) + * 4. Invite -> Join: + * newRoom(r), joinedRoom(r,i), aboutToDeleteRoom(i) + * 4a. Leave and Invite -> Join: + * joinedRoom(r,i), aboutToDeleteRoom(i) + * 5. Invite -> Leave: + * newRoom(r), leftRoom(r,i), aboutToDeleteRoom(i) + * 5a. Leave and Invite -> Leave: + * leftRoom(r,i), aboutToDeleteRoom(i) + * 6. Join -> Leave: leftRoom(r) + * 7. Leave -> Invite: newRoom(i), invitedRoom(i,r) + * 8. Leave -> Join: joinedRoom(r) + * The following transitions are only possible via forgetRoom() + * so far; if a room gets forgotten externally, sync won't tell + * about it: + * 9. any -> none: as any -> Leave, then aboutToDeleteRoom(r) + */ + + /** A new room object has been created */ + void newRoom(Room* room); + + /** A room invitation is seen for the first time + * + * If the same room is in Left state, it's passed in prev. Beware + * that initial sync will trigger this signal for all rooms in + * Invite state. + */ + void invitedRoom(Room* room, Room* prev); + + /** A joined room is seen for the first time + * + * It's not the same as receiving a room in "join" section of sync + * response (rooms will be there even after joining); it's also + * not (exactly) the same as actual joining action of a user (all + * rooms coming in initial sync will trigger this signal too). If + * this room was in Invite state before, the respective object is + * passed in prev (and it will be deleted shortly afterwards). + */ + void joinedRoom(Room* room, Room* prev); + + /** A room has just been left + * + * If this room has been in Invite state (as in case of rejecting + * an invitation), the respective object will be passed in prev + * (and will be deleted shortly afterwards). Note that, similar + * to invitedRoom and joinedRoom, this signal is triggered for all + * Left rooms upon initial sync (not only those that were left + * right before the sync). + */ + void leftRoom(Room* room, Room* prev); + + /** The room object is about to be deleted */ + void aboutToDeleteRoom(Room* room); + + /** The room has just been created by createRoom or requestDirectChat + * + * This signal is not emitted in usual room state transitions, + * only as an outcome of room creation operations invoked by + * the client. + * \note requestDirectChat doesn't necessarily create a new chat; + * use directChatAvailable signal if you just need to obtain + * a direct chat room. + */ + void createdRoom(Room* room); + + /** The first sync for the room has been completed + * + * This signal is emitted after the room has been synced the first + * time. This is the right signal to connect to if you need to + * access the room state (name, aliases, members); state transition + * signals (newRoom, joinedRoom etc.) come earlier, when the room + * has just been created. + */ + void loadedRoomState(Room* room); + + /** Account data (except direct chats) have changed */ + void accountDataChanged(QString type); + + /** The direct chat room is ready for using + * This signal is emitted upon any successful outcome from + * requestDirectChat. + */ + void directChatAvailable(Room* directChat); + + /** The list of direct chats has changed + * This signal is emitted every time when the mapping of users + * to direct chat rooms is changed (because of either local updates + * or a different list arrived from the server). + */ + void directChatsListChanged(DirectChatsMap additions, + DirectChatsMap removals); + + void ignoredUsersListChanged(IgnoredUsersList additions, + IgnoredUsersList removals); + + void cacheStateChanged(); + void lazyLoadingChanged(); + void turnServersChanged(const QJsonObject& servers); + +protected: + /** + * @brief Access the underlying ConnectionData class + */ + const ConnectionData* connectionData() const; + + /** Get a Room object for the given id in the given state + * + * Use this method when you need a Room object in the local list + * of rooms, with the given state. Note that this does not interact + * with the server; in particular, does not automatically create + * rooms on the server. This call performs necessary join state + * transitions; e.g., if it finds a room in Invite but + * `joinState == JoinState::Join` then the Invite room object + * will be deleted and a new room object with Join state created. + * In contrast, switching between Join and Leave happens within + * the same object. + * \param roomId room id (not alias!) + * \param joinState desired (target) join state of the room; if + * omitted, any state will be found and return unchanged, or a + * new Join room created. + * @return a pointer to a Room object with the specified id and the + * specified state; nullptr if roomId is empty or if roomFactory() + * failed to create a Room object. + */ + Room* provideRoom(const QString& roomId, + Omittable<JoinState> joinState = none); + + /** + * Completes loading sync data. + */ + void onSyncSuccess(SyncData&& data, bool fromCache = false); + +protected slots: + void syncLoopIteration(); + +private: + class Private; + QScopedPointer<Private> d; + + /** + * A single entry for functions that need to check whether the + * homeserver is valid before running. May either execute connectFn + * synchronously or asynchronously (if tryResolve is true and + * a DNS lookup is initiated); in case of errors, emits resolveError + * if the homeserver URL is not valid and cannot be resolved from + * userId. + * + * @param userId - fully-qualified MXID to resolve HS from + * @param connectFn - a function to execute once the HS URL is good + */ + void checkAndConnect(const QString& userId, std::function<void()> connectFn); + void doConnectToServer(const QString& user, const QString& password, + const QString& initialDeviceName, + const QString& deviceId = {}); + + static room_factory_t _roomFactory; + static user_factory_t _userFactory; +}; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Connection*) diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp index 513e497e..c157565f 100644 --- a/lib/connectiondata.cpp +++ b/lib/connectiondata.cpp @@ -23,8 +23,11 @@ using namespace QMatrixClient; -struct ConnectionData::Private { - explicit Private(const QUrl& url) : baseUrl(url) {} +struct ConnectionData::Private +{ + explicit Private(QUrl url) + : baseUrl(std::move(url)) + {} QUrl baseUrl; QByteArray accessToken; @@ -36,9 +39,8 @@ struct ConnectionData::Private { }; ConnectionData::ConnectionData(QUrl baseUrl) - : d(std::make_unique<Private>(baseUrl)) -{ -} + : d(std::make_unique<Private>(std::move(baseUrl))) +{} ConnectionData::~ConnectionData() = default; @@ -86,11 +88,10 @@ QString ConnectionData::lastEvent() const { return d->lastEvent; } void ConnectionData::setLastEvent(QString identifier) { - d->lastEvent = identifier; + d->lastEvent = std::move(identifier); } QByteArray ConnectionData::generateTxnId() const { - return QByteArray::number(d->id) + 'q' - + QByteArray::number(++d->txnCounter); + return QByteArray::number(d->id) + 'q' + QByteArray::number(++d->txnCounter); } diff --git a/lib/connectiondata.h b/lib/connectiondata.h index fcb67e2d..6f9f090c 100644 --- a/lib/connectiondata.h +++ b/lib/connectiondata.h @@ -24,31 +24,32 @@ class QNetworkAccessManager; -namespace QMatrixClient { - class ConnectionData - { - public: - explicit ConnectionData(QUrl baseUrl); - virtual ~ConnectionData(); - - QByteArray accessToken() const; - QUrl baseUrl() const; - const QString& deviceId() const; - - QNetworkAccessManager* nam() const; - void setBaseUrl(QUrl baseUrl); - void setToken(QByteArray accessToken); - void setHost(QString host); - void setPort(int port); - void setDeviceId(const QString& deviceId); - - QString lastEvent() const; - void setLastEvent(QString identifier); - - QByteArray generateTxnId() const; - - private: - struct Private; - std::unique_ptr<Private> d; - }; +namespace QMatrixClient +{ +class ConnectionData +{ +public: + explicit ConnectionData(QUrl baseUrl); + virtual ~ConnectionData(); + + QByteArray accessToken() const; + QUrl baseUrl() const; + const QString& deviceId() const; + + QNetworkAccessManager* nam() const; + void setBaseUrl(QUrl baseUrl); + void setToken(QByteArray accessToken); + void setHost(QString host); + void setPort(int port); + void setDeviceId(const QString& deviceId); + + QString lastEvent() const; + void setLastEvent(QString identifier); + + QByteArray generateTxnId() const; + +private: + struct Private; + std::unique_ptr<Private> d; +}; } // namespace QMatrixClient diff --git a/lib/converters.cpp b/lib/converters.cpp index 8f45ba3b..ef58c85e 100644 --- a/lib/converters.cpp +++ b/lib/converters.cpp @@ -36,20 +36,20 @@ QJsonObject JsonConverter<variant_map_t>::dump(const variant_map_t& map) { return #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) - QJsonObject::fromVariantHash + QJsonObject::fromVariantHash #else - QJsonObject::fromVariantMap + QJsonObject::fromVariantMap #endif - (map); + (map); } variant_map_t JsonConverter<QVariantHash>::load(const QJsonValue& jv) { return jv.toObject(). #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) - toVariantHash + toVariantHash #else - toVariantMap + toVariantMap #endif - (); + (); } diff --git a/lib/converters.h b/lib/converters.h index 68a841cf..3ba65c22 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -31,343 +31,385 @@ #include <unordered_map> #include <vector> #if 0 // Waiting for C++17 -#include <experimental/optional> +# include <experimental/optional> template <typename T> using optional = std::experimental::optional<T>; #endif // Enable std::unordered_map<QString, T> -namespace std { - template <> struct hash<QString> { - size_t operator()(const QString& s) const Q_DECL_NOEXCEPT - { - return qHash(s +namespace std +{ +template <> +struct hash<QString> +{ + size_t operator()(const QString& s) const Q_DECL_NOEXCEPT + { + return qHash(s #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - , - uint(qGlobalQHashSeed()) + , + uint(qGlobalQHashSeed()) #endif - ); - } - }; -} + ); + } +}; +} // namespace std class QVariant; -namespace QMatrixClient { - template <typename T> struct JsonObjectConverter { - static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod; } - static void fillFrom(const QJsonObject& jo, T& pod) { pod = jo; } - }; - - template <typename T> struct JsonConverter { - static QJsonObject dump(const T& pod) - { - QJsonObject jo; - JsonObjectConverter<T>::dumpTo(jo, pod); - return jo; - } - static T doLoad(const QJsonObject& jo) - { - T pod; - JsonObjectConverter<T>::fillFrom(jo, pod); - return pod; - } - static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } - static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } - }; +namespace QMatrixClient +{ +template <typename T> +struct JsonObjectConverter +{ + static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod; } + static void fillFrom(const QJsonObject& jo, T& pod) { pod = jo; } +}; - template <typename T> inline auto toJson(const T& pod) +template <typename T> +struct JsonConverter +{ + static QJsonObject dump(const T& pod) { - return JsonConverter<T>::dump(pod); + QJsonObject jo; + JsonObjectConverter<T>::dumpTo(jo, pod); + return jo; } - - template <typename T> inline auto fillJson(QJsonObject& json, const T& data) + static T doLoad(const QJsonObject& jo) { - JsonObjectConverter<T>::dumpTo(json, data); + T pod; + JsonObjectConverter<T>::fillFrom(jo, pod); + return pod; } + static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); } + static T load(const QJsonDocument& jd) { return doLoad(jd.object()); } +}; + +template <typename T> +inline auto toJson(const T& pod) +{ + return JsonConverter<T>::dump(pod); +} + +template <typename T> +inline auto fillJson(QJsonObject& json, const T& data) +{ + JsonObjectConverter<T>::dumpTo(json, data); +} + +template <typename T> +inline auto fromJson(const QJsonValue& jv) +{ + return JsonConverter<T>::load(jv); +} + +template <typename T> +inline T fromJson(const QJsonDocument& jd) +{ + return JsonConverter<T>::load(jd); +} + +template <typename T> +inline void fromJson(const QJsonValue& jv, T& pod) +{ + if (!jv.isUndefined()) + pod = fromJson<T>(jv); +} + +template <typename T> +inline void fromJson(const QJsonDocument& jd, T& pod) +{ + pod = fromJson<T>(jd); +} + +// Unfolds Omittable<> +template <typename T> +inline void fromJson(const QJsonValue& jv, Omittable<T>& pod) +{ + if (jv.isUndefined()) + pod = none; + else + pod = fromJson<T>(jv); +} + +template <typename T> +inline void fillFromJson(const QJsonValue& jv, T& pod) +{ + if (jv.isObject()) + JsonObjectConverter<T>::fillFrom(jv.toObject(), pod); +} - template <typename T> inline auto fromJson(const QJsonValue& jv) +// JsonConverter<> specialisations + +template <typename T> +struct TrivialJsonDumper +{ + // Works for: QJsonValue (and all things it can consume), + // QJsonObject, QJsonArray + static auto dump(const T& val) { return val; } +}; + +template <> +struct JsonConverter<bool> : public TrivialJsonDumper<bool> +{ + static auto load(const QJsonValue& jv) { return jv.toBool(); } +}; + +template <> +struct JsonConverter<int> : public TrivialJsonDumper<int> +{ + static auto load(const QJsonValue& jv) { return jv.toInt(); } +}; + +template <> +struct JsonConverter<double> : public TrivialJsonDumper<double> +{ + static auto load(const QJsonValue& jv) { return jv.toDouble(); } +}; + +template <> +struct JsonConverter<float> : public TrivialJsonDumper<float> +{ + static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } +}; + +template <> +struct JsonConverter<qint64> : public TrivialJsonDumper<qint64> +{ + static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } +}; + +template <> +struct JsonConverter<QString> : public TrivialJsonDumper<QString> +{ + static auto load(const QJsonValue& jv) { return jv.toString(); } +}; + +template <> +struct JsonConverter<QDateTime> +{ + static auto dump(const QDateTime& val) = delete; // not provided yet + static auto load(const QJsonValue& jv) { - return JsonConverter<T>::load(jv); + return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), Qt::UTC); } +}; - template <typename T> inline T fromJson(const QJsonDocument& jd) +template <> +struct JsonConverter<QDate> +{ + static auto dump(const QDate& val) = delete; // not provided yet + static auto load(const QJsonValue& jv) { - return JsonConverter<T>::load(jd); + return fromJson<QDateTime>(jv).date(); } - - template <typename T> inline void fromJson(const QJsonValue& jv, T& pod) +}; + +template <> +struct JsonConverter<QJsonArray> : public TrivialJsonDumper<QJsonArray> +{ + static auto load(const QJsonValue& jv) { return jv.toArray(); } +}; + +template <> +struct JsonConverter<QByteArray> +{ + static QString dump(const QByteArray& ba) { return ba.constData(); } + static auto load(const QJsonValue& jv) { - if (!jv.isUndefined()) - pod = fromJson<T>(jv); + return fromJson<QString>(jv).toLatin1(); } - - template <typename T> inline void fromJson(const QJsonDocument& jd, T& pod) +}; + +template <> +struct JsonConverter<QVariant> +{ + static QJsonValue dump(const QVariant& v); + static QVariant load(const QJsonValue& jv); +}; + +template <typename VectorT, typename T = typename VectorT::value_type> +struct JsonArrayConverter +{ + static void dumpTo(QJsonArray& ar, const VectorT& vals) { - pod = fromJson<T>(jd); + for (const auto& v : vals) + ar.push_back(toJson(v)); } - - // Unfolds Omittable<> - template <typename T> - inline void fromJson(const QJsonValue& jv, Omittable<T>& pod) + static auto dump(const VectorT& vals) { - if (jv.isUndefined()) - pod = none; - else - pod = fromJson<T>(jv); + QJsonArray ja; + dumpTo(ja, vals); + return ja; } - - template <typename T> inline void fillFromJson(const QJsonValue& jv, T& pod) + static auto load(const QJsonArray& ja) { - if (jv.isObject()) - JsonObjectConverter<T>::fillFrom(jv.toObject(), pod); + VectorT vect; + vect.reserve(typename VectorT::size_type(ja.size())); + for (const auto& i : ja) + vect.push_back(fromJson<T>(i)); + return vect; } + static auto load(const QJsonValue& jv) { return load(jv.toArray()); } + static auto load(const QJsonDocument& jd) { return load(jd.array()); } +}; - // JsonConverter<> specialisations - - template <typename T> struct TrivialJsonDumper { - // Works for: QJsonValue (and all things it can consume), - // QJsonObject, QJsonArray - static auto dump(const T& val) { return val; } - }; - - template <> struct JsonConverter<bool> : public TrivialJsonDumper<bool> { - static auto load(const QJsonValue& jv) { return jv.toBool(); } - }; - - template <> struct JsonConverter<int> : public TrivialJsonDumper<int> { - static auto load(const QJsonValue& jv) { return jv.toInt(); } - }; - - template <> - struct JsonConverter<double> : public TrivialJsonDumper<double> { - static auto load(const QJsonValue& jv) { return jv.toDouble(); } - }; - - template <> struct JsonConverter<float> : public TrivialJsonDumper<float> { - static auto load(const QJsonValue& jv) { return float(jv.toDouble()); } - }; +template <typename T> +struct JsonConverter<std::vector<T>> : public JsonArrayConverter<std::vector<T>> +{}; - template <> - struct JsonConverter<qint64> : public TrivialJsonDumper<qint64> { - static auto load(const QJsonValue& jv) { return qint64(jv.toDouble()); } - }; +template <typename T> +struct JsonConverter<QVector<T>> : public JsonArrayConverter<QVector<T>> +{}; - template <> - struct JsonConverter<QString> : public TrivialJsonDumper<QString> { - static auto load(const QJsonValue& jv) { return jv.toString(); } - }; +template <typename T> +struct JsonConverter<QList<T>> : public JsonArrayConverter<QList<T>> +{}; - template <> struct JsonConverter<QDateTime> { - static auto dump(const QDateTime& val) = delete; // not provided yet - static auto load(const QJsonValue& jv) - { - return QDateTime::fromMSecsSinceEpoch(fromJson<qint64>(jv), - Qt::UTC); - } - }; +template <> +struct JsonConverter<QStringList> : public JsonConverter<QList<QString>> +{ + static auto dump(const QStringList& sl) + { + return QJsonArray::fromStringList(sl); + } +}; - template <> struct JsonConverter<QDate> { - static auto dump(const QDate& val) = delete; // not provided yet - static auto load(const QJsonValue& jv) - { - return fromJson<QDateTime>(jv).date(); - } - }; +template <> +struct JsonObjectConverter<QSet<QString>> +{ + static void dumpTo(QJsonObject& json, const QSet<QString>& s) + { + for (const auto& e : s) + json.insert(toJson(e), QJsonObject {}); + } + static auto fillFrom(const QJsonObject& json, QSet<QString>& s) + { + s.reserve(s.size() + json.size()); + for (auto it = json.begin(); it != json.end(); ++it) + s.insert(it.key()); + return s; + } +}; - template <> - struct JsonConverter<QJsonArray> : public TrivialJsonDumper<QJsonArray> { - static auto load(const QJsonValue& jv) { return jv.toArray(); } - }; +template <typename HashMapT> +struct HashMapFromJson +{ + static void dumpTo(QJsonObject& json, const HashMapT& hashMap) + { + for (auto it = hashMap.begin(); it != hashMap.end(); ++it) + json.insert(it.key(), toJson(it.value())); + } + static void fillFrom(const QJsonObject& jo, HashMapT& h) + { + h.reserve(jo.size()); + for (auto it = jo.begin(); it != jo.end(); ++it) + h[it.key()] = fromJson<typename HashMapT::mapped_type>(it.value()); + } +}; - template <> struct JsonConverter<QByteArray> { - static QString dump(const QByteArray& ba) { return ba.constData(); } - static auto load(const QJsonValue& jv) - { - return fromJson<QString>(jv).toLatin1(); - } - }; +template <typename T> +struct JsonObjectConverter<std::unordered_map<QString, T>> + : public HashMapFromJson<std::unordered_map<QString, T>> +{}; - template <> struct JsonConverter<QVariant> { - static QJsonValue dump(const QVariant& v); - static QVariant load(const QJsonValue& jv); - }; +template <typename T> +struct JsonObjectConverter<QHash<QString, T>> + : public HashMapFromJson<QHash<QString, T>> +{}; + +// We could use std::conditional<> below but QT_VERSION* macros in C++ code +// cause (kinda valid but useless and noisy) compiler warnings about +// bitwise operations on signed integers; so use the preprocessor for now. +using variant_map_t = +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + QVariantHash; +#else + QVariantMap; +#endif +template <> +struct JsonConverter<variant_map_t> +{ + static QJsonObject dump(const variant_map_t& vh); + static QVariantHash load(const QJsonValue& jv); +}; + +// Conditional insertion into a QJsonObject + +namespace _impl +{ + template <typename ValT> + inline void addTo(QJsonObject& o, const QString& k, ValT&& v) + { + o.insert(k, toJson(v)); + } - template <typename VectorT, typename T = typename VectorT::value_type> - struct JsonArrayConverter { - static void dumpTo(QJsonArray& ar, const VectorT& vals) - { - for (const auto& v : vals) - ar.push_back(toJson(v)); - } - static auto dump(const VectorT& vals) - { - QJsonArray ja; - dumpTo(ja, vals); - return ja; - } - static auto load(const QJsonArray& ja) - { - VectorT vect; - vect.reserve(typename VectorT::size_type(ja.size())); - for (const auto& i : ja) - vect.push_back(fromJson<T>(i)); - return vect; - } - static auto load(const QJsonValue& jv) { return load(jv.toArray()); } - static auto load(const QJsonDocument& jd) { return load(jd.array()); } - }; + template <typename ValT> + inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) + { + q.addQueryItem(k, QStringLiteral("%1").arg(v)); + } - template <typename T> - struct JsonConverter<std::vector<T>> - : public JsonArrayConverter<std::vector<T>> { - }; + // OpenAPI is entirely JSON-based, which means representing bools as + // textual true/false, rather than 1/0. + inline void addTo(QUrlQuery& q, const QString& k, bool v) + { + q.addQueryItem(k, v ? QStringLiteral("true") : QStringLiteral("false")); + } - template <typename T> - struct JsonConverter<QVector<T>> : public JsonArrayConverter<QVector<T>> { - }; + inline void addTo(QUrlQuery& q, const QString& k, const QStringList& vals) + { + for (const auto& v : vals) + q.addQueryItem(k, v); + } - template <typename T> - struct JsonConverter<QList<T>> : public JsonArrayConverter<QList<T>> { - }; + inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) + { + for (auto it = vals.begin(); it != vals.end(); ++it) + q.addQueryItem(it.key(), it.value().toString()); + } - template <> - struct JsonConverter<QStringList> : public JsonConverter<QList<QString>> { - static auto dump(const QStringList& sl) + // This one is for types that don't have isEmpty() + template <typename ValT, bool Force = true, typename = bool> + struct AddNode + { + template <typename ContT, typename ForwardedT> + static void impl(ContT& container, const QString& key, + ForwardedT&& value) { - return QJsonArray::fromStringList(sl); + addTo(container, key, std::forward<ForwardedT>(value)); } }; - template <> struct JsonObjectConverter<QSet<QString>> { - static void dumpTo(QJsonObject& json, const QSet<QString>& s) - { - for (const auto& e : s) - json.insert(toJson(e), QJsonObject {}); - } - static auto fillFrom(const QJsonObject& json, QSet<QString>& s) + // This one is for types that have isEmpty() + template <typename ValT> + struct AddNode<ValT, false, decltype(std::declval<ValT>().isEmpty())> + { + template <typename ContT, typename ForwardedT> + static void impl(ContT& container, const QString& key, + ForwardedT&& value) { - s.reserve(s.size() + json.size()); - for (auto it = json.begin(); it != json.end(); ++it) - s.insert(it.key()); - return s; + if (!value.isEmpty()) + AddNode<ValT>::impl(container, key, + std::forward<ForwardedT>(value)); } }; - template <typename HashMapT> struct HashMapFromJson { - static void dumpTo(QJsonObject& json, const HashMapT& hashMap) - { - for (auto it = hashMap.begin(); it != hashMap.end(); ++it) - json.insert(it.key(), toJson(it.value())); - } - static void fillFrom(const QJsonObject& jo, HashMapT& h) + // This is a special one that unfolds Omittable<> + template <typename ValT, bool Force> + struct AddNode<Omittable<ValT>, Force> + { + template <typename ContT, typename OmittableT> + static void impl(ContT& container, const QString& key, + const OmittableT& value) { - h.reserve(jo.size()); - for (auto it = jo.begin(); it != jo.end(); ++it) - h[it.key()] = - fromJson<typename HashMapT::mapped_type>(it.value()); + if (!value.omitted()) + AddNode<ValT>::impl(container, key, value.value()); + else if (Force) // Edge case, no value but must put something + AddNode<ValT>::impl(container, key, QString {}); } }; - template <typename T> - struct JsonObjectConverter<std::unordered_map<QString, T>> - : public HashMapFromJson<std::unordered_map<QString, T>> { - }; - - template <typename T> - struct JsonObjectConverter<QHash<QString, T>> - : public HashMapFromJson<QHash<QString, T>> { - }; - - // We could use std::conditional<> below but QT_VERSION* macros in C++ code - // cause (kinda valid but useless and noisy) compiler warnings about - // bitwise operations on signed integers; so use the preprocessor for now. - using variant_map_t = -#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) - QVariantHash; -#else - QVariantMap; -#endif - template <> struct JsonConverter<variant_map_t> { - static QJsonObject dump(const variant_map_t& vh); - static QVariantHash load(const QJsonValue& jv); - }; - - // Conditional insertion into a QJsonObject - - namespace _impl { - template <typename ValT> - inline void addTo(QJsonObject& o, const QString& k, ValT&& v) - { - o.insert(k, toJson(v)); - } - - template <typename ValT> - inline void addTo(QUrlQuery& q, const QString& k, ValT&& v) - { - q.addQueryItem(k, QStringLiteral("%1").arg(v)); - } - - // OpenAPI is entirely JSON-based, which means representing bools as - // textual true/false, rather than 1/0. - inline void addTo(QUrlQuery& q, const QString& k, bool v) - { - q.addQueryItem( - k, v ? QStringLiteral("true") : QStringLiteral("false")); - } - - inline void addTo(QUrlQuery& q, const QString& k, - const QStringList& vals) - { - for (const auto& v : vals) - q.addQueryItem(k, v); - } - - inline void addTo(QUrlQuery& q, const QString&, const QJsonObject& vals) - { - for (auto it = vals.begin(); it != vals.end(); ++it) - q.addQueryItem(it.key(), it.value().toString()); - } - - // This one is for types that don't have isEmpty() - template <typename ValT, bool Force = true, typename = bool> - struct AddNode { - template <typename ContT, typename ForwardedT> - static void impl(ContT& container, const QString& key, - ForwardedT&& value) - { - addTo(container, key, std::forward<ForwardedT>(value)); - } - }; - - // This one is for types that have isEmpty() - template <typename ValT> - struct AddNode<ValT, false, decltype(std::declval<ValT>().isEmpty())> { - template <typename ContT, typename ForwardedT> - static void impl(ContT& container, const QString& key, - ForwardedT&& value) - { - if (!value.isEmpty()) - AddNode<ValT>::impl(container, key, - std::forward<ForwardedT>(value)); - } - }; - - // This is a special one that unfolds Omittable<> - template <typename ValT, bool Force> - struct AddNode<Omittable<ValT>, Force> { - template <typename ContT, typename OmittableT> - static void impl(ContT& container, const QString& key, - const OmittableT& value) - { - if (!value.omitted()) - AddNode<ValT>::impl(container, key, value.value()); - else if (Force) // Edge case, no value but must put something - AddNode<ValT>::impl(container, key, QString {}); - } - }; - #if 0 // This is a special one that unfolds optional<> template <typename ValT, bool Force> @@ -385,14 +427,14 @@ namespace QMatrixClient { }; #endif - } // namespace _impl +} // namespace _impl - static constexpr bool IfNotEmpty = false; +static constexpr bool IfNotEmpty = false; - template <bool Force = true, typename ContT, typename ValT> - inline void addParam(ContT& container, const QString& key, ValT&& value) - { - _impl::AddNode<std::decay_t<ValT>, Force>::impl( - container, key, std::forward<ValT>(value)); - } +template <bool Force = true, typename ContT, typename ValT> +inline void addParam(ContT& container, const QString& key, ValT&& value) +{ + _impl::AddNode<std::decay_t<ValT>, Force>::impl(container, key, + std::forward<ValT>(value)); +} } // namespace QMatrixClient diff --git a/lib/csapi/account-data.cpp b/lib/csapi/account-data.cpp index 40388673..7d4f1ad7 100644 --- a/lib/csapi/account-data.cpp +++ b/lib/csapi/account-data.cpp @@ -22,8 +22,23 @@ SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, setRequestData(Data(toJson(content))); } +QUrl GetAccountDataJob::makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& type) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/user/" % userId + % "/account_data/" % type); +} + +static const auto GetAccountDataJobName = QStringLiteral("GetAccountDataJob"); + +GetAccountDataJob::GetAccountDataJob(const QString& userId, const QString& type) + : BaseJob(HttpVerb::Get, GetAccountDataJobName, + basePath % "/user/" % userId % "/account_data/" % type) +{} + static const auto SetAccountDataPerRoomJobName = - QStringLiteral("SetAccountDataPerRoomJob"); + QStringLiteral("SetAccountDataPerRoomJob"); SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QString& roomId, @@ -31,7 +46,28 @@ SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QJsonObject& content) : BaseJob(HttpVerb::Put, SetAccountDataPerRoomJobName, basePath % "/user/" % userId % "/rooms/" % roomId - % "/account_data/" % type) + % "/account_data/" % type) { setRequestData(Data(toJson(content))); } + +QUrl GetAccountDataPerRoomJob::makeRequestUrl(QUrl baseUrl, + const QString& userId, + const QString& roomId, + const QString& type) +{ + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/user/" % userId % "/rooms/" + % roomId % "/account_data/" % type); +} + +static const auto GetAccountDataPerRoomJobName = + QStringLiteral("GetAccountDataPerRoomJob"); + +GetAccountDataPerRoomJob::GetAccountDataPerRoomJob(const QString& userId, + const QString& roomId, + const QString& type) + : BaseJob(HttpVerb::Get, GetAccountDataPerRoomJobName, + basePath % "/user/" % userId % "/rooms/" % roomId + % "/account_data/" % type) +{} diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h index 669a4e2c..75bb9ce3 100644 --- a/lib/csapi/account-data.h +++ b/lib/csapi/account-data.h @@ -8,49 +8,118 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Set some account_data for the user. - /// - /// Set some account_data for the client. This config is only visible to the - /// user that set the account_data. The config will be synced to clients in - /// the top-level ``account_data``. - class SetAccountDataJob : public BaseJob - { - public: - /*! Set some account_data for the user. - * \param userId - * The id of the user to set account_data for. The access token must - * be authorized to make requests for this user id. \param type The - * event type of the account_data to set. Custom types should be - * namespaced to avoid clashes. - * \param content - * The content of the account_data - */ - explicit SetAccountDataJob(const QString& userId, const QString& type, - const QJsonObject& content = {}); - }; - - /// Set some account_data for the user. - /// - /// Set some account_data for the client on a given room. This config is - /// only visible to the user that set the account_data. The config will be - /// synced to clients in the per-room ``account_data``. - class SetAccountDataPerRoomJob : public BaseJob - { - public: - /*! Set some account_data for the user. - * \param userId - * The id of the user to set account_data for. The access token must - * be authorized to make requests for this user id. \param roomId The id - * of the room to set account_data on. \param type The event type of the - * account_data to set. Custom types should be namespaced to avoid - * clashes. \param content The content of the account_data - */ - explicit SetAccountDataPerRoomJob(const QString& userId, - const QString& roomId, - const QString& type, - const QJsonObject& content = {}); - }; +namespace QMatrixClient +{ + +// Operations + +/// Set some account_data for the user. +/*! + * Set some account_data for the client. This config is only visible to the user + * that set the account_data. The config will be synced to clients in the + * top-level ``account_data``. + */ +class SetAccountDataJob : public BaseJob +{ +public: + /*! Set some account_data for the user. + * \param userId + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. + * \param type + * The event type of the account_data to set. Custom types should be + * namespaced to avoid clashes. + * \param content + * The content of the account_data + */ + explicit SetAccountDataJob(const QString& userId, const QString& type, + const QJsonObject& content = {}); +}; + +/// Get some account_data for the user. +/*! + * Get some account_data for the client. This config is only visible to the user + * that set the account_data. + */ +class GetAccountDataJob : public BaseJob +{ +public: + /*! Get some account_data for the user. + * \param userId + * The ID of the user to get account_data for. The access token must be + * authorized to make requests for this user ID. + * \param type + * The event type of the account_data to get. Custom types should be + * namespaced to avoid clashes. + */ + explicit GetAccountDataJob(const QString& userId, const QString& type); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAccountDataJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& type); +}; + +/// Set some account_data for the user. +/*! + * Set some account_data for the client on a given room. This config is only + * visible to the user that set the account_data. The config will be synced to + * clients in the per-room ``account_data``. + */ +class SetAccountDataPerRoomJob : public BaseJob +{ +public: + /*! Set some account_data for the user. + * \param userId + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to set account_data on. + * \param type + * The event type of the account_data to set. Custom types should be + * namespaced to avoid clashes. + * \param content + * The content of the account_data + */ + explicit SetAccountDataPerRoomJob(const QString& userId, + const QString& roomId, const QString& type, + const QJsonObject& content = {}); +}; + +/// Get some account_data for the user. +/*! + * Get some account_data for the client on a given room. This config is only + * visible to the user that set the account_data. + */ +class GetAccountDataPerRoomJob : public BaseJob +{ +public: + /*! Get some account_data for the user. + * \param userId + * The ID of the user to set account_data for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to get account_data for. + * \param type + * The event type of the account_data to get. Custom types should be + * namespaced to avoid clashes. + */ + explicit GetAccountDataPerRoomJob(const QString& userId, + const QString& roomId, + const QString& type); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAccountDataPerRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& roomId, const QString& type); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/admin.cpp b/lib/csapi/admin.cpp index 7922ffe3..58334118 100644 --- a/lib/csapi/admin.cpp +++ b/lib/csapi/admin.cpp @@ -12,39 +12,45 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<GetWhoIsJob::ConnectionInfo> { - static void fillFrom(const QJsonObject& jo, - GetWhoIsJob::ConnectionInfo& result) - { - fromJson(jo.value("ip"_ls), result.ip); - fromJson(jo.value("last_seen"_ls), result.lastSeen); - fromJson(jo.value("user_agent"_ls), result.userAgent); - } - }; - - template <> struct JsonObjectConverter<GetWhoIsJob::SessionInfo> { - static void fillFrom(const QJsonObject& jo, - GetWhoIsJob::SessionInfo& result) - { - fromJson(jo.value("connections"_ls), result.connections); - } - }; - - template <> struct JsonObjectConverter<GetWhoIsJob::DeviceInfo> { - static void fillFrom(const QJsonObject& jo, - GetWhoIsJob::DeviceInfo& result) - { - fromJson(jo.value("sessions"_ls), result.sessions); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetWhoIsJob::ConnectionInfo> +{ + static void fillFrom(const QJsonObject& jo, + GetWhoIsJob::ConnectionInfo& result) + { + fromJson(jo.value("ip"_ls), result.ip); + fromJson(jo.value("last_seen"_ls), result.lastSeen); + fromJson(jo.value("user_agent"_ls), result.userAgent); + } +}; + +template <> +struct JsonObjectConverter<GetWhoIsJob::SessionInfo> +{ + static void fillFrom(const QJsonObject& jo, GetWhoIsJob::SessionInfo& result) + { + fromJson(jo.value("connections"_ls), result.connections); + } +}; + +template <> +struct JsonObjectConverter<GetWhoIsJob::DeviceInfo> +{ + static void fillFrom(const QJsonObject& jo, GetWhoIsJob::DeviceInfo& result) + { + fromJson(jo.value("sessions"_ls), result.sessions); + } +}; + } // namespace QMatrixClient class GetWhoIsJob::Private { - public: +public: QString userId; QHash<QString, DeviceInfo> devices; }; @@ -59,10 +65,9 @@ static const auto GetWhoIsJobName = QStringLiteral("GetWhoIsJob"); GetWhoIsJob::GetWhoIsJob(const QString& userId) : BaseJob(HttpVerb::Get, GetWhoIsJobName, - basePath % "/admin/whois/" % userId), - d(new Private) -{ -} + basePath % "/admin/whois/" % userId) + , d(new Private) +{} GetWhoIsJob::~GetWhoIsJob() = default; @@ -78,5 +83,6 @@ BaseJob::Status GetWhoIsJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("user_id"_ls), d->userId); fromJson(json.value("devices"_ls), d->devices); + return Success; } diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h index 3c21e2cb..bc27c025 100644 --- a/lib/csapi/admin.h +++ b/lib/csapi/admin.h @@ -4,92 +4,94 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QHash> #include <QtCore/QVector> -namespace QMatrixClient { - // Operations - - /// Gets information about a particular user. - /// - /// Gets information about a particular user. - /// - /// This API may be restricted to only be called by the user being looked - /// up, or by a server admin. Server-local administrator privileges are not - /// specified in this document. - class GetWhoIsJob : public BaseJob +namespace QMatrixClient +{ + +// Operations + +/// Gets information about a particular user. +/*! + * Gets information about a particular user. + * + * This API may be restricted to only be called by the user being looked + * up, or by a server admin. Server-local administrator privileges are not + * specified in this document. + */ +class GetWhoIsJob : public BaseJob +{ +public: + // Inner data structures + + /// Gets information about a particular user.This API may be restricted to + /// only be called by the user being lookedup, or by a server admin. + /// Server-local administrator privileges are notspecified in this document. + struct ConnectionInfo { - public: - // Inner data structures - - /// Gets information about a particular user. - /// - /// This API may be restricted to only be called by the user being - /// looked up, or by a server admin. Server-local administrator - /// privileges are not specified in this document. - struct ConnectionInfo { - /// Most recently seen IP address of the session. - QString ip; - /// Unix timestamp that the session was last active. - Omittable<qint64> lastSeen; - /// User agent string last seen in the session. - QString userAgent; - }; - - /// Gets information about a particular user. - /// - /// This API may be restricted to only be called by the user being - /// looked up, or by a server admin. Server-local administrator - /// privileges are not specified in this document. - struct SessionInfo { - /// Information particular connections in the session. - QVector<ConnectionInfo> connections; - }; - - /// Gets information about a particular user. - /// - /// This API may be restricted to only be called by the user being - /// looked up, or by a server admin. Server-local administrator - /// privileges are not specified in this document. - struct DeviceInfo { - /// A user's sessions (i.e. what they did with an access token from - /// one login). - QVector<SessionInfo> sessions; - }; - - // Construction/destruction - - /*! Gets information about a particular user. - * \param userId - * The user to look up. - */ - explicit GetWhoIsJob(const QString& userId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetWhoIsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - - ~GetWhoIsJob() override; - - // Result properties - - /// The Matrix user ID of the user. - const QString& userId() const; - /// Each key is an identitfier for one of the user's devices. - const QHash<QString, DeviceInfo>& devices() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// Most recently seen IP address of the session. + QString ip; + /// Unix timestamp that the session was last active. + Omittable<qint64> lastSeen; + /// User agent string last seen in the session. + QString userAgent; }; + + /// Gets information about a particular user.This API may be restricted to + /// only be called by the user being lookedup, or by a server admin. + /// Server-local administrator privileges are notspecified in this document. + struct SessionInfo + { + /// Information particular connections in the session. + QVector<ConnectionInfo> connections; + }; + + /// Gets information about a particular user.This API may be restricted to + /// only be called by the user being lookedup, or by a server admin. + /// Server-local administrator privileges are notspecified in this document. + struct DeviceInfo + { + /// A user's sessions (i.e. what they did with an access token from one + /// login). + QVector<SessionInfo> sessions; + }; + + // Construction/destruction + + /*! Gets information about a particular user. + * \param userId + * The user to look up. + */ + explicit GetWhoIsJob(const QString& userId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetWhoIsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); + + ~GetWhoIsJob() override; + + // Result properties + + /// The Matrix user ID of the user. + const QString& userId() const; + /// Each key is an identitfier for one of the user's devices. + const QHash<QString, DeviceInfo>& devices() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/administrative_contact.cpp b/lib/csapi/administrative_contact.cpp index f64f2723..067fb68a 100644 --- a/lib/csapi/administrative_contact.cpp +++ b/lib/csapi/administrative_contact.cpp @@ -12,25 +12,28 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> - struct JsonObjectConverter<GetAccount3PIDsJob::ThirdPartyIdentifier> { - static void fillFrom(const QJsonObject& jo, - GetAccount3PIDsJob::ThirdPartyIdentifier& result) - { - fromJson(jo.value("medium"_ls), result.medium); - fromJson(jo.value("address"_ls), result.address); - fromJson(jo.value("validated_at"_ls), result.validatedAt); - fromJson(jo.value("added_at"_ls), result.addedAt); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetAccount3PIDsJob::ThirdPartyIdentifier> +{ + static void fillFrom(const QJsonObject& jo, + GetAccount3PIDsJob::ThirdPartyIdentifier& result) + { + fromJson(jo.value("medium"_ls), result.medium); + fromJson(jo.value("address"_ls), result.address); + fromJson(jo.value("validated_at"_ls), result.validatedAt); + fromJson(jo.value("added_at"_ls), result.addedAt); + } +}; + } // namespace QMatrixClient class GetAccount3PIDsJob::Private { - public: +public: QVector<ThirdPartyIdentifier> threepids; }; @@ -43,11 +46,9 @@ QUrl GetAccount3PIDsJob::makeRequestUrl(QUrl baseUrl) static const auto GetAccount3PIDsJobName = QStringLiteral("GetAccount3PIDsJob"); GetAccount3PIDsJob::GetAccount3PIDsJob() - : BaseJob(HttpVerb::Get, GetAccount3PIDsJobName, - basePath % "/account/3pid"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetAccount3PIDsJobName, basePath % "/account/3pid") + , d(new Private) +{} GetAccount3PIDsJob::~GetAccount3PIDsJob() = default; @@ -61,21 +62,26 @@ BaseJob::Status GetAccount3PIDsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("threepids"_ls), d->threepids); + return Success; } -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<Post3PIDsJob::ThreePidCredentials> { - static void dumpTo(QJsonObject& jo, - const Post3PIDsJob::ThreePidCredentials& pod) - { - addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); - addParam<>(jo, QStringLiteral("id_server"), pod.idServer); - addParam<>(jo, QStringLiteral("sid"), pod.sid); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<Post3PIDsJob::ThreePidCredentials> +{ + static void dumpTo(QJsonObject& jo, + const Post3PIDsJob::ThreePidCredentials& pod) + { + addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); + addParam<>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<>(jo, QStringLiteral("sid"), pod.sid); + } +}; + } // namespace QMatrixClient static const auto Post3PIDsJobName = QStringLiteral("Post3PIDsJob"); @@ -91,7 +97,7 @@ Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, } static const auto Delete3pidFromAccountJobName = - QStringLiteral("Delete3pidFromAccountJob"); + QStringLiteral("Delete3pidFromAccountJob"); Delete3pidFromAccountJob::Delete3pidFromAccountJob(const QString& medium, const QString& address) @@ -106,19 +112,19 @@ Delete3pidFromAccountJob::Delete3pidFromAccountJob(const QString& medium, class RequestTokenTo3PIDEmailJob::Private { - public: +public: Sid data; }; static const auto RequestTokenTo3PIDEmailJobName = - QStringLiteral("RequestTokenTo3PIDEmailJob"); + QStringLiteral("RequestTokenTo3PIDEmailJob"); RequestTokenTo3PIDEmailJob::RequestTokenTo3PIDEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) + const QString& clientSecret, const QString& email, int sendAttempt, + const QString& idServer, const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenTo3PIDEmailJobName, - basePath % "/account/3pid/email/requestToken", false), - d(new Private) + basePath % "/account/3pid/email/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -141,20 +147,20 @@ BaseJob::Status RequestTokenTo3PIDEmailJob::parseJson(const QJsonDocument& data) class RequestTokenTo3PIDMSISDNJob::Private { - public: +public: Sid data; }; static const auto RequestTokenTo3PIDMSISDNJobName = - QStringLiteral("RequestTokenTo3PIDMSISDNJob"); + QStringLiteral("RequestTokenTo3PIDMSISDNJob"); RequestTokenTo3PIDMSISDNJob::RequestTokenTo3PIDMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const QString& clientSecret, const QString& country, + const QString& phoneNumber, int sendAttempt, const QString& idServer, + const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenTo3PIDMSISDNJobName, - basePath % "/account/3pid/msisdn/requestToken", false), - d(new Private) + basePath % "/account/3pid/msisdn/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -170,8 +176,7 @@ RequestTokenTo3PIDMSISDNJob::~RequestTokenTo3PIDMSISDNJob() = default; const Sid& RequestTokenTo3PIDMSISDNJob::data() const { return d->data; } -BaseJob::Status -RequestTokenTo3PIDMSISDNJob::parseJson(const QJsonDocument& data) +BaseJob::Status RequestTokenTo3PIDMSISDNJob::parseJson(const QJsonDocument& data) { fromJson(data, d->data); return Success; diff --git a/lib/csapi/administrative_contact.h b/lib/csapi/administrative_contact.h index d99fde42..7f2d0cdc 100644 --- a/lib/csapi/administrative_contact.h +++ b/lib/csapi/administrative_contact.h @@ -4,17 +4,71 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/../identity/definitions/sid.h" + +#include "jobs/basejob.h" + #include <QtCore/QVector> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Gets a list of a user's third party identifiers. +/*! + * Gets a list of the third party identifiers that the homeserver has + * associated with the user's account. + * + * This is *not* the same as the list of third party identifiers bound to + * the user's Matrix ID in identity servers. + * + * Identifiers in this list may be used by the homeserver as, for example, + * identifiers that it will accept to reset the user's account password. + */ +class GetAccount3PIDsJob : public BaseJob +{ +public: + // Inner data structures + + /// Gets a list of the third party identifiers that the homeserver + /// hasassociated with the user's account.This is *not* the same as the list + /// of third party identifiers bound tothe user's Matrix ID in identity + /// servers.Identifiers in this list may be used by the homeserver as, for + /// example,identifiers that it will accept to reset the user's account + /// password. + struct ThirdPartyIdentifier + { + /// The medium of the third party identifier. + QString medium; + /// The third party identifier address. + QString address; + /// The timestamp, in milliseconds, when the identifier wasvalidated by + /// the identity server. + qint64 validatedAt; + /// The timestamp, in milliseconds, when the homeserver associated the + /// third party identifier with the user. + qint64 addedAt; + }; + + // Construction/destruction + + explicit GetAccount3PIDsJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAccount3PIDsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetAccount3PIDsJob() override; + + // Result properties - /// Gets a list of a user's third party identifiers. - /// /// Gets a list of the third party identifiers that the homeserver has /// associated with the user's account. /// @@ -23,227 +77,183 @@ namespace QMatrixClient { /// /// Identifiers in this list may be used by the homeserver as, for example, /// identifiers that it will accept to reset the user's account password. - class GetAccount3PIDsJob : public BaseJob - { - public: - // Inner data structures - - /// Gets a list of the third party identifiers that the homeserver has - /// associated with the user's account. - /// - /// This is *not* the same as the list of third party identifiers bound - /// to the user's Matrix ID in identity servers. - /// - /// Identifiers in this list may be used by the homeserver as, for - /// example, identifiers that it will accept to reset the user's account - /// password. - struct ThirdPartyIdentifier { - /// The medium of the third party identifier. - QString medium; - /// The third party identifier address. - QString address; - /// The timestamp, in milliseconds, when the identifier was - /// validated by the identity server. - qint64 validatedAt; - /// The timestamp, in milliseconds, when the homeserver associated - /// the third party identifier with the user. - qint64 addedAt; - }; - - // Construction/destruction - - explicit GetAccount3PIDsJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetAccount3PIDsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetAccount3PIDsJob() override; - - // Result properties - - /// Gets a list of the third party identifiers that the homeserver has - /// associated with the user's account. - /// - /// This is *not* the same as the list of third party identifiers bound - /// to the user's Matrix ID in identity servers. - /// - /// Identifiers in this list may be used by the homeserver as, for - /// example, identifiers that it will accept to reset the user's account - /// password. - const QVector<ThirdPartyIdentifier>& threepids() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + const QVector<ThirdPartyIdentifier>& threepids() const; - /// Adds contact information to the user's account. - /// - /// Adds contact information to the user's account. - class Post3PIDsJob : public BaseJob - { - public: - // Inner data structures - - /// The third party credentials to associate with the account. - struct ThreePidCredentials { - /// The client secret used in the session with the identity server. - QString clientSecret; - /// The identity server to use. - QString idServer; - /// The session identifier given by the identity server. - QString sid; - }; - - // Construction/destruction - - /*! Adds contact information to the user's account. - * \param threePidCreds - * The third party credentials to associate with the account. - * \param bind - * Whether the homeserver should also bind this third party - * identifier to the account's Matrix ID with the passed identity - * server. Default: ``false``. - */ - explicit Post3PIDsJob(const ThreePidCredentials& threePidCreds, - Omittable<bool> bind = none); - }; +protected: + Status parseJson(const QJsonDocument& data) override; - /// Deletes a third party identifier from the user's account - /// - /// Removes a third party identifier from the user's account. This might not - /// cause an unbind of the identifier from the identity server. - class Delete3pidFromAccountJob : public BaseJob - { - public: - /*! Deletes a third party identifier from the user's account - * \param medium - * The medium of the third party identifier being removed. - * \param address - * The third party address being removed. - */ - explicit Delete3pidFromAccountJob(const QString& medium, - const QString& address); - }; +private: + class Private; + QScopedPointer<Private> d; +}; - /// Begins the validation process for an email address for association with - /// the user's account. - /// - /// Proxies the Identity Service API ``validate/email/requestToken``, but - /// first checks that the given email address is **not** already associated - /// with an account on this homeserver. This API should be used to request - /// validation tokens when adding an email address to an account. This API's - /// parameters and response are identical to that of the - /// |/register/email/requestToken|_ endpoint. - class RequestTokenTo3PIDEmailJob : public BaseJob - { - public: - /*! Begins the validation process for an email address for association with the user's account. - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param email The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenTo3PIDEmailJob(const QString& clientSecret, - const QString& email, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - ~RequestTokenTo3PIDEmailJob() override; - - // Result properties - - /// An email was sent to the given address. - const Sid& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +/// Adds contact information to the user's account. +/*! + * Adds contact information to the user's account. + */ +class Post3PIDsJob : public BaseJob +{ +public: + // Inner data structures - /// Begins the validation process for a phone number for association with - /// the user's account. - /// - /// Proxies the Identity Service API ``validate/msisdn/requestToken``, but - /// first checks that the given phone number is **not** already associated - /// with an account on this homeserver. This API should be used to request - /// validation tokens when adding a phone number to an account. This API's - /// parameters and response are identical to that of the - /// |/register/msisdn/requestToken|_ endpoint. - class RequestTokenTo3PIDMSISDNJob : public BaseJob + /// The third party credentials to associate with the account. + struct ThreePidCredentials { - public: - /*! Begins the validation process for a phone number for association with the user's account. - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param country The two-letter uppercase ISO - * country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenTo3PIDMSISDNJob(const QString& clientSecret, - const QString& country, - const QString& phoneNumber, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - ~RequestTokenTo3PIDMSISDNJob() override; - - // Result properties - - /// An SMS message was sent to the given phone number. - const Sid& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The client secret used in the session with the identity server. + QString clientSecret; + /// The identity server to use. + QString idServer; + /// The session identifier given by the identity server. + QString sid; }; + + // Construction/destruction + + /*! Adds contact information to the user's account. + * \param threePidCreds + * The third party credentials to associate with the account. + * \param bind + * Whether the homeserver should also bind this third party + * identifier to the account's Matrix ID with the passed identity + * server. Default: ``false``. + */ + explicit Post3PIDsJob(const ThreePidCredentials& threePidCreds, + Omittable<bool> bind = none); +}; + +/// Deletes a third party identifier from the user's account +/*! + * Removes a third party identifier from the user's account. This might not + * cause an unbind of the identifier from the identity server. + */ +class Delete3pidFromAccountJob : public BaseJob +{ +public: + /*! Deletes a third party identifier from the user's account + * \param medium + * The medium of the third party identifier being removed. + * \param address + * The third party address being removed. + */ + explicit Delete3pidFromAccountJob(const QString& medium, + const QString& address); +}; + +/// Begins the validation process for an email address for association with the +/// user's account. +/*! + * Proxies the Identity Service API ``validate/email/requestToken``, but + * first checks that the given email address is **not** already associated + * with an account on this homeserver. This API should be used to request + * validation tokens when adding an email address to an account. This API's + * parameters and response are identical to that of the + * |/register/email/requestToken|_ endpoint. + */ +class RequestTokenTo3PIDEmailJob : public BaseJob +{ +public: + /*! Begins the validation process for an email address for association with + * the user's account. \param clientSecret A unique string generated by the + * client, and used to identify the validation attempt. It must be a string + * consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param email + * The email address to validate. + * \param sendAttempt + * The server will only send an email if the ``send_attempt`` + * is a number greater than the most recent one which it has seen, + * scoped to that ``email`` + ``client_secret`` pair. This is to + * avoid repeatedly sending the same email in the case of request + * retries between the POSTing user and the identity server. + * The client should increment this value if they desire a new + * email (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenTo3PIDEmailJob(const QString& clientSecret, + const QString& email, int sendAttempt, + const QString& idServer, + const QString& nextLink = {}); + + ~RequestTokenTo3PIDEmailJob() override; + + // Result properties + + /// An email was sent to the given address. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Begins the validation process for a phone number for association with the +/// user's account. +/*! + * Proxies the Identity Service API ``validate/msisdn/requestToken``, but + * first checks that the given phone number is **not** already associated + * with an account on this homeserver. This API should be used to request + * validation tokens when adding a phone number to an account. This API's + * parameters and response are identical to that of the + * |/register/msisdn/requestToken|_ endpoint. + */ +class RequestTokenTo3PIDMSISDNJob : public BaseJob +{ +public: + /*! Begins the validation process for a phone number for association with + * the user's account. \param clientSecret A unique string generated by the + * client, and used to identify the validation attempt. It must be a string + * consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param country + * The two-letter uppercase ISO country code that the number in + * ``phone_number`` should be parsed as if it were dialled from. + * \param phoneNumber + * The phone number to validate. + * \param sendAttempt + * The server will only send an SMS if the ``send_attempt`` is a + * number greater than the most recent one which it has seen, + * scoped to that ``country`` + ``phone_number`` + ``client_secret`` + * triple. This is to avoid repeatedly sending the same SMS in + * the case of request retries between the POSTing user and the + * identity server. The client should increment this value if + * they desire a new SMS (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenTo3PIDMSISDNJob(const QString& clientSecret, + const QString& country, + const QString& phoneNumber, + int sendAttempt, + const QString& idServer, + const QString& nextLink = {}); + + ~RequestTokenTo3PIDMSISDNJob() override; + + // Result properties + + /// An SMS message was sent to the given phone number. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/appservice_room_directory.cpp b/lib/csapi/appservice_room_directory.cpp index cf76874c..74e037cd 100644 --- a/lib/csapi/appservice_room_directory.cpp +++ b/lib/csapi/appservice_room_directory.cpp @@ -13,15 +13,12 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); static const auto UpdateAppserviceRoomDirectoryVsibilityJobName = - QStringLiteral("UpdateAppserviceRoomDirectoryVsibilityJob"); + QStringLiteral("UpdateAppserviceRoomDirectoryVsibilityJob"); -UpdateAppserviceRoomDirectoryVsibilityJob:: - UpdateAppserviceRoomDirectoryVsibilityJob(const QString& networkId, - const QString& roomId, - const QString& visibility) +UpdateAppserviceRoomDirectoryVsibilityJob::UpdateAppserviceRoomDirectoryVsibilityJob( + const QString& networkId, const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, UpdateAppserviceRoomDirectoryVsibilityJobName, - basePath % "/directory/list/appservice/" % networkId % "/" - % roomId) + basePath % "/directory/list/appservice/" % networkId % "/" % roomId) { QJsonObject _data; addParam<>(_data, QStringLiteral("visibility"), visibility); diff --git a/lib/csapi/appservice_room_directory.h b/lib/csapi/appservice_room_directory.h index 2ee680c9..d1c3f89f 100644 --- a/lib/csapi/appservice_room_directory.h +++ b/lib/csapi/appservice_room_directory.h @@ -6,37 +6,40 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Updates a room's visibility in the application service's room directory. +/*! + * Updates the visibility of a given room on the application service's room + * directory. + * + * This API is similar to the room directory visibility API used by clients + * to update the homeserver's more general room directory. + * + * This API requires the use of an application service access token + * (``as_token``) instead of a typical client's access_token. This API cannot be + * invoked by users who are not identified as application services. + */ +class UpdateAppserviceRoomDirectoryVsibilityJob : public BaseJob +{ +public: + /*! Updates a room's visibility in the application service's room directory. + * \param networkId + * The protocol (network) ID to update the room list for. This would + * have been provided by the application service as being listed as + * a supported protocol. + * \param roomId + * The room ID to add to the directory. + * \param visibility + * Whether the room should be visible (public) in the directory + * or not (private). + */ + explicit UpdateAppserviceRoomDirectoryVsibilityJob(const QString& networkId, + const QString& roomId, + const QString& visibility); +}; - /// Updates a room's visibility in the application service's room directory. - /// - /// Updates the visibility of a given room on the application service's room - /// directory. - /// - /// This API is similar to the room directory visibility API used by clients - /// to update the homeserver's more general room directory. - /// - /// This API requires the use of an application service access token - /// (``as_token``) instead of a typical client's access_token. This API - /// cannot be invoked by users who are not identified as application - /// services. - class UpdateAppserviceRoomDirectoryVsibilityJob : public BaseJob - { - public: - /*! Updates a room's visibility in the application service's room directory. - * \param networkId - * The protocol (network) ID to update the room list for. This would - * have been provided by the application service as being listed as - * a supported protocol. - * \param roomId - * The room ID to add to the directory. - * \param visibility - * Whether the room should be visible (public) in the directory - * or not (private). - */ - explicit UpdateAppserviceRoomDirectoryVsibilityJob( - const QString& networkId, const QString& roomId, - const QString& visibility); - }; } // namespace QMatrixClient diff --git a/lib/csapi/banning.cpp b/lib/csapi/banning.cpp index 201126c3..a46e78f1 100644 --- a/lib/csapi/banning.cpp +++ b/lib/csapi/banning.cpp @@ -16,8 +16,7 @@ static const auto BanJobName = QStringLiteral("BanJob"); BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) - : BaseJob(HttpVerb::Post, BanJobName, - basePath % "/rooms/" % roomId % "/ban") + : BaseJob(HttpVerb::Post, BanJobName, basePath % "/rooms/" % roomId % "/ban") { QJsonObject _data; addParam<>(_data, QStringLiteral("user_id"), userId); diff --git a/lib/csapi/banning.h b/lib/csapi/banning.h index e1886f0e..0aa5785b 100644 --- a/lib/csapi/banning.h +++ b/lib/csapi/banning.h @@ -6,51 +6,56 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Ban a user in the room. - /// - /// Ban a user in the room. If the user is currently in the room, also kick - /// them. - /// - /// When a user is banned from a room, they may not join it or be invited to - /// it until they are unbanned. - /// - /// The caller must have the required power level in order to perform this - /// operation. - class BanJob : public BaseJob - { - public: - /*! Ban a user in the room. - * \param roomId - * The room identifier (not alias) from which the user should be - * banned. \param userId The fully qualified user ID of the user being - * banned. \param reason The reason the user has been banned. This will - * be supplied as the ``reason`` on the target's updated - * `m.room.member`_ event. - */ - explicit BanJob(const QString& roomId, const QString& userId, - const QString& reason = {}); - }; +// Operations + +/// Ban a user in the room. +/*! + * Ban a user in the room. If the user is currently in the room, also kick them. + * + * When a user is banned from a room, they may not join it or be invited to it + * until they are unbanned. + * + * The caller must have the required power level in order to perform this + * operation. + */ +class BanJob : public BaseJob +{ +public: + /*! Ban a user in the room. + * \param roomId + * The room identifier (not alias) from which the user should be banned. + * \param userId + * The fully qualified user ID of the user being banned. + * \param reason + * The reason the user has been banned. This will be supplied as the + * ``reason`` on the target's updated `m.room.member`_ event. + */ + explicit BanJob(const QString& roomId, const QString& userId, + const QString& reason = {}); +}; + +/// Unban a user from the room. +/*! + * Unban a user from the room. This allows them to be invited to the room, + * and join if they would otherwise be allowed to join according to its join + * rules. + * + * The caller must have the required power level in order to perform this + * operation. + */ +class UnbanJob : public BaseJob +{ +public: + /*! Unban a user from the room. + * \param roomId + * The room identifier (not alias) from which the user should be unbanned. + * \param userId + * The fully qualified user ID of the user being unbanned. + */ + explicit UnbanJob(const QString& roomId, const QString& userId); +}; - /// Unban a user from the room. - /// - /// Unban a user from the room. This allows them to be invited to the room, - /// and join if they would otherwise be allowed to join according to its - /// join rules. - /// - /// The caller must have the required power level in order to perform this - /// operation. - class UnbanJob : public BaseJob - { - public: - /*! Unban a user from the room. - * \param roomId - * The room identifier (not alias) from which the user should be - * unbanned. \param userId The fully qualified user ID of the user being - * unbanned. - */ - explicit UnbanJob(const QString& roomId, const QString& userId); - }; } // namespace QMatrixClient diff --git a/lib/csapi/capabilities.cpp b/lib/csapi/capabilities.cpp index 0fb9fbae..9a054fe9 100644 --- a/lib/csapi/capabilities.cpp +++ b/lib/csapi/capabilities.cpp @@ -12,43 +12,48 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> - struct JsonObjectConverter<GetCapabilitiesJob::ChangePasswordCapability> { - static void - fillFrom(const QJsonObject& jo, - GetCapabilitiesJob::ChangePasswordCapability& result) - { - fromJson(jo.value("enabled"_ls), result.enabled); - } - }; - - template <> - struct JsonObjectConverter<GetCapabilitiesJob::RoomVersionsCapability> { - static void fillFrom(const QJsonObject& jo, - GetCapabilitiesJob::RoomVersionsCapability& result) - { - fromJson(jo.value("default"_ls), result.defaultVersion); - fromJson(jo.value("available"_ls), result.available); - } - }; - - template <> struct JsonObjectConverter<GetCapabilitiesJob::Capabilities> { - 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); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetCapabilitiesJob::ChangePasswordCapability> +{ + static void fillFrom(const QJsonObject& jo, + GetCapabilitiesJob::ChangePasswordCapability& result) + { + fromJson(jo.value("enabled"_ls), result.enabled); + } +}; + +template <> +struct JsonObjectConverter<GetCapabilitiesJob::RoomVersionsCapability> +{ + static void fillFrom(const QJsonObject& jo, + GetCapabilitiesJob::RoomVersionsCapability& result) + { + fromJson(jo.value("default"_ls), result.defaultVersion); + fromJson(jo.value("available"_ls), result.available); + } +}; + +template <> +struct JsonObjectConverter<GetCapabilitiesJob::Capabilities> +{ + 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: +public: Capabilities capabilities; }; @@ -61,11 +66,9 @@ QUrl GetCapabilitiesJob::makeRequestUrl(QUrl baseUrl) static const auto GetCapabilitiesJobName = QStringLiteral("GetCapabilitiesJob"); GetCapabilitiesJob::GetCapabilitiesJob() - : BaseJob(HttpVerb::Get, GetCapabilitiesJobName, - basePath % "/capabilities"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetCapabilitiesJobName, basePath % "/capabilities") + , d(new Private) +{} GetCapabilitiesJob::~GetCapabilitiesJob() = default; @@ -78,8 +81,9 @@ BaseJob::Status GetCapabilitiesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("capabilities"_ls)) - return { JsonParseError, + return { IncorrectResponse, "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 6282c2fd..f6e7ad06 100644 --- a/lib/csapi/capabilities.h +++ b/lib/csapi/capabilities.h @@ -4,75 +4,84 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QHash> #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Gets information about the server's capabilities. +/*! + * Gets information about the server's supported feature set + * and other relevant capabilities. + */ +class GetCapabilitiesJob : public BaseJob +{ +public: + // Inner data structures - /// Gets information about the server's capabilities. - /// - /// Gets information about the server's supported feature set - /// and other relevant capabilities. - class GetCapabilitiesJob : public BaseJob + /// Capability to indicate if the user can change their password. + struct ChangePasswordCapability { - public: - // Inner data structures + /// True if the user can change their password, false otherwise. + bool enabled; + }; - /// Capability to indicate if the user can change their password. - struct ChangePasswordCapability { - /// True if the user can change their password, false otherwise. - bool enabled; - }; + /// The room versions the server supports. + struct RoomVersionsCapability + { + /// The default room version the server is using for new rooms. + QString defaultVersion; + /// A detailed description of the room versions the server supports. + QHash<QString, QString> available; + }; + /// The custom capabilities the server supports, using theJava package + /// naming convention. + struct Capabilities + { + /// Capability to indicate if the user can change their password. + Omittable<ChangePasswordCapability> changePassword; /// The room versions the server supports. - struct RoomVersionsCapability { - /// The default room version the server is using for new rooms. - QString defaultVersion; - /// A detailed description of the room versions the server supports. - QHash<QString, QString> 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<ChangePasswordCapability> changePassword; - /// The room versions the server supports. - Omittable<RoomVersionsCapability> roomVersions; - /// The custom capabilities the server supports, using the - /// Java package naming convention. - QHash<QString, QJsonObject> additionalProperties; - }; - - // Construction/destruction - - explicit GetCapabilitiesJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetCapabilitiesJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetCapabilitiesJob() override; - - // Result properties - - /// Gets information about the server's supported feature set - /// and other relevant capabilities. - const Capabilities& capabilities() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + Omittable<RoomVersionsCapability> roomVersions; + + /// The custom capabilities the server supports, using theJava package + /// naming convention. + QHash<QString, QJsonObject> additionalProperties; }; + + // Construction/destruction + + explicit GetCapabilitiesJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetCapabilitiesJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetCapabilitiesJob() override; + + // Result properties + + /// The custom capabilities the server supports, using the + /// Java package naming convention. + const Capabilities& capabilities() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/content-repo.cpp b/lib/csapi/content-repo.cpp index d59449b9..c2720d63 100644 --- a/lib/csapi/content-repo.cpp +++ b/lib/csapi/content-repo.cpp @@ -15,7 +15,7 @@ static const auto basePath = QStringLiteral("/_matrix/media/r0"); class UploadContentJob::Private { - public: +public: QString contentUri; }; @@ -31,11 +31,10 @@ static const auto UploadContentJobName = QStringLiteral("UploadContentJob"); UploadContentJob::UploadContentJob(QIODevice* content, const QString& filename, const QString& contentType) : BaseJob(HttpVerb::Post, UploadContentJobName, basePath % "/upload", - queryToUploadContent(filename)), - d(new Private) + queryToUploadContent(filename)) + , d(new Private) { setRequestHeader("Content-Type", contentType.toLatin1()); - setRequestData(Data(content)); } @@ -47,15 +46,16 @@ BaseJob::Status UploadContentJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("content_uri"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'content_uri' not found in the response" }; fromJson(json.value("content_uri"_ls), d->contentUri); + return Success; } class GetContentJob::Private { - public: +public: QString contentType; QString contentDisposition; QIODevice* data; @@ -73,7 +73,7 @@ QUrl GetContentJob::makeRequestUrl(QUrl baseUrl, const QString& serverName, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/download/" % serverName % "/" - % mediaId, + % mediaId, queryToGetContent(allowRemote)); } @@ -83,8 +83,8 @@ GetContentJob::GetContentJob(const QString& serverName, const QString& mediaId, bool allowRemote) : BaseJob(HttpVerb::Get, GetContentJobName, basePath % "/download/" % serverName % "/" % mediaId, - queryToGetContent(allowRemote), {}, false), - d(new Private) + queryToGetContent(allowRemote), {}, false) + , d(new Private) { setExpectedContentTypes({ "*/*" }); } @@ -110,7 +110,7 @@ BaseJob::Status GetContentJob::parseReply(QNetworkReply* reply) class GetContentOverrideNameJob::Private { - public: +public: QString contentType; QString contentDisposition; QIODevice* data; @@ -131,12 +131,12 @@ QUrl GetContentOverrideNameJob::makeRequestUrl(QUrl baseUrl, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/download/" % serverName % "/" - % mediaId % "/" % fileName, + % mediaId % "/" % fileName, queryToGetContentOverrideName(allowRemote)); } static const auto GetContentOverrideNameJobName = - QStringLiteral("GetContentOverrideNameJob"); + QStringLiteral("GetContentOverrideNameJob"); GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, const QString& mediaId, @@ -144,9 +144,9 @@ GetContentOverrideNameJob::GetContentOverrideNameJob(const QString& serverName, bool allowRemote) : BaseJob(HttpVerb::Get, GetContentOverrideNameJobName, basePath % "/download/" % serverName % "/" % mediaId % "/" - % fileName, - queryToGetContentOverrideName(allowRemote), {}, false), - d(new Private) + % fileName, + queryToGetContentOverrideName(allowRemote), {}, false) + , d(new Private) { setExpectedContentTypes({ "*/*" }); } @@ -175,7 +175,7 @@ BaseJob::Status GetContentOverrideNameJob::parseReply(QNetworkReply* reply) class GetContentThumbnailJob::Private { - public: +public: QString contentType; QIODevice* data; }; @@ -199,24 +199,23 @@ QUrl GetContentThumbnailJob::makeRequestUrl(QUrl baseUrl, bool allowRemote) { return BaseJob::makeRequestUrl( - std::move(baseUrl), - basePath % "/thumbnail/" % serverName % "/" % mediaId, - queryToGetContentThumbnail(width, height, method, allowRemote)); + std::move(baseUrl), + basePath % "/thumbnail/" % serverName % "/" % mediaId, + queryToGetContentThumbnail(width, height, method, allowRemote)); } static const auto GetContentThumbnailJobName = - QStringLiteral("GetContentThumbnailJob"); + QStringLiteral("GetContentThumbnailJob"); GetContentThumbnailJob::GetContentThumbnailJob(const QString& serverName, - const QString& mediaId, - int width, int height, - const QString& method, + const QString& mediaId, int width, + int height, const QString& method, bool allowRemote) : BaseJob(HttpVerb::Get, GetContentThumbnailJobName, basePath % "/thumbnail/" % serverName % "/" % mediaId, queryToGetContentThumbnail(width, height, method, allowRemote), - {}, false), - d(new Private) + {}, false) + , d(new Private) { setExpectedContentTypes({ "image/jpeg", "image/png" }); } @@ -239,7 +238,7 @@ BaseJob::Status GetContentThumbnailJob::parseReply(QNetworkReply* reply) class GetUrlPreviewJob::Private { - public: +public: Omittable<qint64> matrixImageSize; QString ogImage; }; @@ -255,8 +254,7 @@ BaseJob::Query queryToGetUrlPreview(const QString& url, Omittable<qint64> ts) QUrl GetUrlPreviewJob::makeRequestUrl(QUrl baseUrl, const QString& url, Omittable<qint64> ts) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/preview_url", + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/preview_url", queryToGetUrlPreview(url, ts)); } @@ -264,10 +262,9 @@ static const auto GetUrlPreviewJobName = QStringLiteral("GetUrlPreviewJob"); GetUrlPreviewJob::GetUrlPreviewJob(const QString& url, Omittable<qint64> ts) : BaseJob(HttpVerb::Get, GetUrlPreviewJobName, basePath % "/preview_url", - queryToGetUrlPreview(url, ts)), - d(new Private) -{ -} + queryToGetUrlPreview(url, ts)) + , d(new Private) +{} GetUrlPreviewJob::~GetUrlPreviewJob() = default; @@ -283,12 +280,13 @@ BaseJob::Status GetUrlPreviewJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("matrix:image:size"_ls), d->matrixImageSize); fromJson(json.value("og:image"_ls), d->ogImage); + return Success; } class GetConfigJob::Private { - public: +public: Omittable<qint64> uploadSize; }; @@ -300,10 +298,9 @@ QUrl GetConfigJob::makeRequestUrl(QUrl baseUrl) static const auto GetConfigJobName = QStringLiteral("GetConfigJob"); GetConfigJob::GetConfigJob() - : BaseJob(HttpVerb::Get, GetConfigJobName, basePath % "/config"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetConfigJobName, basePath % "/config") + , d(new Private) +{} GetConfigJob::~GetConfigJob() = default; @@ -313,5 +310,6 @@ BaseJob::Status GetConfigJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("m.upload.size"_ls), d->uploadSize); + return Success; } diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h index 1bef6380..9f267f6c 100644 --- a/lib/csapi/content-repo.h +++ b/lib/csapi/content-repo.h @@ -4,276 +4,282 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QIODevice> -namespace QMatrixClient { - // Operations - - /// Upload some content to the content repository. - class UploadContentJob : public BaseJob - { - public: - /*! Upload some content to the content repository. - * \param content - * \param filename - * The name of the file being uploaded - * \param contentType - * The content type of the file being uploaded - */ - explicit UploadContentJob(QIODevice* content, - const QString& filename = {}, - const QString& contentType = {}); - ~UploadContentJob() override; - - // Result properties - - /// The MXC URI to the uploaded content. - const QString& contentUri() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Download content from the content repository. - class GetContentJob : public BaseJob - { - public: - /*! Download content from the content repository. - * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) - * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) - * \param allowRemote - * Indicates to the server that it should not attempt to fetch the - * media if it is deemed remote. This is to prevent routing loops where - * the server contacts itself. Defaults to true if not provided. - */ - explicit GetContentJob(const QString& serverName, +namespace QMatrixClient +{ + +// Operations + +/// Upload some content to the content repository. + +class UploadContentJob : public BaseJob +{ +public: + /*! Upload some content to the content repository. + * \param content + * \param filename + * The name of the file being uploaded + * \param contentType + * The content type of the file being uploaded + */ + explicit UploadContentJob(QIODevice* content, const QString& filename = {}, + const QString& contentType = {}); + + ~UploadContentJob() override; + + // Result properties + + /// The MXC URI to the uploaded content. + const QString& contentUri() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Download content from the content repository. + +class GetContentJob : public BaseJob +{ +public: + /*! Download content from the content repository. + * \param serverName + * The server name from the ``mxc://`` URI (the authoritory component) + * \param mediaId + * The media ID from the ``mxc://`` URI (the path component) + * \param allowRemote + * Indicates to the server that it should not attempt to fetch the media + * if it is deemed remote. This is to prevent routing loops where the server + * contacts itself. Defaults to true if not provided. + */ + explicit GetContentJob(const QString& serverName, const QString& mediaId, + bool allowRemote = true); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetContentJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, const QString& mediaId, bool allowRemote = true); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetContentJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, - const QString& mediaId, - bool allowRemote = true); - - ~GetContentJob() override; - - // Result properties - - /// The content type of the file that was previously uploaded. - const QString& contentType() const; - /// The name of the file that was previously uploaded, if set. - const QString& contentDisposition() const; - /// The content that was previously uploaded. - QIODevice* data() const; - - protected: - Status parseReply(QNetworkReply* reply) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Download content from the content repository as a given filename. - class GetContentOverrideNameJob : public BaseJob - { - public: - /*! Download content from the content repository as a given filename. - * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) - * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) - * \param fileName - * The filename to give in the Content-Disposition - * \param allowRemote - * Indicates to the server that it should not attempt to fetch the - * media if it is deemed remote. This is to prevent routing loops where - * the server contacts itself. Defaults to true if not provided. - */ - explicit GetContentOverrideNameJob(const QString& serverName, - const QString& mediaId, - const QString& fileName, - bool allowRemote = true); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetContentOverrideNameJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, - const QString& mediaId, - const QString& fileName, - bool allowRemote = true); - - ~GetContentOverrideNameJob() override; - - // Result properties - - /// The content type of the file that was previously uploaded. - const QString& contentType() const; - /// The name of file given in the request - const QString& contentDisposition() const; - /// The content that was previously uploaded. - QIODevice* data() const; - - protected: - Status parseReply(QNetworkReply* reply) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Download a thumbnail of the content from the content repository. - class GetContentThumbnailJob : public BaseJob - { - public: - /*! Download a thumbnail of the content from the content repository. - * \param serverName - * The server name from the ``mxc://`` URI (the authoritory component) - * \param mediaId - * The media ID from the ``mxc://`` URI (the path component) - * \param width - * The *desired* width of the thumbnail. The actual thumbnail may not - * match the size specified. - * \param height - * The *desired* height of the thumbnail. The actual thumbnail may not - * match the size specified. - * \param method - * The desired resizing method. - * \param allowRemote - * Indicates to the server that it should not attempt to fetch the - * media if it is deemed remote. This is to prevent routing loops where - * the server contacts itself. Defaults to true if not provided. - */ - explicit GetContentThumbnailJob(const QString& serverName, - const QString& mediaId, int width, - int height, const QString& method = {}, - bool allowRemote = true); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetContentThumbnailJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, - const QString& mediaId, int width, - int height, const QString& method = {}, - bool allowRemote = true); - - ~GetContentThumbnailJob() override; - - // Result properties - - /// The content type of the thumbnail. - const QString& contentType() const; - /// A thumbnail of the requested content. - QIODevice* data() const; - - protected: - Status parseReply(QNetworkReply* reply) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Get information about a URL for a client - class GetUrlPreviewJob : public BaseJob - { - public: - /*! Get information about a URL for a client - * \param url - * The URL to get a preview of - * \param ts - * The preferred point in time to return a preview for. The server may - * return a newer version if it does not have the requested version - * available. - */ - explicit GetUrlPreviewJob(const QString& url, - Omittable<qint64> ts = none); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetUrlPreviewJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& url, - Omittable<qint64> ts = none); - - ~GetUrlPreviewJob() override; - - // Result properties - - /// The byte-size of the image. Omitted if there is no image attached. - Omittable<qint64> matrixImageSize() const; - /// An MXC URI to the image. Omitted if there is no image. - const QString& ogImage() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Get the configuration for the content repository. - /// - /// This endpoint allows clients to retrieve the configuration of the - /// content repository, such as upload limitations. Clients SHOULD use this - /// as a guide when using content repository endpoints. All values are - /// intentionally left optional. Clients SHOULD follow the advice given in - /// the field description when the field is not available. - /// - /// **NOTE:** Both clients and server administrators should be aware that - /// proxies between the client and the server may affect the apparent - /// behaviour of content repository APIs, for example, proxies may enforce a - /// lower upload size limit than is advertised by the server on this - /// endpoint. - class GetConfigJob : public BaseJob - { - public: - explicit GetConfigJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetConfigJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetConfigJob() override; - - // Result properties - - /// The maximum size an upload can be in bytes. - /// Clients SHOULD use this as a guide when uploading content. - /// If not listed or null, the size limit should be treated as unknown. - Omittable<qint64> uploadSize() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + ~GetContentJob() override; + + // Result properties + + /// The content type of the file that was previously uploaded. + const QString& contentType() const; + /// The name of the file that was previously uploaded, if set. + const QString& contentDisposition() const; + /// The content that was previously uploaded. + QIODevice* data() const; + +protected: + Status parseReply(QNetworkReply* reply) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Download content from the content repository as a given filename. + +class GetContentOverrideNameJob : public BaseJob +{ +public: + /*! Download content from the content repository as a given filename. + * \param serverName + * The server name from the ``mxc://`` URI (the authoritory component) + * \param mediaId + * The media ID from the ``mxc://`` URI (the path component) + * \param fileName + * The filename to give in the Content-Disposition + * \param allowRemote + * Indicates to the server that it should not attempt to fetch the media + * if it is deemed remote. This is to prevent routing loops where the server + * contacts itself. Defaults to true if not provided. + */ + explicit GetContentOverrideNameJob(const QString& serverName, + const QString& mediaId, + const QString& fileName, + bool allowRemote = true); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetContentOverrideNameJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, + const QString& mediaId, const QString& fileName, + bool allowRemote = true); + + ~GetContentOverrideNameJob() override; + + // Result properties + + /// The content type of the file that was previously uploaded. + const QString& contentType() const; + /// The name of file given in the request + const QString& contentDisposition() const; + /// The content that was previously uploaded. + QIODevice* data() const; + +protected: + Status parseReply(QNetworkReply* reply) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Download a thumbnail of the content from the content repository. + +class GetContentThumbnailJob : public BaseJob +{ +public: + /*! Download a thumbnail of the content from the content repository. + * \param serverName + * The server name from the ``mxc://`` URI (the authoritory component) + * \param mediaId + * The media ID from the ``mxc://`` URI (the path component) + * \param width + * The *desired* width of the thumbnail. The actual thumbnail may not + * match the size specified. + * \param height + * The *desired* height of the thumbnail. The actual thumbnail may not + * match the size specified. + * \param method + * The desired resizing method. + * \param allowRemote + * Indicates to the server that it should not attempt to fetch the media + * if it is deemed remote. This is to prevent routing loops where the server + * contacts itself. Defaults to true if not provided. + */ + explicit GetContentThumbnailJob(const QString& serverName, + const QString& mediaId, int width, + int height, const QString& method = {}, + bool allowRemote = true); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetContentThumbnailJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& serverName, + const QString& mediaId, int width, int height, + const QString& method = {}, + bool allowRemote = true); + + ~GetContentThumbnailJob() override; + + // Result properties + + /// The content type of the thumbnail. + const QString& contentType() const; + /// A thumbnail of the requested content. + QIODevice* data() const; + +protected: + Status parseReply(QNetworkReply* reply) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get information about a URL for a client + +class GetUrlPreviewJob : public BaseJob +{ +public: + /*! Get information about a URL for a client + * \param url + * The URL to get a preview of + * \param ts + * The preferred point in time to return a preview for. The server may + * return a newer version if it does not have the requested version + * available. + */ + explicit GetUrlPreviewJob(const QString& url, Omittable<qint64> ts = none); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetUrlPreviewJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& url, + Omittable<qint64> ts = none); + + ~GetUrlPreviewJob() override; + + // Result properties + + /// The byte-size of the image. Omitted if there is no image attached. + Omittable<qint64> matrixImageSize() const; + /// An MXC URI to the image. Omitted if there is no image. + const QString& ogImage() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get the configuration for the content repository. +/*! + * This endpoint allows clients to retrieve the configuration of the content + * repository, such as upload limitations. + * Clients SHOULD use this as a guide when using content repository endpoints. + * All values are intentionally left optional. Clients SHOULD follow + * the advice given in the field description when the field is not available. + * + * **NOTE:** Both clients and server administrators should be aware that proxies + * between the client and the server may affect the apparent behaviour of + * content repository APIs, for example, proxies may enforce a lower upload size + * limit than is advertised by the server on this endpoint. + */ +class GetConfigJob : public BaseJob +{ +public: + explicit GetConfigJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetConfigJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetConfigJob() override; + + // Result properties + + /// The maximum size an upload can be in bytes. + /// Clients SHOULD use this as a guide when uploading content. + /// If not listed or null, the size limit should be treated as unknown. + Omittable<qint64> uploadSize() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/create_room.cpp b/lib/csapi/create_room.cpp index 47a13d8e..e94cb008 100644 --- a/lib/csapi/create_room.cpp +++ b/lib/csapi/create_room.cpp @@ -12,33 +12,37 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<CreateRoomJob::Invite3pid> { - static void dumpTo(QJsonObject& jo, - const CreateRoomJob::Invite3pid& pod) - { - addParam<>(jo, QStringLiteral("id_server"), pod.idServer); - addParam<>(jo, QStringLiteral("medium"), pod.medium); - addParam<>(jo, QStringLiteral("address"), pod.address); - } - }; - - template <> struct JsonObjectConverter<CreateRoomJob::StateEvent> { - static void dumpTo(QJsonObject& jo, - const CreateRoomJob::StateEvent& pod) - { - addParam<>(jo, QStringLiteral("type"), pod.type); - addParam<IfNotEmpty>(jo, QStringLiteral("state_key"), pod.stateKey); - addParam<>(jo, QStringLiteral("content"), pod.content); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<CreateRoomJob::Invite3pid> +{ + static void dumpTo(QJsonObject& jo, const CreateRoomJob::Invite3pid& pod) + { + addParam<>(jo, QStringLiteral("id_server"), pod.idServer); + addParam<>(jo, QStringLiteral("medium"), pod.medium); + addParam<>(jo, QStringLiteral("address"), pod.address); + } +}; + +template <> +struct JsonObjectConverter<CreateRoomJob::StateEvent> +{ + static void dumpTo(QJsonObject& jo, const CreateRoomJob::StateEvent& pod) + { + addParam<>(jo, QStringLiteral("type"), pod.type); + addParam<IfNotEmpty>(jo, QStringLiteral("state_key"), pod.stateKey); + addParam<>(jo, QStringLiteral("content"), pod.content); + } +}; + } // namespace QMatrixClient class CreateRoomJob::Private { - public: +public: QString roomId; }; @@ -53,8 +57,8 @@ CreateRoomJob::CreateRoomJob(const QString& visibility, const QVector<StateEvent>& initialState, const QString& preset, Omittable<bool> isDirect, const QJsonObject& powerLevelContentOverride) - : BaseJob(HttpVerb::Post, CreateRoomJobName, basePath % "/createRoom"), - d(new Private) + : BaseJob(HttpVerb::Post, CreateRoomJobName, basePath % "/createRoom") + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("visibility"), visibility); @@ -83,8 +87,9 @@ BaseJob::Status CreateRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); + return Success; } diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h index 4348bd63..a066a3f3 100644 --- a/lib/csapi/create_room.h +++ b/lib/csapi/create_room.h @@ -4,269 +4,235 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QJsonObject> #include <QtCore/QVector> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Create a new room +/*! + * Create a new room with various configuration options. + * + * The server MUST apply the normal state resolution rules when creating + * the new room, including checking power levels for each event. It MUST + * apply the events implied by the request in the following order: + * + * 0. A default ``m.room.power_levels`` event, giving the room creator + * (and not other members) permission to send state events. Overridden + * by the ``power_level_content_override`` parameter. + * + * 1. Events set by the ``preset``. Currently these are the + * ``m.room.join_rules``, + * ``m.room.history_visibility``, and ``m.room.guest_access`` state events. + * + * 2. Events listed in ``initial_state``, in the order that they are + * listed. + * + * 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + * ``m.room.topic`` state events). + * + * 4. Invite events implied by ``invite`` and ``invite_3pid`` (``m.room.member`` + * with + * ``membership: invite`` and ``m.room.third_party_invite``). + * + * The available presets do the following with respect to room state: + * + * ======================== ============== ====================== + * ================ ========= Preset ``join_rules`` + * ``history_visibility`` ``guest_access`` Other + * ======================== ============== ====================== + * ================ ========= + * ``private_chat`` ``invite`` ``shared`` ``can_join`` + * ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All + * invitees are given the same power level as the room creator. + * ``public_chat`` ``public`` ``shared`` ``forbidden`` + * ======================== ============== ====================== + * ================ ========= + * + * The server will create a ``m.room.create`` event in the room with the + * requesting user as the creator, alongside other keys provided in the + * ``creation_content``. + */ +class CreateRoomJob : public BaseJob +{ +public: + // Inner data structures - /// Create a new room - /// - /// Create a new room with various configuration options. - /// - /// The server MUST apply the normal state resolution rules when creating - /// the new room, including checking power levels for each event. It MUST - /// apply the events implied by the request in the following order: - /// - /// 0. A default ``m.room.power_levels`` event, giving the room creator - /// (and not other members) permission to send state events. Overridden - /// by the ``power_level_content_override`` parameter. - /// - /// 1. Events set by the ``preset``. Currently these are the - /// ``m.room.join_rules``, - /// ``m.room.history_visibility``, and ``m.room.guest_access`` state - /// events. - /// - /// 2. Events listed in ``initial_state``, in the order that they are - /// listed. - /// - /// 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and - /// ``m.room.topic`` - /// state events). - /// - /// 4. Invite events implied by ``invite`` and ``invite_3pid`` - /// (``m.room.member`` with - /// ``membership: invite`` and ``m.room.third_party_invite``). - /// - /// The available presets do the following with respect to room state: - /// - /// ======================== ============== ====================== - /// ================ ========= - /// Preset ``join_rules`` ``history_visibility`` - /// ``guest_access`` Other - /// ======================== ============== ====================== - /// ================ ========= - /// ``private_chat`` ``invite`` ``shared`` ``can_join`` - /// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` All - /// invitees are given the same power level as the room creator. - /// ``public_chat`` ``public`` ``shared`` ``forbidden`` - /// ======================== ============== ====================== - /// ================ ========= - /// - /// The server will create a ``m.room.create`` event in the room with the - /// requesting user as the creator, alongside other keys provided in the - /// ``creation_content``. - class CreateRoomJob : public BaseJob + /// Create a new room with various configuration options.The server MUST + /// apply the normal state resolution rules when creatingthe new room, + /// including checking power levels for each event. It MUSTapply the events + /// implied by the request in the following order:0. A default + /// ``m.room.power_levels`` event, giving the room creator (and not other + /// members) permission to send state events. Overridden by the + /// ``power_level_content_override`` parameter.1. Events set by the + /// ``preset``. Currently these are the ``m.room.join_rules``, + /// ``m.room.history_visibility``, and ``m.room.guest_access`` state + /// events.2. Events listed in ``initial_state``, in the order that they are + /// listed.3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + /// ``m.room.topic`` state events).4. Invite events implied by ``invite`` + /// and ``invite_3pid`` (``m.room.member`` with ``membership: invite`` and + /// ``m.room.third_party_invite``).The available presets do the following + /// with respect to room state:======================== ============== + /// ====================== ================ ========= Preset + /// ``join_rules`` ``history_visibility`` ``guest_access`` + /// Other======================== ============== ====================== + /// ================ =========``private_chat`` ``invite`` + /// ``shared`` ``can_join````trusted_private_chat`` ``invite`` + /// ``shared`` ``can_join`` All invitees are given the + /// same power level as the room creator.``public_chat`` ``public`` + /// ``shared`` ``forbidden``======================== + /// ============== ====================== ================ =========The + /// server will create a ``m.room.create`` event in the room with + /// therequesting user as the creator, alongside other keys provided in + /// the``creation_content``. + struct Invite3pid { - public: - // Inner data structures + /// The hostname+port of the identity server which should be used for + /// third party identifier lookups. + QString idServer; + /// The kind of address being passed in the address field, for example + /// ``email``. + QString medium; + /// The invitee's third party identifier. + QString address; + }; - /// Create a new room with various configuration options. - /// - /// The server MUST apply the normal state resolution rules when - /// creating the new room, including checking power levels for each - /// event. It MUST apply the events implied by the request in the - /// following order: - /// - /// 0. A default ``m.room.power_levels`` event, giving the room creator - /// (and not other members) permission to send state events. - /// Overridden by the ``power_level_content_override`` parameter. - /// - /// 1. Events set by the ``preset``. Currently these are the - /// ``m.room.join_rules``, - /// ``m.room.history_visibility``, and ``m.room.guest_access`` state - /// events. - /// - /// 2. Events listed in ``initial_state``, in the order that they are - /// listed. - /// - /// 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and - /// ``m.room.topic`` - /// state events). - /// - /// 4. Invite events implied by ``invite`` and ``invite_3pid`` - /// (``m.room.member`` with - /// ``membership: invite`` and ``m.room.third_party_invite``). - /// - /// The available presets do the following with respect to room state: - /// - /// ======================== ============== ====================== - /// ================ ========= - /// Preset ``join_rules`` ``history_visibility`` - /// ``guest_access`` Other - /// ======================== ============== ====================== - /// ================ ========= - /// ``private_chat`` ``invite`` ``shared`` ``can_join`` - /// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` - /// All invitees are given the same power level as the room creator. - /// ``public_chat`` ``public`` ``shared`` ``forbidden`` - /// ======================== ============== ====================== - /// ================ ========= - /// - /// The server will create a ``m.room.create`` event in the room with - /// the requesting user as the creator, alongside other keys provided in - /// the - /// ``creation_content``. - struct Invite3pid { - /// The hostname+port of the identity server which should be used - /// for third party identifier lookups. - QString idServer; - /// The kind of address being passed in the address field, for - /// example ``email``. - QString medium; - /// The invitee's third party identifier. - QString address; - }; + /// Create a new room with various configuration options.The server MUST + /// apply the normal state resolution rules when creatingthe new room, + /// including checking power levels for each event. It MUSTapply the events + /// implied by the request in the following order:0. A default + /// ``m.room.power_levels`` event, giving the room creator (and not other + /// members) permission to send state events. Overridden by the + /// ``power_level_content_override`` parameter.1. Events set by the + /// ``preset``. Currently these are the ``m.room.join_rules``, + /// ``m.room.history_visibility``, and ``m.room.guest_access`` state + /// events.2. Events listed in ``initial_state``, in the order that they are + /// listed.3. Events implied by ``name`` and ``topic`` (``m.room.name`` and + /// ``m.room.topic`` state events).4. Invite events implied by ``invite`` + /// and ``invite_3pid`` (``m.room.member`` with ``membership: invite`` and + /// ``m.room.third_party_invite``).The available presets do the following + /// with respect to room state:======================== ============== + /// ====================== ================ ========= Preset + /// ``join_rules`` ``history_visibility`` ``guest_access`` + /// Other======================== ============== ====================== + /// ================ =========``private_chat`` ``invite`` + /// ``shared`` ``can_join````trusted_private_chat`` ``invite`` + /// ``shared`` ``can_join`` All invitees are given the + /// same power level as the room creator.``public_chat`` ``public`` + /// ``shared`` ``forbidden``======================== + /// ============== ====================== ================ =========The + /// server will create a ``m.room.create`` event in the room with + /// therequesting user as the creator, alongside other keys provided in + /// the``creation_content``. + struct StateEvent + { + /// The type of event to send. + QString type; + /// The state_key of the state event. Defaults to an empty string. + QString stateKey; + /// The content of the event. + QJsonObject content; + }; - /// Create a new room with various configuration options. - /// - /// The server MUST apply the normal state resolution rules when - /// creating the new room, including checking power levels for each - /// event. It MUST apply the events implied by the request in the - /// following order: - /// - /// 0. A default ``m.room.power_levels`` event, giving the room creator - /// (and not other members) permission to send state events. - /// Overridden by the ``power_level_content_override`` parameter. - /// - /// 1. Events set by the ``preset``. Currently these are the - /// ``m.room.join_rules``, - /// ``m.room.history_visibility``, and ``m.room.guest_access`` state - /// events. - /// - /// 2. Events listed in ``initial_state``, in the order that they are - /// listed. - /// - /// 3. Events implied by ``name`` and ``topic`` (``m.room.name`` and - /// ``m.room.topic`` - /// state events). - /// - /// 4. Invite events implied by ``invite`` and ``invite_3pid`` - /// (``m.room.member`` with - /// ``membership: invite`` and ``m.room.third_party_invite``). - /// - /// The available presets do the following with respect to room state: - /// - /// ======================== ============== ====================== - /// ================ ========= - /// Preset ``join_rules`` ``history_visibility`` - /// ``guest_access`` Other - /// ======================== ============== ====================== - /// ================ ========= - /// ``private_chat`` ``invite`` ``shared`` ``can_join`` - /// ``trusted_private_chat`` ``invite`` ``shared`` ``can_join`` - /// All invitees are given the same power level as the room creator. - /// ``public_chat`` ``public`` ``shared`` ``forbidden`` - /// ======================== ============== ====================== - /// ================ ========= - /// - /// The server will create a ``m.room.create`` event in the room with - /// the requesting user as the creator, alongside other keys provided in - /// the - /// ``creation_content``. - struct StateEvent { - /// The type of event to send. - QString type; - /// The state_key of the state event. Defaults to an empty string. - QString stateKey; - /// The content of the event. - QJsonObject content; - }; + // Construction/destruction - // Construction/destruction + /*! Create a new room + * \param visibility + * A ``public`` visibility indicates that the room will be shown + * in the published room list. A ``private`` visibility will hide + * the room from the published room list. Rooms default to + * ``private`` visibility if this key is not included. NB: This + * should not be confused with ``join_rules`` which also uses the + * word ``public``. + * \param roomAliasName + * The desired room alias **local part**. If this is included, a + * room alias will be created and mapped to the newly created + * room. The alias will belong on the *same* homeserver which + * created the room. For example, if this was set to "foo" and + * sent to the homeserver "example.com" the complete room alias + * would be ``#foo:example.com``. + * + * The complete room alias will become the canonical alias for + * the room. + * \param name + * If this is included, an ``m.room.name`` event will be sent + * into the room to indicate the name of the room. See Room + * Events for more information on ``m.room.name``. + * \param topic + * If this is included, an ``m.room.topic`` event will be sent + * into the room to indicate the topic for the room. See Room + * Events for more information on ``m.room.topic``. + * \param invite + * A list of user IDs to invite to the room. This will tell the + * server to invite everyone in the list to the newly created room. + * \param invite3pid + * A list of objects representing third party IDs to invite into + * the room. + * \param roomVersion + * The room version to set for the room. If not provided, the homeserver + * is to use its configured default. If provided, the homeserver will return + * a 400 error with the errcode ``M_UNSUPPORTED_ROOM_VERSION`` if it does + * not support the room version. \param creationContent Extra keys, such as + * ``m.federate``, to be added to the content of the `m.room.create`_ event. + * The server will clobber the following keys: ``creator``, + * ``room_version``. Future versions of the specification may allow the + * server to clobber other keys. \param initialState A list of state events + * to set in the new room. This allows the user to override the default + * state events set in the new room. The expected format of the state events + * are an object with type, state_key and content keys set. + * + * Takes precedence over events set by ``preset``, but gets + * overriden by ``name`` and ``topic`` keys. + * \param preset + * Convenience parameter for setting various default state events + * based on a preset. + * + * If unspecified, the server should use the ``visibility`` to determine + * which preset to use. A visbility of ``public`` equates to a preset of + * ``public_chat`` and ``private`` visibility equates to a preset of + * ``private_chat``. + * \param isDirect + * This flag makes the server set the ``is_direct`` flag on the + * ``m.room.member`` events sent to the users in ``invite`` and + * ``invite_3pid``. See `Direct Messaging`_ for more information. + * \param powerLevelContentOverride + * The power level content to override in the default power level + * event. This object is applied on top of the generated + * `m.room.power_levels`_ event content prior to it being sent to the room. + * Defaults to overriding nothing. + */ + explicit CreateRoomJob(const QString& visibility = {}, + const QString& roomAliasName = {}, + const QString& name = {}, const QString& topic = {}, + const QStringList& invite = {}, + const QVector<Invite3pid>& invite3pid = {}, + const QString& roomVersion = {}, + const QJsonObject& creationContent = {}, + const QVector<StateEvent>& initialState = {}, + const QString& preset = {}, + Omittable<bool> isDirect = none, + const QJsonObject& powerLevelContentOverride = {}); - /*! Create a new room - * \param visibility - * A ``public`` visibility indicates that the room will be shown - * in the published room list. A ``private`` visibility will hide - * the room from the published room list. Rooms default to - * ``private`` visibility if this key is not included. NB: This - * should not be confused with ``join_rules`` which also uses the - * word ``public``. - * \param roomAliasName - * The desired room alias **local part**. If this is included, a - * room alias will be created and mapped to the newly created - * room. The alias will belong on the *same* homeserver which - * created the room. For example, if this was set to "foo" and - * sent to the homeserver "example.com" the complete room alias - * would be ``#foo:example.com``. - * - * The complete room alias will become the canonical alias for - * the room. - * \param name - * If this is included, an ``m.room.name`` event will be sent - * into the room to indicate the name of the room. See Room - * Events for more information on ``m.room.name``. - * \param topic - * If this is included, an ``m.room.topic`` event will be sent - * into the room to indicate the topic for the room. See Room - * Events for more information on ``m.room.topic``. - * \param invite - * A list of user IDs to invite to the room. This will tell the - * server to invite everyone in the list to the newly created room. - * \param invite3pid - * A list of objects representing third party IDs to invite into - * the room. - * \param roomVersion - * The room version to set for the room. If not provided, the - * homeserver is to use its configured default. If provided, the - * homeserver will return a 400 error with the errcode - * ``M_UNSUPPORTED_ROOM_VERSION`` if it does not support the room - * version. \param creationContent Extra keys, such as ``m.federate``, - * to be added to the content of the `m.room.create`_ event. The server - * will clobber the following keys: ``creator``, ``room_version``. - * Future versions of the specification may allow the server to clobber - * other keys. \param initialState A list of state events to set in the - * new room. This allows the user to override the default state events - * set in the new room. The expected format of the state events are an - * object with type, state_key and content keys set. - * - * Takes precedence over events set by ``preset``, but gets - * overriden by ``name`` and ``topic`` keys. - * \param preset - * Convenience parameter for setting various default state events - * based on a preset. - * - * If unspecified, the server should use the ``visibility`` to - * determine which preset to use. A visbility of ``public`` equates to a - * preset of - * ``public_chat`` and ``private`` visibility equates to a preset of - * ``private_chat``. - * \param isDirect - * This flag makes the server set the ``is_direct`` flag on the - * ``m.room.member`` events sent to the users in ``invite`` and - * ``invite_3pid``. See `Direct Messaging`_ for more information. - * \param powerLevelContentOverride - * The power level content to override in the default power level - * event. This object is applied on top of the generated - * `m.room.power_levels`_ event content prior to it being sent to the - * room. Defaults to overriding nothing. - */ - explicit CreateRoomJob( - const QString& visibility = {}, - const QString& roomAliasName = {}, const QString& name = {}, - const QString& topic = {}, const QStringList& invite = {}, - const QVector<Invite3pid>& invite3pid = {}, - const QString& roomVersion = {}, - const QJsonObject& creationContent = {}, - const QVector<StateEvent>& initialState = {}, - const QString& preset = {}, Omittable<bool> isDirect = none, - const QJsonObject& powerLevelContentOverride = {}); - ~CreateRoomJob() override; + ~CreateRoomJob() override; - // Result properties + // Result properties - /// The created room's ID. - const QString& roomId() const; + /// The created room's ID. + const QString& roomId() const; - protected: - Status parseJson(const QJsonDocument& data) override; +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/definitions/auth_data.cpp b/lib/csapi/definitions/auth_data.cpp index f40a3a90..b0303a19 100644 --- a/lib/csapi/definitions/auth_data.cpp +++ b/lib/csapi/definitions/auth_data.cpp @@ -4,21 +4,25 @@ #include "auth_data.h" + using namespace QMatrixClient; -void JsonObjectConverter<AuthenticationData>::dumpTo( - QJsonObject& jo, const AuthenticationData& pod) + +void JsonObjectConverter<AuthenticationData>::dumpTo(QJsonObject& jo, const AuthenticationData& pod) { fillJson(jo, pod.authInfo); addParam<>(jo, QStringLiteral("type"), pod.type); addParam<IfNotEmpty>(jo, QStringLiteral("session"), pod.session); -} -void JsonObjectConverter<AuthenticationData>::fillFrom( - QJsonObject jo, AuthenticationData& result) +} + +void JsonObjectConverter<AuthenticationData>::fillFrom(QJsonObject jo, AuthenticationData& result) { fromJson(jo.take("type"_ls), result.type); fromJson(jo.take("session"_ls), result.session); - fromJson(jo, result.authInfo); + } + + + diff --git a/lib/csapi/definitions/auth_data.h b/lib/csapi/definitions/auth_data.h index e25dff7a..689caf49 100644 --- a/lib/csapi/definitions/auth_data.h +++ b/lib/csapi/definitions/auth_data.h @@ -4,27 +4,37 @@ #pragma once + + #include "converters.h" -#include <QtCore/QHash> #include <QtCore/QJsonObject> +#include <QtCore/QHash> + +namespace QMatrixClient +{ + +// Data structures + +/// Used by clients to submit authentication information to the interactive-authentication API +struct AuthenticationData +{ + /// The login type that the client is attempting to complete. + QString type; + /// The value of the session key given by the homeserver. + QString session; + + + /// Keys dependent on the login type + QHash<QString, QJsonObject> authInfo; + +}; + +template <> struct JsonObjectConverter<AuthenticationData> +{ + static void dumpTo(QJsonObject& jo, const AuthenticationData& pod); + static void fillFrom(QJsonObject jo, AuthenticationData& pod);}; + -namespace QMatrixClient { - // Data structures - - /// Used by clients to submit authentication information to the - /// interactive-authentication API - struct AuthenticationData { - /// The login type that the client is attempting to complete. - QString type; - /// The value of the session key given by the homeserver. - QString session; - /// Keys dependent on the login type - QHash<QString, QJsonObject> authInfo; - }; - template <> struct JsonObjectConverter<AuthenticationData> { - static void dumpTo(QJsonObject& jo, const AuthenticationData& pod); - static void fillFrom(QJsonObject jo, AuthenticationData& pod); - }; } // namespace QMatrixClient diff --git a/lib/csapi/definitions/client_device.cpp b/lib/csapi/definitions/client_device.cpp index 2ca58e2f..5710537d 100644 --- a/lib/csapi/definitions/client_device.cpp +++ b/lib/csapi/definitions/client_device.cpp @@ -4,21 +4,27 @@ #include "client_device.h" + using namespace QMatrixClient; + void JsonObjectConverter<Device>::dumpTo(QJsonObject& jo, const Device& pod) { addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); addParam<IfNotEmpty>(jo, QStringLiteral("display_name"), pod.displayName); addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ip"), pod.lastSeenIp); addParam<IfNotEmpty>(jo, QStringLiteral("last_seen_ts"), pod.lastSeenTs); -} -void JsonObjectConverter<Device>::fillFrom(const QJsonObject& jo, - Device& result) +} + +void JsonObjectConverter<Device>::fillFrom(const QJsonObject& jo, Device& result) { fromJson(jo.value("device_id"_ls), result.deviceId); fromJson(jo.value("display_name"_ls), result.displayName); fromJson(jo.value("last_seen_ip"_ls), result.lastSeenIp); fromJson(jo.value("last_seen_ts"_ls), result.lastSeenTs); + } + + + diff --git a/lib/csapi/definitions/client_device.h b/lib/csapi/definitions/client_device.h index b473a037..7c63a9b6 100644 --- a/lib/csapi/definitions/client_device.h +++ b/lib/csapi/definitions/client_device.h @@ -4,31 +4,37 @@ #pragma once + + #include "converters.h" #include "converters.h" -namespace QMatrixClient { - // Data structures - - /// A client device - struct Device { - /// Identifier of this device. - QString deviceId; - /// Display name set by the user for this device. Absent if no name has - /// been set. - QString displayName; - /// The IP address where this device was last seen. (May be a few - /// minutes out of date, for efficiency reasons). - QString lastSeenIp; - /// The timestamp (in milliseconds since the unix epoch) when this - /// devices was last seen. (May be a few minutes out of date, for - /// efficiency reasons). - Omittable<qint64> lastSeenTs; - }; - template <> struct JsonObjectConverter<Device> { - static void dumpTo(QJsonObject& jo, const Device& pod); - static void fillFrom(const QJsonObject& jo, Device& pod); - }; +namespace QMatrixClient +{ + +// Data structures + +/// A client device +struct Device +{ + /// Identifier of this device. + QString deviceId; + /// Display name set by the user for this device. Absent if no name has beenset. + QString displayName; + /// The IP address where this device was last seen. (May be a few minutes outof date, for efficiency reasons). + QString lastSeenIp; + /// The timestamp (in milliseconds since the unix epoch) when this deviceswas last seen. (May be a few minutes out of date, for efficiencyreasons). + Omittable<qint64> lastSeenTs; + + +}; + +template <> struct JsonObjectConverter<Device> +{ + static void dumpTo(QJsonObject& jo, const Device& pod); + static void fillFrom(const QJsonObject& jo, Device& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/device_keys.cpp b/lib/csapi/definitions/device_keys.cpp index cc5262b7..ffe0cfbe 100644 --- a/lib/csapi/definitions/device_keys.cpp +++ b/lib/csapi/definitions/device_keys.cpp @@ -4,24 +4,29 @@ #include "device_keys.h" + using namespace QMatrixClient; -void JsonObjectConverter<DeviceKeys>::dumpTo(QJsonObject& jo, - const DeviceKeys& pod) + +void JsonObjectConverter<DeviceKeys>::dumpTo(QJsonObject& jo, const DeviceKeys& pod) { addParam<>(jo, QStringLiteral("user_id"), pod.userId); addParam<>(jo, QStringLiteral("device_id"), pod.deviceId); addParam<>(jo, QStringLiteral("algorithms"), pod.algorithms); addParam<>(jo, QStringLiteral("keys"), pod.keys); addParam<>(jo, QStringLiteral("signatures"), pod.signatures); -} -void JsonObjectConverter<DeviceKeys>::fillFrom(const QJsonObject& jo, - DeviceKeys& result) +} + +void JsonObjectConverter<DeviceKeys>::fillFrom(const QJsonObject& jo, DeviceKeys& result) { fromJson(jo.value("user_id"_ls), result.userId); fromJson(jo.value("device_id"_ls), result.deviceId); fromJson(jo.value("algorithms"_ls), result.algorithms); fromJson(jo.value("keys"_ls), result.keys); fromJson(jo.value("signatures"_ls), result.signatures); + } + + + diff --git a/lib/csapi/definitions/device_keys.h b/lib/csapi/definitions/device_keys.h index 6c417ce7..c86db46a 100644 --- a/lib/csapi/definitions/device_keys.h +++ b/lib/csapi/definitions/device_keys.h @@ -4,38 +4,39 @@ #pragma once + + #include "converters.h" #include <QtCore/QHash> -namespace QMatrixClient { - // Data structures - - /// Device identity keys - struct DeviceKeys { - /// The ID of the user the device belongs to. Must match the user ID - /// used when logging in. - QString userId; - /// The ID of the device these keys belong to. Must match the device ID - /// used when logging in. - QString deviceId; - /// The encryption algorithms supported by this device. - QStringList algorithms; - /// Public identity keys. The names of the properties should be in the - /// format ``<algorithm>:<device_id>``. The keys themselves should be - /// encoded as specified by the key algorithm. - QHash<QString, QString> keys; - /// Signatures for the device key object. A map from user ID, to a map - /// from - /// ``<algorithm>:<device_id>`` to the signature. - /// - /// The signature is calculated using the process described at `Signing - /// JSON`_. - QHash<QString, QHash<QString, QString>> signatures; - }; - template <> struct JsonObjectConverter<DeviceKeys> { - static void dumpTo(QJsonObject& jo, const DeviceKeys& pod); - static void fillFrom(const QJsonObject& jo, DeviceKeys& pod); - }; +namespace QMatrixClient +{ + +// Data structures + +/// Device identity keys +struct DeviceKeys +{ + /// The ID of the user the device belongs to. Must match the user ID usedwhen logging in. + QString userId; + /// The ID of the device these keys belong to. Must match the device ID usedwhen logging in. + QString deviceId; + /// The encryption algorithms supported by this device. + QStringList algorithms; + /// Public identity keys. The names of the properties should be in theformat ``<algorithm>:<device_id>``. The keys themselves should beencoded as specified by the key algorithm. + QHash<QString, QString> keys; + /// Signatures for the device key object. A map from user ID, to a map from``<algorithm>:<device_id>`` to the signature.The signature is calculated using the process described at `SigningJSON`_. + QHash<QString, QHash<QString, QString>> signatures; + + +}; + +template <> struct JsonObjectConverter<DeviceKeys> +{ + static void dumpTo(QJsonObject& jo, const DeviceKeys& pod); + static void fillFrom(const QJsonObject& jo, DeviceKeys& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/event_filter.cpp b/lib/csapi/definitions/event_filter.cpp index 9b2c7a33..8be98c94 100644 --- a/lib/csapi/definitions/event_filter.cpp +++ b/lib/csapi/definitions/event_filter.cpp @@ -4,24 +4,29 @@ #include "event_filter.h" + using namespace QMatrixClient; -void JsonObjectConverter<EventFilter>::dumpTo(QJsonObject& jo, - const EventFilter& pod) + +void JsonObjectConverter<EventFilter>::dumpTo(QJsonObject& jo, const EventFilter& pod) { addParam<IfNotEmpty>(jo, QStringLiteral("limit"), pod.limit); addParam<IfNotEmpty>(jo, QStringLiteral("not_senders"), pod.notSenders); addParam<IfNotEmpty>(jo, QStringLiteral("not_types"), pod.notTypes); addParam<IfNotEmpty>(jo, QStringLiteral("senders"), pod.senders); addParam<IfNotEmpty>(jo, QStringLiteral("types"), pod.types); -} -void JsonObjectConverter<EventFilter>::fillFrom(const QJsonObject& jo, - EventFilter& result) +} + +void JsonObjectConverter<EventFilter>::fillFrom(const QJsonObject& jo, EventFilter& result) { fromJson(jo.value("limit"_ls), result.limit); fromJson(jo.value("not_senders"_ls), result.notSenders); fromJson(jo.value("not_types"_ls), result.notTypes); fromJson(jo.value("senders"_ls), result.senders); fromJson(jo.value("types"_ls), result.types); + } + + + diff --git a/lib/csapi/definitions/event_filter.h b/lib/csapi/definitions/event_filter.h index 5a1a831b..b8b2a983 100644 --- a/lib/csapi/definitions/event_filter.h +++ b/lib/csapi/definitions/event_filter.h @@ -4,36 +4,39 @@ #pragma once + + #include "converters.h" #include "converters.h" -namespace QMatrixClient { - // Data structures - - struct EventFilter { - /// The maximum number of events to return. - Omittable<int> limit; - /// A list of sender IDs to exclude. If this list is absent then no - /// senders are excluded. A matching sender will be excluded even if it - /// is listed in the ``'senders'`` filter. - QStringList notSenders; - /// A list of event types to exclude. If this list is absent then no - /// event types are excluded. A matching type will be excluded even if - /// it is listed in the ``'types'`` filter. A '*' can be used as a - /// wildcard to match any sequence of characters. - QStringList notTypes; - /// A list of senders IDs to include. If this list is absent then all - /// senders are included. - QStringList senders; - /// A list of event types to include. If this list is absent then all - /// event types are included. A ``'*'`` can be used as a wildcard to - /// match any sequence of characters. - QStringList types; - }; - template <> struct JsonObjectConverter<EventFilter> { - static void dumpTo(QJsonObject& jo, const EventFilter& pod); - static void fillFrom(const QJsonObject& jo, EventFilter& pod); - }; +namespace QMatrixClient +{ + +// Data structures + + +struct EventFilter +{ + /// The maximum number of events to return. + Omittable<int> limit; + /// A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will be excluded even if it is listed in the ``'senders'`` filter. + QStringList notSenders; + /// A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the ``'types'`` filter. A '*' can be used as a wildcard to match any sequence of characters. + QStringList notTypes; + /// A list of senders IDs to include. If this list is absent then all senders are included. + QStringList senders; + /// A list of event types to include. If this list is absent then all event types are included. A ``'*'`` can be used as a wildcard to match any sequence of characters. + QStringList types; + + +}; + +template <> struct JsonObjectConverter<EventFilter> +{ + static void dumpTo(QJsonObject& jo, const EventFilter& pod); + static void fillFrom(const QJsonObject& jo, EventFilter& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/public_rooms_response.cpp b/lib/csapi/definitions/public_rooms_response.cpp index 199a7a93..2c03b7d3 100644 --- a/lib/csapi/definitions/public_rooms_response.cpp +++ b/lib/csapi/definitions/public_rooms_response.cpp @@ -4,14 +4,14 @@ #include "public_rooms_response.h" + using namespace QMatrixClient; -void JsonObjectConverter<PublicRoomsChunk>::dumpTo(QJsonObject& jo, - const PublicRoomsChunk& pod) + +void JsonObjectConverter<PublicRoomsChunk>::dumpTo(QJsonObject& jo, const PublicRoomsChunk& pod) { addParam<IfNotEmpty>(jo, QStringLiteral("aliases"), pod.aliases); - addParam<IfNotEmpty>(jo, QStringLiteral("canonical_alias"), - pod.canonicalAlias); + addParam<IfNotEmpty>(jo, QStringLiteral("canonical_alias"), pod.canonicalAlias); addParam<IfNotEmpty>(jo, QStringLiteral("name"), pod.name); addParam<>(jo, QStringLiteral("num_joined_members"), pod.numJoinedMembers); addParam<>(jo, QStringLiteral("room_id"), pod.roomId); @@ -19,10 +19,10 @@ void JsonObjectConverter<PublicRoomsChunk>::dumpTo(QJsonObject& jo, addParam<>(jo, QStringLiteral("world_readable"), pod.worldReadable); addParam<>(jo, QStringLiteral("guest_can_join"), pod.guestCanJoin); addParam<IfNotEmpty>(jo, QStringLiteral("avatar_url"), pod.avatarUrl); -} -void JsonObjectConverter<PublicRoomsChunk>::fillFrom(const QJsonObject& jo, - PublicRoomsChunk& result) +} + +void JsonObjectConverter<PublicRoomsChunk>::fillFrom(const QJsonObject& jo, PublicRoomsChunk& result) { fromJson(jo.value("aliases"_ls), result.aliases); fromJson(jo.value("canonical_alias"_ls), result.canonicalAlias); @@ -33,24 +33,28 @@ void JsonObjectConverter<PublicRoomsChunk>::fillFrom(const QJsonObject& jo, fromJson(jo.value("world_readable"_ls), result.worldReadable); fromJson(jo.value("guest_can_join"_ls), result.guestCanJoin); fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } + -void JsonObjectConverter<PublicRoomsResponse>::dumpTo( - QJsonObject& jo, const PublicRoomsResponse& pod) + +void JsonObjectConverter<PublicRoomsResponse>::dumpTo(QJsonObject& jo, const PublicRoomsResponse& pod) { addParam<>(jo, QStringLiteral("chunk"), pod.chunk); addParam<IfNotEmpty>(jo, QStringLiteral("next_batch"), pod.nextBatch); addParam<IfNotEmpty>(jo, QStringLiteral("prev_batch"), pod.prevBatch); - addParam<IfNotEmpty>(jo, QStringLiteral("total_room_count_estimate"), - pod.totalRoomCountEstimate); -} + addParam<IfNotEmpty>(jo, QStringLiteral("total_room_count_estimate"), pod.totalRoomCountEstimate); -void JsonObjectConverter<PublicRoomsResponse>::fillFrom( - const QJsonObject& jo, PublicRoomsResponse& result) +} + +void JsonObjectConverter<PublicRoomsResponse>::fillFrom(const QJsonObject& jo, PublicRoomsResponse& result) { fromJson(jo.value("chunk"_ls), result.chunk); fromJson(jo.value("next_batch"_ls), result.nextBatch); fromJson(jo.value("prev_batch"_ls), result.prevBatch); - fromJson(jo.value("total_room_count_estimate"_ls), - result.totalRoomCountEstimate); + fromJson(jo.value("total_room_count_estimate"_ls), result.totalRoomCountEstimate); + } + + + diff --git a/lib/csapi/definitions/public_rooms_response.h b/lib/csapi/definitions/public_rooms_response.h index 6d8caf98..d282a592 100644 --- a/lib/csapi/definitions/public_rooms_response.h +++ b/lib/csapi/definitions/public_rooms_response.h @@ -4,60 +4,68 @@ #pragma once + + #include "converters.h" #include "converters.h" #include <QtCore/QVector> -namespace QMatrixClient { - // Data structures - - struct PublicRoomsChunk { - /// Aliases of the room. May be empty. - QStringList aliases; - /// The canonical alias of the room, if any. - QString canonicalAlias; - /// The name of the room, if any. - QString name; - /// The number of members joined to the room. - int numJoinedMembers; - /// The ID of the room. - QString roomId; - /// The topic of the room, if any. - QString topic; - /// Whether the room may be viewed by guest users without joining. - bool worldReadable; - /// Whether guest users may join the room and participate in it. - /// If they can, they will be subject to ordinary power level - /// rules like any other user. - bool guestCanJoin; - /// The URL for the room's avatar, if one is set. - QString avatarUrl; - }; - template <> struct JsonObjectConverter<PublicRoomsChunk> { - static void dumpTo(QJsonObject& jo, const PublicRoomsChunk& pod); - static void fillFrom(const QJsonObject& jo, PublicRoomsChunk& pod); - }; - - /// A list of the rooms on the server. - struct PublicRoomsResponse { - /// A paginated chunk of public rooms. - QVector<PublicRoomsChunk> chunk; - /// A pagination token for the response. The absence of this token - /// means there are no more results to fetch and the client should - /// stop paginating. - QString nextBatch; - /// A pagination token that allows fetching previous results. The - /// absence of this token means there are no results before this - /// batch, i.e. this is the first batch. - QString prevBatch; - /// An estimate on the total number of public rooms, if the - /// server has an estimate. - Omittable<int> totalRoomCountEstimate; - }; - template <> struct JsonObjectConverter<PublicRoomsResponse> { - static void dumpTo(QJsonObject& jo, const PublicRoomsResponse& pod); - static void fillFrom(const QJsonObject& jo, PublicRoomsResponse& pod); - }; +namespace QMatrixClient +{ + +// Data structures + + +struct PublicRoomsChunk +{ + /// Aliases of the room. May be empty. + QStringList aliases; + /// The canonical alias of the room, if any. + QString canonicalAlias; + /// The name of the room, if any. + QString name; + /// The number of members joined to the room. + int numJoinedMembers; + /// The ID of the room. + QString roomId; + /// The topic of the room, if any. + QString topic; + /// Whether the room may be viewed by guest users without joining. + bool worldReadable; + /// Whether guest users may join the room and participate in it.If they can, they will be subject to ordinary power levelrules like any other user. + bool guestCanJoin; + /// The URL for the room's avatar, if one is set. + QString avatarUrl; + + +}; + +template <> struct JsonObjectConverter<PublicRoomsChunk> +{ + static void dumpTo(QJsonObject& jo, const PublicRoomsChunk& pod); + static void fillFrom(const QJsonObject& jo, PublicRoomsChunk& pod);}; + +/// A list of the rooms on the server. +struct PublicRoomsResponse +{ + /// A paginated chunk of public rooms. + QVector<PublicRoomsChunk> chunk; + /// A pagination token for the response. The absence of this tokenmeans there are no more results to fetch and the client shouldstop paginating. + QString nextBatch; + /// A pagination token that allows fetching previous results. Theabsence of this token means there are no results before thisbatch, i.e. this is the first batch. + QString prevBatch; + /// An estimate on the total number of public rooms, if theserver has an estimate. + Omittable<int> totalRoomCountEstimate; + + +}; + +template <> struct JsonObjectConverter<PublicRoomsResponse> +{ + static void dumpTo(QJsonObject& jo, const PublicRoomsResponse& pod); + static void fillFrom(const QJsonObject& jo, PublicRoomsResponse& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/push_condition.cpp b/lib/csapi/definitions/push_condition.cpp index 5bcb845e..86b3107e 100644 --- a/lib/csapi/definitions/push_condition.cpp +++ b/lib/csapi/definitions/push_condition.cpp @@ -4,22 +4,27 @@ #include "push_condition.h" + using namespace QMatrixClient; -void JsonObjectConverter<PushCondition>::dumpTo(QJsonObject& jo, - const PushCondition& pod) + +void JsonObjectConverter<PushCondition>::dumpTo(QJsonObject& jo, const PushCondition& pod) { addParam<>(jo, QStringLiteral("kind"), pod.kind); addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key); addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); addParam<IfNotEmpty>(jo, QStringLiteral("is"), pod.is); -} -void JsonObjectConverter<PushCondition>::fillFrom(const QJsonObject& jo, - PushCondition& result) +} + +void JsonObjectConverter<PushCondition>::fillFrom(const QJsonObject& jo, PushCondition& result) { fromJson(jo.value("kind"_ls), result.kind); fromJson(jo.value("key"_ls), result.key); fromJson(jo.value("pattern"_ls), result.pattern); fromJson(jo.value("is"_ls), result.is); + } + + + diff --git a/lib/csapi/definitions/push_condition.h b/lib/csapi/definitions/push_condition.h index a4e44e93..e61fb24e 100644 --- a/lib/csapi/definitions/push_condition.h +++ b/lib/csapi/definitions/push_condition.h @@ -4,31 +4,36 @@ #pragma once + + #include "converters.h" -namespace QMatrixClient { - // Data structures - - struct PushCondition { - QString kind; - /// Required for ``event_match`` conditions. The dot-separated field of - /// the event to match. - QString key; - /// Required for ``event_match`` conditions. The glob-style pattern to - /// match against. Patterns with no special glob characters should be - /// treated as having asterisks prepended and appended when testing the - /// condition. - QString pattern; - /// Required for ``room_member_count`` conditions. A decimal integer - /// optionally prefixed by one of, ==, <, >, >= or <=. A prefix of < - /// matches rooms where the member count is strictly less than the given - /// number and so forth. If no prefix is present, this parameter - /// defaults to ==. - QString is; - }; - template <> struct JsonObjectConverter<PushCondition> { - static void dumpTo(QJsonObject& jo, const PushCondition& pod); - static void fillFrom(const QJsonObject& jo, PushCondition& pod); - }; + +namespace QMatrixClient +{ + +// Data structures + + +struct PushCondition +{ + + QString kind; + /// Required for ``event_match`` conditions. The dot-separated field of theevent to match. + QString key; + /// Required for ``event_match`` conditions. The glob-style pattern tomatch against. Patterns with no special glob characters should betreated as having asterisks prepended and appended when testing thecondition. + QString pattern; + /// Required for ``room_member_count`` conditions. A decimal integeroptionally prefixed by one of, ==, <, >, >= or <=. A prefix of < matchesrooms where the member count is strictly less than the given number andso forth. If no prefix is present, this parameter defaults to ==. + QString is; + + +}; + +template <> struct JsonObjectConverter<PushCondition> +{ + static void dumpTo(QJsonObject& jo, const PushCondition& pod); + static void fillFrom(const QJsonObject& jo, PushCondition& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/push_rule.cpp b/lib/csapi/definitions/push_rule.cpp index fc2be2c7..bfa8a7ef 100644 --- a/lib/csapi/definitions/push_rule.cpp +++ b/lib/csapi/definitions/push_rule.cpp @@ -4,8 +4,10 @@ #include "push_rule.h" + using namespace QMatrixClient; + void JsonObjectConverter<PushRule>::dumpTo(QJsonObject& jo, const PushRule& pod) { addParam<>(jo, QStringLiteral("actions"), pod.actions); @@ -14,10 +16,10 @@ void JsonObjectConverter<PushRule>::dumpTo(QJsonObject& jo, const PushRule& pod) addParam<>(jo, QStringLiteral("rule_id"), pod.ruleId); addParam<IfNotEmpty>(jo, QStringLiteral("conditions"), pod.conditions); addParam<IfNotEmpty>(jo, QStringLiteral("pattern"), pod.pattern); -} -void JsonObjectConverter<PushRule>::fillFrom(const QJsonObject& jo, - PushRule& result) +} + +void JsonObjectConverter<PushRule>::fillFrom(const QJsonObject& jo, PushRule& result) { fromJson(jo.value("actions"_ls), result.actions); fromJson(jo.value("default"_ls), result.isDefault); @@ -25,4 +27,8 @@ void JsonObjectConverter<PushRule>::fillFrom(const QJsonObject& jo, fromJson(jo.value("rule_id"_ls), result.ruleId); fromJson(jo.value("conditions"_ls), result.conditions); fromJson(jo.value("pattern"_ls), result.pattern); + } + + + diff --git a/lib/csapi/definitions/push_rule.h b/lib/csapi/definitions/push_rule.h index d8d2cc0f..98bd904d 100644 --- a/lib/csapi/definitions/push_rule.h +++ b/lib/csapi/definitions/push_rule.h @@ -4,37 +4,45 @@ #pragma once + + #include "converters.h" #include "converters.h" +#include <QtCore/QVariant> #include "csapi/definitions/push_condition.h" #include <QtCore/QJsonObject> -#include <QtCore/QVariant> #include <QtCore/QVector> -namespace QMatrixClient { - // Data structures - - struct PushRule { - /// The actions to perform when this rule is matched. - QVector<QVariant> actions; - /// Whether this is a default rule, or has been set explicitly. - bool isDefault; - /// Whether the push rule is enabled or not. - bool enabled; - /// The ID of this rule. - QString ruleId; - /// The conditions that must hold true for an event in order for a rule - /// to be applied to an event. A rule with no conditions always matches. - /// Only applicable to ``underride`` and ``override`` rules. - QVector<PushCondition> conditions; - /// The glob-style pattern to match against. Only applicable to - /// ``content`` rules. - QString pattern; - }; - template <> struct JsonObjectConverter<PushRule> { - static void dumpTo(QJsonObject& jo, const PushRule& pod); - static void fillFrom(const QJsonObject& jo, PushRule& pod); - }; +namespace QMatrixClient +{ + +// Data structures + + +struct PushRule +{ + /// The actions to perform when this rule is matched. + QVector<QVariant> actions; + /// Whether this is a default rule, or has been set explicitly. + bool isDefault; + /// Whether the push rule is enabled or not. + bool enabled; + /// The ID of this rule. + QString ruleId; + /// The conditions that must hold true for an event in order for a rule to beapplied to an event. A rule with no conditions always matches. Onlyapplicable to ``underride`` and ``override`` rules. + QVector<PushCondition> conditions; + /// The glob-style pattern to match against. Only applicable to ``content``rules. + QString pattern; + + +}; + +template <> struct JsonObjectConverter<PushRule> +{ + static void dumpTo(QJsonObject& jo, const PushRule& pod); + static void fillFrom(const QJsonObject& jo, PushRule& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/push_ruleset.cpp b/lib/csapi/definitions/push_ruleset.cpp index 6f48d27b..4754e07b 100644 --- a/lib/csapi/definitions/push_ruleset.cpp +++ b/lib/csapi/definitions/push_ruleset.cpp @@ -4,24 +4,29 @@ #include "push_ruleset.h" + using namespace QMatrixClient; -void JsonObjectConverter<PushRuleset>::dumpTo(QJsonObject& jo, - const PushRuleset& pod) + +void JsonObjectConverter<PushRuleset>::dumpTo(QJsonObject& jo, const PushRuleset& pod) { addParam<IfNotEmpty>(jo, QStringLiteral("content"), pod.content); addParam<IfNotEmpty>(jo, QStringLiteral("override"), pod.override); addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); addParam<IfNotEmpty>(jo, QStringLiteral("sender"), pod.sender); addParam<IfNotEmpty>(jo, QStringLiteral("underride"), pod.underride); -} -void JsonObjectConverter<PushRuleset>::fillFrom(const QJsonObject& jo, - PushRuleset& result) +} + +void JsonObjectConverter<PushRuleset>::fillFrom(const QJsonObject& jo, PushRuleset& result) { fromJson(jo.value("content"_ls), result.content); fromJson(jo.value("override"_ls), result.override); fromJson(jo.value("room"_ls), result.room); fromJson(jo.value("sender"_ls), result.sender); fromJson(jo.value("underride"_ls), result.underride); + } + + + diff --git a/lib/csapi/definitions/push_ruleset.h b/lib/csapi/definitions/push_ruleset.h index b2f791c4..e1a2c142 100644 --- a/lib/csapi/definitions/push_ruleset.h +++ b/lib/csapi/definitions/push_ruleset.h @@ -4,25 +4,41 @@ #pragma once -#include "converters.h" + #include "converters.h" + #include "csapi/definitions/push_rule.h" +#include "converters.h" #include <QtCore/QVector> -namespace QMatrixClient { - // Data structures - - struct PushRuleset { - QVector<PushRule> content; - QVector<PushRule> override; - QVector<PushRule> room; - QVector<PushRule> sender; - QVector<PushRule> underride; - }; - template <> struct JsonObjectConverter<PushRuleset> { - static void dumpTo(QJsonObject& jo, const PushRuleset& pod); - static void fillFrom(const QJsonObject& jo, PushRuleset& pod); - }; +namespace QMatrixClient +{ + +// Data structures + + +struct PushRuleset +{ + + QVector<PushRule> content; + + QVector<PushRule> override; + + QVector<PushRule> room; + + QVector<PushRule> sender; + + QVector<PushRule> underride; + + +}; + +template <> struct JsonObjectConverter<PushRuleset> +{ + static void dumpTo(QJsonObject& jo, const PushRuleset& pod); + static void fillFrom(const QJsonObject& jo, PushRuleset& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/room_event_filter.cpp b/lib/csapi/definitions/room_event_filter.cpp index bd38ebc7..fc859395 100644 --- a/lib/csapi/definitions/room_event_filter.cpp +++ b/lib/csapi/definitions/room_event_filter.cpp @@ -4,22 +4,27 @@ #include "room_event_filter.h" + using namespace QMatrixClient; -void JsonObjectConverter<RoomEventFilter>::dumpTo(QJsonObject& jo, - const RoomEventFilter& pod) + +void JsonObjectConverter<RoomEventFilter>::dumpTo(QJsonObject& jo, const RoomEventFilter& pod) { fillJson<EventFilter>(jo, pod); addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); addParam<IfNotEmpty>(jo, QStringLiteral("contains_url"), pod.containsUrl); -} -void JsonObjectConverter<RoomEventFilter>::fillFrom(const QJsonObject& jo, - RoomEventFilter& result) +} + +void JsonObjectConverter<RoomEventFilter>::fillFrom(const QJsonObject& jo, RoomEventFilter& result) { fillFromJson<EventFilter>(jo, result); fromJson(jo.value("not_rooms"_ls), result.notRooms); fromJson(jo.value("rooms"_ls), result.rooms); fromJson(jo.value("contains_url"_ls), result.containsUrl); + } + + + diff --git a/lib/csapi/definitions/room_event_filter.h b/lib/csapi/definitions/room_event_filter.h index 13c82341..92e210fe 100644 --- a/lib/csapi/definitions/room_event_filter.h +++ b/lib/csapi/definitions/room_event_filter.h @@ -4,30 +4,36 @@ #pragma once -#include "converters.h" + #include "converters.h" + #include "csapi/definitions/event_filter.h" +#include "converters.h" + +namespace QMatrixClient +{ + +// Data structures + + +struct RoomEventFilter : EventFilter +{ + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the ``'rooms'`` filter. + QStringList notRooms; + /// A list of room IDs to include. If this list is absent then all rooms are included. + QStringList rooms; + /// If ``true``, includes only events with a ``url`` key in their content. If ``false``, excludes those events. If omitted, ``url`` key is not considered for filtering. + Omittable<bool> containsUrl; + + +}; + +template <> struct JsonObjectConverter<RoomEventFilter> +{ + static void dumpTo(QJsonObject& jo, const RoomEventFilter& pod); + static void fillFrom(const QJsonObject& jo, RoomEventFilter& pod);}; + -namespace QMatrixClient { - // Data structures - - struct RoomEventFilter : EventFilter { - /// A list of room IDs to exclude. If this list is absent then no rooms - /// are excluded. A matching room will be excluded even if it is listed - /// in the ``'rooms'`` filter. - QStringList notRooms; - /// A list of room IDs to include. If this list is absent then all rooms - /// are included. - QStringList rooms; - /// If ``true``, includes only events with a ``url`` key in their - /// content. If ``false``, excludes those events. If omitted, ``url`` - /// key is not considered for filtering. - Omittable<bool> containsUrl; - }; - template <> struct JsonObjectConverter<RoomEventFilter> { - static void dumpTo(QJsonObject& jo, const RoomEventFilter& pod); - static void fillFrom(const QJsonObject& jo, RoomEventFilter& pod); - }; } // namespace QMatrixClient diff --git a/lib/csapi/definitions/sync_filter.cpp b/lib/csapi/definitions/sync_filter.cpp index c06c16ca..a1ef53c5 100644 --- a/lib/csapi/definitions/sync_filter.cpp +++ b/lib/csapi/definitions/sync_filter.cpp @@ -4,29 +4,29 @@ #include "sync_filter.h" + using namespace QMatrixClient; -void JsonObjectConverter<StateFilter>::dumpTo(QJsonObject& jo, - const StateFilter& pod) + +void JsonObjectConverter<StateFilter>::dumpTo(QJsonObject& jo, const StateFilter& pod) { fillJson<RoomEventFilter>(jo, pod); - addParam<IfNotEmpty>(jo, QStringLiteral("lazy_load_members"), - pod.lazyLoadMembers); - addParam<IfNotEmpty>(jo, QStringLiteral("include_redundant_members"), - pod.includeRedundantMembers); -} + addParam<IfNotEmpty>(jo, QStringLiteral("lazy_load_members"), pod.lazyLoadMembers); + addParam<IfNotEmpty>(jo, QStringLiteral("include_redundant_members"), pod.includeRedundantMembers); -void JsonObjectConverter<StateFilter>::fillFrom(const QJsonObject& jo, - StateFilter& result) +} + +void JsonObjectConverter<StateFilter>::fillFrom(const QJsonObject& jo, StateFilter& result) { fillFromJson<RoomEventFilter>(jo, result); fromJson(jo.value("lazy_load_members"_ls), result.lazyLoadMembers); - fromJson(jo.value("include_redundant_members"_ls), - result.includeRedundantMembers); + fromJson(jo.value("include_redundant_members"_ls), result.includeRedundantMembers); + } + -void JsonObjectConverter<RoomFilter>::dumpTo(QJsonObject& jo, - const RoomFilter& pod) + +void JsonObjectConverter<RoomFilter>::dumpTo(QJsonObject& jo, const RoomFilter& pod) { addParam<IfNotEmpty>(jo, QStringLiteral("not_rooms"), pod.notRooms); addParam<IfNotEmpty>(jo, QStringLiteral("rooms"), pod.rooms); @@ -35,10 +35,10 @@ void JsonObjectConverter<RoomFilter>::dumpTo(QJsonObject& jo, addParam<IfNotEmpty>(jo, QStringLiteral("state"), pod.state); addParam<IfNotEmpty>(jo, QStringLiteral("timeline"), pod.timeline); addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), pod.accountData); -} -void JsonObjectConverter<RoomFilter>::fillFrom(const QJsonObject& jo, - RoomFilter& result) +} + +void JsonObjectConverter<RoomFilter>::fillFrom(const QJsonObject& jo, RoomFilter& result) { fromJson(jo.value("not_rooms"_ls), result.notRooms); fromJson(jo.value("rooms"_ls), result.rooms); @@ -47,8 +47,11 @@ void JsonObjectConverter<RoomFilter>::fillFrom(const QJsonObject& jo, fromJson(jo.value("state"_ls), result.state); fromJson(jo.value("timeline"_ls), result.timeline); fromJson(jo.value("account_data"_ls), result.accountData); + } + + void JsonObjectConverter<Filter>::dumpTo(QJsonObject& jo, const Filter& pod) { addParam<IfNotEmpty>(jo, QStringLiteral("event_fields"), pod.eventFields); @@ -56,14 +59,18 @@ void JsonObjectConverter<Filter>::dumpTo(QJsonObject& jo, const Filter& pod) addParam<IfNotEmpty>(jo, QStringLiteral("presence"), pod.presence); addParam<IfNotEmpty>(jo, QStringLiteral("account_data"), pod.accountData); addParam<IfNotEmpty>(jo, QStringLiteral("room"), pod.room); -} -void JsonObjectConverter<Filter>::fillFrom(const QJsonObject& jo, - Filter& result) +} + +void JsonObjectConverter<Filter>::fillFrom(const QJsonObject& jo, Filter& result) { fromJson(jo.value("event_fields"_ls), result.eventFields); fromJson(jo.value("event_format"_ls), result.eventFormat); fromJson(jo.value("presence"_ls), result.presence); fromJson(jo.value("account_data"_ls), result.accountData); fromJson(jo.value("room"_ls), result.room); + } + + + diff --git a/lib/csapi/definitions/sync_filter.h b/lib/csapi/definitions/sync_filter.h index d523c388..551ba2fd 100644 --- a/lib/csapi/definitions/sync_filter.h +++ b/lib/csapi/definitions/sync_filter.h @@ -4,94 +4,83 @@ #pragma once -#include "converters.h" + #include "converters.h" + #include "csapi/definitions/event_filter.h" +#include "converters.h" #include "csapi/definitions/room_event_filter.h" -namespace QMatrixClient { - // Data structures +namespace QMatrixClient +{ + +// Data structures + +/// The state events to include for rooms. +struct StateFilter : RoomEventFilter +{ + /// If ``true``, the only ``m.room.member`` events returned inthe ``state`` section of the ``/sync`` response are thosewhich are definitely necessary for a client to displaythe ``sender`` of the timeline events in that response.If ``false``, ``m.room.member`` events are not filtered.By default, servers should suppress duplicate redundantlazy-loaded ``m.room.member`` events from being sent to a givenclient across multiple calls to ``/sync``, given that most clientscache membership events (see ``include_redundant_members``to change this behaviour). + Omittable<bool> lazyLoadMembers; + /// If ``true``, the ``state`` section of the ``/sync`` response willalways contain the ``m.room.member`` events required to displaythe ``sender`` of the timeline events in that response, assuming``lazy_load_members`` is enabled. This means that redundantduplicate member events may be returned across multiple calls to``/sync``. This is useful for naive clients who never trackmembership data. If ``false``, duplicate ``m.room.member`` eventsmay be suppressed by the server across multiple calls to ``/sync``.If ``lazy_load_members`` is ``false`` this field is ignored. + Omittable<bool> includeRedundantMembers; + + +}; + +template <> struct JsonObjectConverter<StateFilter> +{ + static void dumpTo(QJsonObject& jo, const StateFilter& pod); + static void fillFrom(const QJsonObject& jo, StateFilter& pod);}; +/// Filters to be applied to room data. +struct RoomFilter +{ + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the ``'rooms'`` filter. This filter is applied before the filters in ``ephemeral``, ``state``, ``timeline`` or ``account_data`` + QStringList notRooms; + /// A list of room IDs to include. If this list is absent then all rooms are included. This filter is applied before the filters in ``ephemeral``, ``state``, ``timeline`` or ``account_data`` + QStringList rooms; + /// The events that aren't recorded in the room history, e.g. typing and receipts, to include for rooms. + Omittable<RoomEventFilter> ephemeral; + /// Include rooms that the user has left in the sync, default false + Omittable<bool> includeLeave; /// The state events to include for rooms. - struct StateFilter : RoomEventFilter { - /// If ``true``, the only ``m.room.member`` events returned in - /// the ``state`` section of the ``/sync`` response are those - /// which are definitely necessary for a client to display - /// the ``sender`` of the timeline events in that response. - /// If ``false``, ``m.room.member`` events are not filtered. - /// By default, servers should suppress duplicate redundant - /// lazy-loaded ``m.room.member`` events from being sent to a given - /// client across multiple calls to ``/sync``, given that most clients - /// cache membership events (see ``include_redundant_members`` - /// to change this behaviour). - Omittable<bool> lazyLoadMembers; - /// If ``true``, the ``state`` section of the ``/sync`` response will - /// always contain the ``m.room.member`` events required to display - /// the ``sender`` of the timeline events in that response, assuming - /// ``lazy_load_members`` is enabled. This means that redundant - /// duplicate member events may be returned across multiple calls to - /// ``/sync``. This is useful for naive clients who never track - /// membership data. If ``false``, duplicate ``m.room.member`` events - /// may be suppressed by the server across multiple calls to ``/sync``. - /// If ``lazy_load_members`` is ``false`` this field is ignored. - Omittable<bool> includeRedundantMembers; - }; - template <> struct JsonObjectConverter<StateFilter> { - static void dumpTo(QJsonObject& jo, const StateFilter& pod); - static void fillFrom(const QJsonObject& jo, StateFilter& pod); - }; + Omittable<StateFilter> state; + /// The message and state update events to include for rooms. + Omittable<RoomEventFilter> timeline; + /// The per user account data to include for rooms. + Omittable<RoomEventFilter> accountData; + +}; + +template <> struct JsonObjectConverter<RoomFilter> +{ + static void dumpTo(QJsonObject& jo, const RoomFilter& pod); + static void fillFrom(const QJsonObject& jo, RoomFilter& pod);}; + + +struct Filter +{ + /// List of event fields to include. If this list is absent then all fields are included. The entries may include '.' charaters to indicate sub-fields. So ['content.body'] will include the 'body' field of the 'content' object. A literal '.' character in a field name may be escaped using a '\\'. A server may include more fields than were requested. + QStringList eventFields; + /// The format to use for events. 'client' will return the events in a format suitable for clients. 'federation' will return the raw event as receieved over federation. The default is 'client'. + QString eventFormat; + /// The presence updates to include. + Omittable<EventFilter> presence; + /// The user account data that isn't associated with rooms to include. + Omittable<EventFilter> accountData; /// Filters to be applied to room data. - struct RoomFilter { - /// A list of room IDs to exclude. If this list is absent then no rooms - /// are excluded. A matching room will be excluded even if it is listed - /// in the ``'rooms'`` filter. This filter is applied before the filters - /// in ``ephemeral``, ``state``, ``timeline`` or ``account_data`` - QStringList notRooms; - /// A list of room IDs to include. If this list is absent then all rooms - /// are included. This filter is applied before the filters in - /// ``ephemeral``, ``state``, ``timeline`` or ``account_data`` - QStringList rooms; - /// The events that aren't recorded in the room history, e.g. typing and - /// receipts, to include for rooms. - Omittable<RoomEventFilter> ephemeral; - /// Include rooms that the user has left in the sync, default false - Omittable<bool> includeLeave; - /// The state events to include for rooms. - Omittable<StateFilter> state; - /// The message and state update events to include for rooms. - Omittable<RoomEventFilter> timeline; - /// The per user account data to include for rooms. - Omittable<RoomEventFilter> accountData; - }; - template <> struct JsonObjectConverter<RoomFilter> { - static void dumpTo(QJsonObject& jo, const RoomFilter& pod); - static void fillFrom(const QJsonObject& jo, RoomFilter& pod); - }; - - struct Filter { - /// List of event fields to include. If this list is absent then all - /// fields are included. The entries may include '.' charaters to - /// indicate sub-fields. So ['content.body'] will include the 'body' - /// field of the 'content' object. A literal '.' character in a field - /// name may be escaped using a '\\'. A server may include more fields - /// than were requested. - QStringList eventFields; - /// The format to use for events. 'client' will return the events in a - /// format suitable for clients. 'federation' will return the raw event - /// as receieved over federation. The default is 'client'. - QString eventFormat; - /// The presence updates to include. - Omittable<EventFilter> presence; - /// The user account data that isn't associated with rooms to include. - Omittable<EventFilter> accountData; - /// Filters to be applied to room data. - Omittable<RoomFilter> room; - }; - template <> struct JsonObjectConverter<Filter> { - static void dumpTo(QJsonObject& jo, const Filter& pod); - static void fillFrom(const QJsonObject& jo, Filter& pod); - }; + Omittable<RoomFilter> room; + + +}; + +template <> struct JsonObjectConverter<Filter> +{ + static void dumpTo(QJsonObject& jo, const Filter& pod); + static void fillFrom(const QJsonObject& jo, Filter& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/user_identifier.cpp b/lib/csapi/definitions/user_identifier.cpp index 998f1b85..02201179 100644 --- a/lib/csapi/definitions/user_identifier.cpp +++ b/lib/csapi/definitions/user_identifier.cpp @@ -4,19 +4,23 @@ #include "user_identifier.h" + using namespace QMatrixClient; -void JsonObjectConverter<UserIdentifier>::dumpTo(QJsonObject& jo, - const UserIdentifier& pod) + +void JsonObjectConverter<UserIdentifier>::dumpTo(QJsonObject& jo, const UserIdentifier& pod) { fillJson(jo, pod.additionalProperties); addParam<>(jo, QStringLiteral("type"), pod.type); -} -void JsonObjectConverter<UserIdentifier>::fillFrom(QJsonObject jo, - UserIdentifier& result) +} + +void JsonObjectConverter<UserIdentifier>::fillFrom(QJsonObject jo, UserIdentifier& result) { fromJson(jo.take("type"_ls), result.type); - fromJson(jo, result.additionalProperties); + } + + + diff --git a/lib/csapi/definitions/user_identifier.h b/lib/csapi/definitions/user_identifier.h index 4a9ce684..831ba191 100644 --- a/lib/csapi/definitions/user_identifier.h +++ b/lib/csapi/definitions/user_identifier.h @@ -4,24 +4,34 @@ #pragma once + + #include "converters.h" #include <QtCore/QVariant> -namespace QMatrixClient { - // Data structures +namespace QMatrixClient +{ + +// Data structures + +/// Identification information for a user +struct UserIdentifier +{ + /// The type of identification. See `Identifier types`_ for supported values and additional property descriptions. + QString type; + /// Identification information for a user - struct UserIdentifier { - /// The type of identification. See `Identifier types`_ for supported - /// values and additional property descriptions. - QString type; - /// Identification information for a user - QVariantHash additionalProperties; - }; - template <> struct JsonObjectConverter<UserIdentifier> { - static void dumpTo(QJsonObject& jo, const UserIdentifier& pod); - static void fillFrom(QJsonObject jo, UserIdentifier& pod); - }; + QVariantHash additionalProperties; + +}; + +template <> struct JsonObjectConverter<UserIdentifier> +{ + static void dumpTo(QJsonObject& jo, const UserIdentifier& pod); + static void fillFrom(QJsonObject jo, UserIdentifier& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/wellknown/full.cpp b/lib/csapi/definitions/wellknown/full.cpp index 35c0df5b..a2d71db0 100644 --- a/lib/csapi/definitions/wellknown/full.cpp +++ b/lib/csapi/definitions/wellknown/full.cpp @@ -4,22 +4,25 @@ #include "full.h" + using namespace QMatrixClient; -void JsonObjectConverter<DiscoveryInformation>::dumpTo( - QJsonObject& jo, const DiscoveryInformation& pod) + +void JsonObjectConverter<DiscoveryInformation>::dumpTo(QJsonObject& jo, const DiscoveryInformation& pod) { fillJson(jo, pod.additionalProperties); addParam<>(jo, QStringLiteral("m.homeserver"), pod.homeserver); - addParam<IfNotEmpty>(jo, QStringLiteral("m.identity_server"), - pod.identityServer); -} + addParam<IfNotEmpty>(jo, QStringLiteral("m.identity_server"), pod.identityServer); -void JsonObjectConverter<DiscoveryInformation>::fillFrom( - QJsonObject jo, DiscoveryInformation& result) +} + +void JsonObjectConverter<DiscoveryInformation>::fillFrom(QJsonObject jo, DiscoveryInformation& result) { fromJson(jo.take("m.homeserver"_ls), result.homeserver); fromJson(jo.take("m.identity_server"_ls), result.identityServer); - fromJson(jo, result.additionalProperties); + } + + + diff --git a/lib/csapi/definitions/wellknown/full.h b/lib/csapi/definitions/wellknown/full.h index 9b920eda..ef975969 100644 --- a/lib/csapi/definitions/wellknown/full.h +++ b/lib/csapi/definitions/wellknown/full.h @@ -4,32 +4,40 @@ #pragma once + + #include "converters.h" +#include <QtCore/QJsonObject> #include "converters.h" #include "csapi/definitions/wellknown/homeserver.h" #include "csapi/definitions/wellknown/identity_server.h" #include <QtCore/QHash> -#include <QtCore/QJsonObject> -namespace QMatrixClient { - // Data structures - - /// Used by clients to determine the homeserver, identity server, and other - /// optional components they should be interacting with. - struct DiscoveryInformation { - /// Used by clients to determine the homeserver, identity server, and - /// other optional components they should be interacting with. - HomeserverInformation homeserver; - /// Used by clients to determine the homeserver, identity server, and - /// other optional components they should be interacting with. - Omittable<IdentityServerInformation> identityServer; - /// Application-dependent keys using Java package naming convention. - QHash<QString, QJsonObject> additionalProperties; - }; - template <> struct JsonObjectConverter<DiscoveryInformation> { - static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod); - static void fillFrom(QJsonObject jo, DiscoveryInformation& pod); - }; +namespace QMatrixClient +{ + +// Data structures + +/// Used by clients to determine the homeserver, identity server, and other/// optional components they should be interacting with. +struct DiscoveryInformation +{ + /// Used by clients to determine the homeserver, identity server, and otheroptional components they should be interacting with. + HomeserverInformation homeserver; + /// Used by clients to determine the homeserver, identity server, and otheroptional components they should be interacting with. + Omittable<IdentityServerInformation> identityServer; + + + /// Application-dependent keys using Java package naming convention. + QHash<QString, QJsonObject> additionalProperties; + +}; + +template <> struct JsonObjectConverter<DiscoveryInformation> +{ + static void dumpTo(QJsonObject& jo, const DiscoveryInformation& pod); + static void fillFrom(QJsonObject jo, DiscoveryInformation& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/wellknown/homeserver.cpp b/lib/csapi/definitions/wellknown/homeserver.cpp index a7337520..a8a5077b 100644 --- a/lib/csapi/definitions/wellknown/homeserver.cpp +++ b/lib/csapi/definitions/wellknown/homeserver.cpp @@ -4,16 +4,21 @@ #include "homeserver.h" + using namespace QMatrixClient; -void JsonObjectConverter<HomeserverInformation>::dumpTo( - QJsonObject& jo, const HomeserverInformation& pod) + +void JsonObjectConverter<HomeserverInformation>::dumpTo(QJsonObject& jo, const HomeserverInformation& pod) { addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); -} -void JsonObjectConverter<HomeserverInformation>::fillFrom( - const QJsonObject& jo, HomeserverInformation& result) +} + +void JsonObjectConverter<HomeserverInformation>::fillFrom(const QJsonObject& jo, HomeserverInformation& result) { fromJson(jo.value("base_url"_ls), result.baseUrl); + } + + + diff --git a/lib/csapi/definitions/wellknown/homeserver.h b/lib/csapi/definitions/wellknown/homeserver.h index 8bd3c150..fe6af172 100644 --- a/lib/csapi/definitions/wellknown/homeserver.h +++ b/lib/csapi/definitions/wellknown/homeserver.h @@ -4,19 +4,30 @@ #pragma once + + #include "converters.h" -namespace QMatrixClient { - // Data structures - - /// Used by clients to discover homeserver information. - struct HomeserverInformation { - /// The base URL for the homeserver for client-server connections. - QString baseUrl; - }; - template <> struct JsonObjectConverter<HomeserverInformation> { - static void dumpTo(QJsonObject& jo, const HomeserverInformation& pod); - static void fillFrom(const QJsonObject& jo, HomeserverInformation& pod); - }; + +namespace QMatrixClient +{ + +// Data structures + +/// Used by clients to discover homeserver information. +struct HomeserverInformation +{ + /// The base URL for the homeserver for client-server connections. + QString baseUrl; + + +}; + +template <> struct JsonObjectConverter<HomeserverInformation> +{ + static void dumpTo(QJsonObject& jo, const HomeserverInformation& pod); + static void fillFrom(const QJsonObject& jo, HomeserverInformation& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/definitions/wellknown/identity_server.cpp b/lib/csapi/definitions/wellknown/identity_server.cpp index 46a614d8..a9fae614 100644 --- a/lib/csapi/definitions/wellknown/identity_server.cpp +++ b/lib/csapi/definitions/wellknown/identity_server.cpp @@ -4,16 +4,21 @@ #include "identity_server.h" + using namespace QMatrixClient; -void JsonObjectConverter<IdentityServerInformation>::dumpTo( - QJsonObject& jo, const IdentityServerInformation& pod) + +void JsonObjectConverter<IdentityServerInformation>::dumpTo(QJsonObject& jo, const IdentityServerInformation& pod) { addParam<>(jo, QStringLiteral("base_url"), pod.baseUrl); -} -void JsonObjectConverter<IdentityServerInformation>::fillFrom( - const QJsonObject& jo, IdentityServerInformation& result) +} + +void JsonObjectConverter<IdentityServerInformation>::fillFrom(const QJsonObject& jo, IdentityServerInformation& result) { fromJson(jo.value("base_url"_ls), result.baseUrl); + } + + + diff --git a/lib/csapi/definitions/wellknown/identity_server.h b/lib/csapi/definitions/wellknown/identity_server.h index fd53dfc1..4462f86e 100644 --- a/lib/csapi/definitions/wellknown/identity_server.h +++ b/lib/csapi/definitions/wellknown/identity_server.h @@ -4,21 +4,30 @@ #pragma once + + #include "converters.h" -namespace QMatrixClient { - // Data structures - - /// Used by clients to discover identity server information. - struct IdentityServerInformation { - /// The base URL for the identity server for client-server connections. - QString baseUrl; - }; - template <> struct JsonObjectConverter<IdentityServerInformation> { - static void dumpTo(QJsonObject& jo, - const IdentityServerInformation& pod); - static void fillFrom(const QJsonObject& jo, - IdentityServerInformation& pod); - }; + +namespace QMatrixClient +{ + +// Data structures + +/// Used by clients to discover identity server information. +struct IdentityServerInformation +{ + /// The base URL for the identity server for client-server connections. + QString baseUrl; + + +}; + +template <> struct JsonObjectConverter<IdentityServerInformation> +{ + static void dumpTo(QJsonObject& jo, const IdentityServerInformation& pod); + static void fillFrom(const QJsonObject& jo, IdentityServerInformation& pod);}; + + } // namespace QMatrixClient diff --git a/lib/csapi/device_management.cpp b/lib/csapi/device_management.cpp index 7d15bb2b..9135c22d 100644 --- a/lib/csapi/device_management.cpp +++ b/lib/csapi/device_management.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetDevicesJob::Private { - public: +public: QVector<Device> devices; }; @@ -26,10 +26,9 @@ QUrl GetDevicesJob::makeRequestUrl(QUrl baseUrl) static const auto GetDevicesJobName = QStringLiteral("GetDevicesJob"); GetDevicesJob::GetDevicesJob() - : BaseJob(HttpVerb::Get, GetDevicesJobName, basePath % "/devices"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetDevicesJobName, basePath % "/devices") + , d(new Private) +{} GetDevicesJob::~GetDevicesJob() = default; @@ -39,12 +38,13 @@ BaseJob::Status GetDevicesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("devices"_ls), d->devices); + return Success; } class GetDeviceJob::Private { - public: +public: Device data; }; @@ -57,11 +57,9 @@ QUrl GetDeviceJob::makeRequestUrl(QUrl baseUrl, const QString& deviceId) static const auto GetDeviceJobName = QStringLiteral("GetDeviceJob"); GetDeviceJob::GetDeviceJob(const QString& deviceId) - : BaseJob(HttpVerb::Get, GetDeviceJobName, - basePath % "/devices/" % deviceId), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetDeviceJobName, basePath % "/devices/" % deviceId) + , d(new Private) +{} GetDeviceJob::~GetDeviceJob() = default; @@ -101,8 +99,7 @@ static const auto DeleteDevicesJobName = QStringLiteral("DeleteDevicesJob"); DeleteDevicesJob::DeleteDevicesJob(const QStringList& devices, const Omittable<AuthenticationData>& auth) - : BaseJob(HttpVerb::Post, DeleteDevicesJobName, - basePath % "/delete_devices") + : BaseJob(HttpVerb::Post, DeleteDevicesJobName, basePath % "/delete_devices") { QJsonObject _data; addParam<>(_data, QStringLiteral("devices"), devices); diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h index 628f26d2..01838c6f 100644 --- a/lib/csapi/device_management.h +++ b/lib/csapi/device_management.h @@ -4,138 +4,145 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/auth_data.h" #include "csapi/definitions/client_device.h" + +#include "jobs/basejob.h" + #include <QtCore/QVector> -namespace QMatrixClient { - // Operations - - /// List registered devices for the current user - /// - /// Gets information about all devices for the current user. - class GetDevicesJob : public BaseJob - { - public: - explicit GetDevicesJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetDevicesJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetDevicesJob() override; - - // Result properties - - /// A list of all registered devices for this user. - const QVector<Device>& devices() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Get a single device - /// - /// Gets information on a single device, by device id. - class GetDeviceJob : public BaseJob - { - public: - /*! Get a single device - * \param deviceId - * The device to retrieve. - */ - explicit GetDeviceJob(const QString& deviceId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetDeviceJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& deviceId); - - ~GetDeviceJob() override; - - // Result properties - - /// Device information - const Device& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Update a device - /// - /// Updates the metadata on the given device. - class UpdateDeviceJob : public BaseJob - { - public: - /*! Update a device - * \param deviceId - * The device to update. - * \param displayName - * The new display name for this device. If not given, the - * display name is unchanged. - */ - explicit UpdateDeviceJob(const QString& deviceId, - const QString& displayName = {}); - }; - - /// Delete a device - /// - /// This API endpoint uses the `User-Interactive Authentication API`_. - /// - /// Deletes the given device, and invalidates any access token associated - /// with it. - class DeleteDeviceJob : public BaseJob - { - public: - /*! Delete a device - * \param deviceId - * The device to delete. - * \param auth - * Additional authentication information for the - * user-interactive authentication API. - */ - explicit DeleteDeviceJob( - const QString& deviceId, - const Omittable<AuthenticationData>& auth = none); - }; - - /// Bulk deletion of devices - /// - /// This API endpoint uses the `User-Interactive Authentication API`_. - /// - /// Deletes the given devices, and invalidates any access token associated - /// with them. - class DeleteDevicesJob : public BaseJob - { - public: - /*! Bulk deletion of devices - * \param devices - * The list of device IDs to delete. - * \param auth - * Additional authentication information for the - * user-interactive authentication API. - */ - explicit DeleteDevicesJob( - const QStringList& devices, - const Omittable<AuthenticationData>& auth = none); - }; +namespace QMatrixClient +{ + +// Operations + +/// List registered devices for the current user +/*! + * Gets information about all devices for the current user. + */ +class GetDevicesJob : public BaseJob +{ +public: + explicit GetDevicesJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetDevicesJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetDevicesJob() override; + + // Result properties + + /// A list of all registered devices for this user. + const QVector<Device>& devices() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get a single device +/*! + * Gets information on a single device, by device id. + */ +class GetDeviceJob : public BaseJob +{ +public: + /*! Get a single device + * \param deviceId + * The device to retrieve. + */ + explicit GetDeviceJob(const QString& deviceId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetDeviceJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& deviceId); + + ~GetDeviceJob() override; + + // Result properties + + /// Device information + const Device& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Update a device +/*! + * Updates the metadata on the given device. + */ +class UpdateDeviceJob : public BaseJob +{ +public: + /*! Update a device + * \param deviceId + * The device to update. + * \param displayName + * The new display name for this device. If not given, the + * display name is unchanged. + */ + explicit UpdateDeviceJob(const QString& deviceId, + const QString& displayName = {}); +}; + +/// Delete a device +/*! + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * Deletes the given device, and invalidates any access token associated with it. + */ +class DeleteDeviceJob : public BaseJob +{ +public: + /*! Delete a device + * \param deviceId + * The device to delete. + * \param auth + * Additional authentication information for the + * user-interactive authentication API. + */ + explicit DeleteDeviceJob(const QString& deviceId, + const Omittable<AuthenticationData>& auth = none); +}; + +/// Bulk deletion of devices +/*! + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * Deletes the given devices, and invalidates any access token associated with + * them. + */ +class DeleteDevicesJob : public BaseJob +{ +public: + /*! Bulk deletion of devices + * \param devices + * The list of device IDs to delete. + * \param auth + * Additional authentication information for the + * user-interactive authentication API. + */ + explicit DeleteDevicesJob(const QStringList& devices, + const Omittable<AuthenticationData>& auth = none); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/directory.cpp b/lib/csapi/directory.cpp index b4282ffb..992d1da5 100644 --- a/lib/csapi/directory.cpp +++ b/lib/csapi/directory.cpp @@ -14,8 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0/directory"); static const auto SetRoomAliasJobName = QStringLiteral("SetRoomAliasJob"); -SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, - const QString& roomId) +SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) : BaseJob(HttpVerb::Put, SetRoomAliasJobName, basePath % "/room/" % roomAlias) { @@ -26,7 +25,7 @@ SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, class GetRoomIdByAliasJob::Private { - public: +public: QString roomId; QStringList servers; }; @@ -38,14 +37,13 @@ QUrl GetRoomIdByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& roomAlias) } static const auto GetRoomIdByAliasJobName = - QStringLiteral("GetRoomIdByAliasJob"); + QStringLiteral("GetRoomIdByAliasJob"); GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Get, GetRoomIdByAliasJobName, - basePath % "/room/" % roomAlias, false), - d(new Private) -{ -} + basePath % "/room/" % roomAlias, false) + , d(new Private) +{} GetRoomIdByAliasJob::~GetRoomIdByAliasJob() = default; @@ -58,6 +56,7 @@ BaseJob::Status GetRoomIdByAliasJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("room_id"_ls), d->roomId); fromJson(json.value("servers"_ls), d->servers); + return Success; } @@ -72,5 +71,4 @@ static const auto DeleteRoomAliasJobName = QStringLiteral("DeleteRoomAliasJob"); DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) : BaseJob(HttpVerb::Delete, DeleteRoomAliasJobName, basePath % "/room/" % roomAlias) -{ -} +{} diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h index 6bf5ad14..f5331db8 100644 --- a/lib/csapi/directory.h +++ b/lib/csapi/directory.h @@ -6,86 +6,91 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Create a new mapping from room alias to room ID. - class SetRoomAliasJob : public BaseJob - { - public: - /*! Create a new mapping from room alias to room ID. - * \param roomAlias - * The room alias to set. - * \param roomId - * The room ID to set. - */ - explicit SetRoomAliasJob(const QString& roomAlias, - const QString& roomId); - }; - - /// Get the room ID corresponding to this room alias. - /// - /// Requests that the server resolve a room alias to a room ID. - /// - /// The server will use the federation API to resolve the alias if the - /// domain part of the alias does not correspond to the server's own - /// domain. - class GetRoomIdByAliasJob : public BaseJob - { - public: - /*! Get the room ID corresponding to this room alias. - * \param roomAlias - * The room alias. - */ - explicit GetRoomIdByAliasJob(const QString& roomAlias); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomIdByAliasJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); - - ~GetRoomIdByAliasJob() override; - - // Result properties - - /// The room ID for this room alias. - const QString& roomId() const; - /// A list of servers that are aware of this room alias. - const QStringList& servers() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Remove a mapping of room alias to room ID. - /// - /// Remove a mapping of room alias to room ID. - /// - /// Servers may choose to implement additional access control checks here, - /// for instance that room aliases can only be deleted by their creator or a - /// server administrator. - class DeleteRoomAliasJob : public BaseJob - { - public: - /*! Remove a mapping of room alias to room ID. - * \param roomAlias - * The room alias to remove. - */ - explicit DeleteRoomAliasJob(const QString& roomAlias); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * DeleteRoomAliasJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); - }; +namespace QMatrixClient +{ + +// Operations + +/// Create a new mapping from room alias to room ID. + +class SetRoomAliasJob : public BaseJob +{ +public: + /*! Create a new mapping from room alias to room ID. + * \param roomAlias + * The room alias to set. + * \param roomId + * The room ID to set. + */ + explicit SetRoomAliasJob(const QString& roomAlias, const QString& roomId); +}; + +/// Get the room ID corresponding to this room alias. +/*! + * Requests that the server resolve a room alias to a room ID. + * + * The server will use the federation API to resolve the alias if the + * domain part of the alias does not correspond to the server's own + * domain. + */ +class GetRoomIdByAliasJob : public BaseJob +{ +public: + /*! Get the room ID corresponding to this room alias. + * \param roomAlias + * The room alias. + */ + explicit GetRoomIdByAliasJob(const QString& roomAlias); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomIdByAliasJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); + + ~GetRoomIdByAliasJob() override; + + // Result properties + + /// The room ID for this room alias. + const QString& roomId() const; + /// A list of servers that are aware of this room alias. + const QStringList& servers() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Remove a mapping of room alias to room ID. +/*! + * Remove a mapping of room alias to room ID. + * + * Servers may choose to implement additional access control checks here, for + * instance that room aliases can only be deleted by their creator or a server + * administrator. + */ +class DeleteRoomAliasJob : public BaseJob +{ +public: + /*! Remove a mapping of room alias to room ID. + * \param roomAlias + * The room alias to remove. + */ + explicit DeleteRoomAliasJob(const QString& roomAlias); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * DeleteRoomAliasJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomAlias); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/event_context.cpp b/lib/csapi/event_context.cpp index 9ead6ac6..936b2430 100644 --- a/lib/csapi/event_context.cpp +++ b/lib/csapi/event_context.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetEventContextJob::Private { - public: +public: QString begin; QString end; RoomEvents eventsBefore; @@ -36,7 +36,7 @@ QUrl GetEventContextJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" % roomId % "/context/" - % eventId, + % eventId, queryToGetEventContext(limit)); } @@ -47,10 +47,9 @@ GetEventContextJob::GetEventContextJob(const QString& roomId, Omittable<int> limit) : BaseJob(HttpVerb::Get, GetEventContextJobName, basePath % "/rooms/" % roomId % "/context/" % eventId, - queryToGetEventContext(limit)), - d(new Private) -{ -} + queryToGetEventContext(limit)) + , d(new Private) +{} GetEventContextJob::~GetEventContextJob() = default; @@ -81,5 +80,6 @@ BaseJob::Status GetEventContextJob::parseJson(const QJsonDocument& data) fromJson(json.value("event"_ls), d->event); fromJson(json.value("events_after"_ls), d->eventsAfter); fromJson(json.value("state"_ls), d->state); + return Success; } diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h index cfefc550..ca06f4b9 100644 --- a/lib/csapi/event_context.h +++ b/lib/csapi/event_context.h @@ -4,68 +4,71 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "events/eventloader.h" +#include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Get events and state around the specified event. +/*! + * This API returns a number of events that happened just before and + * after the specified event. This allows clients to get the context + * surrounding an event. + */ +class GetEventContextJob : public BaseJob +{ +public: + /*! Get events and state around the specified event. + * \param roomId + * The room to get events from. + * \param eventId + * The event to get context around. + * \param limit + * The maximum number of events to return. Default: 10. + */ + explicit GetEventContextJob(const QString& roomId, const QString& eventId, + Omittable<int> limit = none); - /// Get events and state around the specified event. - /// - /// This API returns a number of events that happened just before and - /// after the specified event. This allows clients to get the context - /// surrounding an event. - class GetEventContextJob : public BaseJob - { - public: - /*! Get events and state around the specified event. - * \param roomId - * The room to get events from. - * \param eventId - * The event to get context around. - * \param limit - * The maximum number of events to return. Default: 10. - */ - explicit GetEventContextJob(const QString& roomId, - const QString& eventId, - Omittable<int> limit = none); + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetEventContextJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& eventId, + Omittable<int> limit = none); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetEventContextJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& eventId, - Omittable<int> limit = none); + ~GetEventContextJob() override; - ~GetEventContextJob() override; + // Result properties - // Result properties + /// A token that can be used to paginate backwards with. + const QString& begin() const; + /// A token that can be used to paginate forwards with. + const QString& end() const; + /// A list of room events that happened just before the + /// requested event, in reverse-chronological order. + RoomEvents&& eventsBefore(); + /// Details of the requested event. + RoomEventPtr&& event(); + /// A list of room events that happened just after the + /// requested event, in chronological order. + RoomEvents&& eventsAfter(); + /// The state of the room at the last event returned. + StateEvents&& state(); - /// A token that can be used to paginate backwards with. - const QString& begin() const; - /// A token that can be used to paginate forwards with. - const QString& end() const; - /// A list of room events that happened just before the - /// requested event, in reverse-chronological order. - RoomEvents&& eventsBefore(); - /// Details of the requested event. - RoomEventPtr&& event(); - /// A list of room events that happened just after the - /// requested event, in chronological order. - RoomEvents&& eventsAfter(); - /// The state of the room at the last event returned. - StateEvents&& state(); +protected: + Status parseJson(const QJsonDocument& data) override; - protected: - Status parseJson(const QJsonDocument& data) override; +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/filter.cpp b/lib/csapi/filter.cpp index 40743de4..79dd5ea5 100644 --- a/lib/csapi/filter.cpp +++ b/lib/csapi/filter.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class DefineFilterJob::Private { - public: +public: QString filterId; }; @@ -22,8 +22,8 @@ static const auto DefineFilterJobName = QStringLiteral("DefineFilterJob"); DefineFilterJob::DefineFilterJob(const QString& userId, const Filter& filter) : BaseJob(HttpVerb::Post, DefineFilterJobName, - basePath % "/user/" % userId % "/filter"), - d(new Private) + basePath % "/user/" % userId % "/filter") + , d(new Private) { setRequestData(Data(toJson(filter))); } @@ -36,34 +36,34 @@ BaseJob::Status DefineFilterJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("filter_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'filter_id' not found in the response" }; fromJson(json.value("filter_id"_ls), d->filterId); + return Success; } class GetFilterJob::Private { - public: +public: Filter data; }; QUrl GetFilterJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& filterId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/user/" % userId % "/filter/" - % filterId); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/user/" + % userId % "/filter/" + % filterId); } static const auto GetFilterJobName = QStringLiteral("GetFilterJob"); GetFilterJob::GetFilterJob(const QString& userId, const QString& filterId) : BaseJob(HttpVerb::Get, GetFilterJobName, - basePath % "/user/" % userId % "/filter/" % filterId), - d(new Private) -{ -} + basePath % "/user/" % userId % "/filter/" % filterId) + , d(new Private) +{} GetFilterJob::~GetFilterJob() = default; diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h index 85e05667..0a5a98ae 100644 --- a/lib/csapi/filter.h +++ b/lib/csapi/filter.h @@ -4,82 +4,88 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/sync_filter.h" -namespace QMatrixClient { - // Operations - - /// Upload a new filter. - /// - /// Uploads a new filter definition to the homeserver. - /// Returns a filter ID that may be used in future requests to - /// restrict which events are returned to the client. - class DefineFilterJob : public BaseJob - { - public: - /*! Upload a new filter. - * \param userId - * The id of the user uploading the filter. The access token must be - * authorized to make requests for this user id. \param filter Uploads a - * new filter definition to the homeserver. Returns a filter ID that may - * be used in future requests to restrict which events are returned to - * the client. - */ - explicit DefineFilterJob(const QString& userId, const Filter& filter); - ~DefineFilterJob() override; - - // Result properties - - /// The ID of the filter that was created. Cannot start - /// with a ``{`` as this character is used to determine - /// if the filter provided is inline JSON or a previously - /// declared filter by homeservers on some APIs. - const QString& filterId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Download a filter - class GetFilterJob : public BaseJob - { - public: - /*! Download a filter - * \param userId - * The user ID to download a filter for. - * \param filterId - * The filter ID to download. - */ - explicit GetFilterJob(const QString& userId, const QString& filterId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetFilterJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, - const QString& filterId); - - ~GetFilterJob() override; - - // Result properties - - /// "The filter defintion" - const Filter& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +#include "jobs/basejob.h" + +namespace QMatrixClient +{ + +// Operations + +/// Upload a new filter. +/*! + * Uploads a new filter definition to the homeserver. + * Returns a filter ID that may be used in future requests to + * restrict which events are returned to the client. + */ +class DefineFilterJob : public BaseJob +{ +public: + /*! Upload a new filter. + * \param userId + * The id of the user uploading the filter. The access token must be + * authorized to make requests for this user id. \param filter Uploads a new + * filter definition to the homeserver. Returns a filter ID that may be used + * in future requests to restrict which events are returned to the client. + */ + explicit DefineFilterJob(const QString& userId, const Filter& filter); + + ~DefineFilterJob() override; + + // Result properties + + /// The ID of the filter that was created. Cannot start + /// with a ``{`` as this character is used to determine + /// if the filter provided is inline JSON or a previously + /// declared filter by homeservers on some APIs. + const QString& filterId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Download a filter + +class GetFilterJob : public BaseJob +{ +public: + /*! Download a filter + * \param userId + * The user ID to download a filter for. + * \param filterId + * The filter ID to download. + */ + explicit GetFilterJob(const QString& userId, const QString& filterId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetFilterJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& filterId); + + ~GetFilterJob() override; + + // Result properties + + /// "The filter defintion" + const Filter& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/inviting.h b/lib/csapi/inviting.h index 12cf1b58..b0911ea8 100644 --- a/lib/csapi/inviting.h +++ b/lib/csapi/inviting.h @@ -6,38 +6,42 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Invite a user to participate in a particular room. +/*! + * .. _invite-by-user-id-endpoint: + * + * *Note that there are two forms of this API, which are documented separately. + * This version of the API requires that the inviter knows the Matrix + * identifier of the invitee. The other is documented in the* + * `third party invites section`_. + * + * This API invites a user to participate in a particular room. + * They do not start participating in the room until they actually join the + * room. + * + * Only users currently in a particular room can invite other users to + * join that room. + * + * If the user was invited to the room, the homeserver will append a + * ``m.room.member`` event to the room. + * + * .. _third party invites section: `invite-by-third-party-id-endpoint`_ + */ +class InviteUserJob : public BaseJob +{ +public: + /*! Invite a user to participate in a particular room. + * \param roomId + * The room identifier (not alias) to which to invite the user. + * \param userId + * The fully qualified user ID of the invitee. + */ + explicit InviteUserJob(const QString& roomId, const QString& userId); +}; - /// Invite a user to participate in a particular room. - /// - /// .. _invite-by-user-id-endpoint: - /// - /// *Note that there are two forms of this API, which are documented - /// separately. This version of the API requires that the inviter knows the - /// Matrix identifier of the invitee. The other is documented in the* `third - /// party invites section`_. - /// - /// This API invites a user to participate in a particular room. - /// They do not start participating in the room until they actually join the - /// room. - /// - /// Only users currently in a particular room can invite other users to - /// join that room. - /// - /// If the user was invited to the room, the homeserver will append a - /// ``m.room.member`` event to the room. - /// - /// .. _third party invites section: `invite-by-third-party-id-endpoint`_ - class InviteUserJob : public BaseJob - { - public: - /*! Invite a user to participate in a particular room. - * \param roomId - * The room identifier (not alias) to which to invite the user. - * \param userId - * The fully qualified user ID of the invitee. - */ - explicit InviteUserJob(const QString& roomId, const QString& userId); - }; } // namespace QMatrixClient diff --git a/lib/csapi/joining.cpp b/lib/csapi/joining.cpp index fe4c83e3..cb40cb96 100644 --- a/lib/csapi/joining.cpp +++ b/lib/csapi/joining.cpp @@ -12,35 +12,38 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<JoinRoomByIdJob::ThirdPartySigned> { - static void dumpTo(QJsonObject& jo, - const JoinRoomByIdJob::ThirdPartySigned& pod) - { - addParam<>(jo, QStringLiteral("sender"), pod.sender); - addParam<>(jo, QStringLiteral("mxid"), pod.mxid); - addParam<>(jo, QStringLiteral("token"), pod.token); - addParam<>(jo, QStringLiteral("signatures"), pod.signatures); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<JoinRoomByIdJob::ThirdPartySigned> +{ + static void dumpTo(QJsonObject& jo, + const JoinRoomByIdJob::ThirdPartySigned& pod) + { + addParam<>(jo, QStringLiteral("sender"), pod.sender); + addParam<>(jo, QStringLiteral("mxid"), pod.mxid); + addParam<>(jo, QStringLiteral("token"), pod.token); + addParam<>(jo, QStringLiteral("signatures"), pod.signatures); + } +}; + } // namespace QMatrixClient class JoinRoomByIdJob::Private { - public: +public: QString roomId; }; static const auto JoinRoomByIdJobName = QStringLiteral("JoinRoomByIdJob"); JoinRoomByIdJob::JoinRoomByIdJob( - const QString& roomId, - const Omittable<ThirdPartySigned>& thirdPartySigned) + const QString& roomId, const Omittable<ThirdPartySigned>& thirdPartySigned) : BaseJob(HttpVerb::Post, JoinRoomByIdJobName, - basePath % "/rooms/" % roomId % "/join"), - d(new Private) + basePath % "/rooms/" % roomId % "/join") + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("third_party_signed"), @@ -56,37 +59,43 @@ BaseJob::Status JoinRoomByIdJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); + return Success; } -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<JoinRoomJob::Signed> { - static void dumpTo(QJsonObject& jo, const JoinRoomJob::Signed& pod) - { - addParam<>(jo, QStringLiteral("sender"), pod.sender); - addParam<>(jo, QStringLiteral("mxid"), pod.mxid); - addParam<>(jo, QStringLiteral("token"), pod.token); - addParam<>(jo, QStringLiteral("signatures"), pod.signatures); - } - }; - - template <> struct JsonObjectConverter<JoinRoomJob::ThirdPartySigned> { - static void dumpTo(QJsonObject& jo, - const JoinRoomJob::ThirdPartySigned& pod) - { - addParam<>(jo, QStringLiteral("signed"), pod.signedData); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<JoinRoomJob::Signed> +{ + static void dumpTo(QJsonObject& jo, const JoinRoomJob::Signed& pod) + { + addParam<>(jo, QStringLiteral("sender"), pod.sender); + addParam<>(jo, QStringLiteral("mxid"), pod.mxid); + addParam<>(jo, QStringLiteral("token"), pod.token); + addParam<>(jo, QStringLiteral("signatures"), pod.signatures); + } +}; + +template <> +struct JsonObjectConverter<JoinRoomJob::ThirdPartySigned> +{ + static void dumpTo(QJsonObject& jo, const JoinRoomJob::ThirdPartySigned& pod) + { + addParam<>(jo, QStringLiteral("signed"), pod.signedData); + } +}; + } // namespace QMatrixClient class JoinRoomJob::Private { - public: +public: QString roomId; }; @@ -103,8 +112,8 @@ JoinRoomJob::JoinRoomJob(const QString& roomIdOrAlias, const QStringList& serverName, const Omittable<ThirdPartySigned>& thirdPartySigned) : BaseJob(HttpVerb::Post, JoinRoomJobName, - basePath % "/join/" % roomIdOrAlias, queryToJoinRoom(serverName)), - d(new Private) + basePath % "/join/" % roomIdOrAlias, queryToJoinRoom(serverName)) + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("third_party_signed"), @@ -120,8 +129,9 @@ BaseJob::Status JoinRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("room_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'room_id' not found in the response" }; fromJson(json.value("room_id"_ls), d->roomId); + return Success; } diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h index 5d118dab..a96f323d 100644 --- a/lib/csapi/joining.h +++ b/lib/csapi/joining.h @@ -4,161 +4,165 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Start the requesting user participating in a particular room. - /// - /// *Note that this API requires a room ID, not alias.* - /// ``/join/{roomIdOrAlias}`` *exists if you have a room alias.* - /// - /// This API starts a user participating in a particular room, if that user - /// is allowed to participate in that room. After this call, the client is - /// allowed to see all current state events in the room, and all subsequent - /// events associated with the room until the user leaves the room. - /// - /// After a user has joined a room, the room will appear as an entry in the - /// response of the |/initialSync|_ and |/sync|_ APIs. - /// - /// If a ``third_party_signed`` was supplied, the homeserver must verify - /// that it matches a pending ``m.room.third_party_invite`` event in the - /// room, and perform key validity checking if required by the event. - class JoinRoomByIdJob : public BaseJob +namespace QMatrixClient +{ + +// Operations + +/// Start the requesting user participating in a particular room. +/*! + * *Note that this API requires a room ID, not alias.* ``/join/{roomIdOrAlias}`` + * *exists if you have a room alias.* + * + * This API starts a user participating in a particular room, if that user + * is allowed to participate in that room. After this call, the client is + * allowed to see all current state events in the room, and all subsequent + * events associated with the room until the user leaves the room. + * + * After a user has joined a room, the room will appear as an entry in the + * response of the |/initialSync|_ and |/sync|_ APIs. + * + * If a ``third_party_signed`` was supplied, the homeserver must verify + * that it matches a pending ``m.room.third_party_invite`` event in the + * room, and perform key validity checking if required by the event. + */ +class JoinRoomByIdJob : public BaseJob +{ +public: + // Inner data structures + + /// A signature of an ``m.third_party_invite`` token to prove that this user + /// owns a third party identity which has been invited to the room. + struct ThirdPartySigned { - public: - // Inner data structures - - /// A signature of an ``m.third_party_invite`` token to prove that this - /// user owns a third party identity which has been invited to the room. - struct ThirdPartySigned { - /// The Matrix ID of the user who issued the invite. - QString sender; - /// The Matrix ID of the invitee. - QString mxid; - /// The state key of the m.third_party_invite event. - QString token; - /// A signatures object containing a signature of the entire signed - /// object. - QJsonObject signatures; - }; - - // Construction/destruction - - /*! Start the requesting user participating in a particular room. - * \param roomId - * The room identifier (not alias) to join. - * \param thirdPartySigned - * A signature of an ``m.third_party_invite`` token to prove that this - * user owns a third party identity which has been invited to the room. - */ - explicit JoinRoomByIdJob( - const QString& roomId, - const Omittable<ThirdPartySigned>& thirdPartySigned = none); - ~JoinRoomByIdJob() override; - - // Result properties - - /// The joined room ID. - const QString& roomId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The Matrix ID of the user who issued the invite. + QString sender; + /// The Matrix ID of the invitee. + QString mxid; + /// The state key of the m.third_party_invite event. + QString token; + /// A signatures object containing a signature of the entire signed + /// object. + QJsonObject signatures; }; - /// Start the requesting user participating in a particular room. - /// + // Construction/destruction + + /*! Start the requesting user participating in a particular room. + * \param roomId + * The room identifier (not alias) to join. + * \param thirdPartySigned + * A signature of an ``m.third_party_invite`` token to prove that this + * user owns a third party identity which has been invited to the room. + */ + explicit JoinRoomByIdJob( + const QString& roomId, + const Omittable<ThirdPartySigned>& thirdPartySigned = none); + + ~JoinRoomByIdJob() override; + + // Result properties + + /// The joined room ID. + const QString& roomId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Start the requesting user participating in a particular room. +/*! + * *Note that this API takes either a room ID or alias, unlike* + * ``/room/{roomId}/join``. + * + * This API starts a user participating in a particular room, if that user + * is allowed to participate in that room. After this call, the client is + * allowed to see all current state events in the room, and all subsequent + * events associated with the room until the user leaves the room. + * + * After a user has joined a room, the room will appear as an entry in the + * response of the |/initialSync|_ and |/sync|_ APIs. + * + * If a ``third_party_signed`` was supplied, the homeserver must verify + * that it matches a pending ``m.room.third_party_invite`` event in the + * room, and perform key validity checking if required by the event. + */ +class JoinRoomJob : public BaseJob +{ +public: + // Inner data structures + /// *Note that this API takes either a room ID or alias, unlike* - /// ``/room/{roomId}/join``. - /// - /// This API starts a user participating in a particular room, if that user - /// is allowed to participate in that room. After this call, the client is - /// allowed to see all current state events in the room, and all subsequent - /// events associated with the room until the user leaves the room. - /// - /// After a user has joined a room, the room will appear as an entry in the - /// response of the |/initialSync|_ and |/sync|_ APIs. - /// - /// If a ``third_party_signed`` was supplied, the homeserver must verify - /// that it matches a pending ``m.room.third_party_invite`` event in the - /// room, and perform key validity checking if required by the event. - class JoinRoomJob : public BaseJob + /// ``/room/{roomId}/join``.This API starts a user participating in a + /// particular room, if that useris allowed to participate in that room. + /// After this call, the client isallowed to see all current state events in + /// the room, and all subsequentevents associated with the room until the + /// user leaves the room.After a user has joined a room, the room will + /// appear as an entry in theresponse of the |/initialSync|_ and |/sync|_ + /// APIs.If a ``third_party_signed`` was supplied, the homeserver must + /// verifythat it matches a pending ``m.room.third_party_invite`` event in + /// theroom, and perform key validity checking if required by the event. + struct Signed { - public: - // Inner data structures - - /// *Note that this API takes either a room ID or alias, unlike* - /// ``/room/{roomId}/join``. - /// - /// This API starts a user participating in a particular room, if that - /// user is allowed to participate in that room. After this call, the - /// client is allowed to see all current state events in the room, and - /// all subsequent events associated with the room until the user leaves - /// the room. - /// - /// After a user has joined a room, the room will appear as an entry in - /// the response of the |/initialSync|_ and |/sync|_ APIs. - /// - /// If a ``third_party_signed`` was supplied, the homeserver must verify - /// that it matches a pending ``m.room.third_party_invite`` event in the - /// room, and perform key validity checking if required by the event. - struct Signed { - /// The Matrix ID of the user who issued the invite. - QString sender; - /// The Matrix ID of the invitee. - QString mxid; - /// The state key of the m.third_party_invite event. - QString token; - /// A signatures object containing a signature of the entire signed - /// object. - QJsonObject signatures; - }; + /// The Matrix ID of the user who issued the invite. + QString sender; + /// The Matrix ID of the invitee. + QString mxid; + /// The state key of the m.third_party_invite event. + QString token; + /// A signatures object containing a signature of the entire signed + /// object. + QJsonObject signatures; + }; + /// A signature of an ``m.third_party_invite`` token to prove that this user + /// owns a third party identity which has been invited to the room. + struct ThirdPartySigned + { /// A signature of an ``m.third_party_invite`` token to prove that this /// user owns a third party identity which has been invited to the room. - struct ThirdPartySigned { - /// A signature of an ``m.third_party_invite`` token to prove that - /// this user owns a third party identity which has been invited to - /// the room. - Signed signedData; - }; - - // Construction/destruction - - /*! Start the requesting user participating in a particular room. - * \param roomIdOrAlias - * The room identifier or alias to join. - * \param serverName - * The servers to attempt to join the room through. One of the servers - * must be participating in the room. - * \param thirdPartySigned - * A signature of an ``m.third_party_invite`` token to prove that this - * user owns a third party identity which has been invited to the room. - */ - explicit JoinRoomJob( - const QString& roomIdOrAlias, - const QStringList& serverName = {}, - const Omittable<ThirdPartySigned>& thirdPartySigned = none); - ~JoinRoomJob() override; - - // Result properties - - /// The joined room ID. - const QString& roomId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + Signed signedData; }; + + // Construction/destruction + + /*! Start the requesting user participating in a particular room. + * \param roomIdOrAlias + * The room identifier or alias to join. + * \param serverName + * The servers to attempt to join the room through. One of the servers + * must be participating in the room. + * \param thirdPartySigned + * A signature of an ``m.third_party_invite`` token to prove that this + * user owns a third party identity which has been invited to the room. + */ + explicit JoinRoomJob( + const QString& roomIdOrAlias, const QStringList& serverName = {}, + const Omittable<ThirdPartySigned>& thirdPartySigned = none); + + ~JoinRoomJob() override; + + // Result properties + + /// The joined room ID. + const QString& roomId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/keys.cpp b/lib/csapi/keys.cpp index 862366a3..1752b865 100644 --- a/lib/csapi/keys.cpp +++ b/lib/csapi/keys.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class UploadKeysJob::Private { - public: +public: QHash<QString, int> oneTimeKeyCounts; }; @@ -22,8 +22,8 @@ static const auto UploadKeysJobName = QStringLiteral("UploadKeysJob"); UploadKeysJob::UploadKeysJob(const Omittable<DeviceKeys>& deviceKeys, const QHash<QString, QVariant>& oneTimeKeys) - : BaseJob(HttpVerb::Post, UploadKeysJobName, basePath % "/keys/upload"), - d(new Private) + : BaseJob(HttpVerb::Post, UploadKeysJobName, basePath % "/keys/upload") + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("device_keys"), deviceKeys); @@ -42,37 +42,43 @@ BaseJob::Status UploadKeysJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("one_time_key_counts"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'one_time_key_counts' not found in the response" }; fromJson(json.value("one_time_key_counts"_ls), d->oneTimeKeyCounts); + return Success; } -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<QueryKeysJob::UnsignedDeviceInfo> { - static void fillFrom(const QJsonObject& jo, - QueryKeysJob::UnsignedDeviceInfo& result) - { - fromJson(jo.value("device_display_name"_ls), - result.deviceDisplayName); - } - }; - - template <> struct JsonObjectConverter<QueryKeysJob::DeviceInformation> { - static void fillFrom(const QJsonObject& jo, - QueryKeysJob::DeviceInformation& result) - { - fillFromJson<DeviceKeys>(jo, result); - fromJson(jo.value("unsigned"_ls), result.unsignedData); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<QueryKeysJob::UnsignedDeviceInfo> +{ + static void fillFrom(const QJsonObject& jo, + QueryKeysJob::UnsignedDeviceInfo& result) + { + fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); + } +}; + +template <> +struct JsonObjectConverter<QueryKeysJob::DeviceInformation> +{ + static void fillFrom(const QJsonObject& jo, + QueryKeysJob::DeviceInformation& result) + { + fillFromJson<DeviceKeys>(jo, result); + fromJson(jo.value("unsigned"_ls), result.unsignedData); + } +}; + } // namespace QMatrixClient class QueryKeysJob::Private { - public: +public: QHash<QString, QJsonObject> failures; QHash<QString, QHash<QString, DeviceInformation>> deviceKeys; }; @@ -81,8 +87,8 @@ static const auto QueryKeysJobName = QStringLiteral("QueryKeysJob"); QueryKeysJob::QueryKeysJob(const QHash<QString, QStringList>& deviceKeys, Omittable<int> timeout, const QString& token) - : BaseJob(HttpVerb::Post, QueryKeysJobName, basePath % "/keys/query"), - d(new Private) + : BaseJob(HttpVerb::Post, QueryKeysJobName, basePath % "/keys/query") + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("timeout"), timeout); @@ -109,12 +115,13 @@ BaseJob::Status QueryKeysJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("failures"_ls), d->failures); fromJson(json.value("device_keys"_ls), d->deviceKeys); + return Success; } class ClaimKeysJob::Private { - public: +public: QHash<QString, QJsonObject> failures; QHash<QString, QHash<QString, QVariant>> oneTimeKeys; }; @@ -122,10 +129,10 @@ class ClaimKeysJob::Private static const auto ClaimKeysJobName = QStringLiteral("ClaimKeysJob"); ClaimKeysJob::ClaimKeysJob( - const QHash<QString, QHash<QString, QString>>& oneTimeKeys, - Omittable<int> timeout) - : BaseJob(HttpVerb::Post, ClaimKeysJobName, basePath % "/keys/claim"), - d(new Private) + const QHash<QString, QHash<QString, QString>>& oneTimeKeys, + Omittable<int> timeout) + : BaseJob(HttpVerb::Post, ClaimKeysJobName, basePath % "/keys/claim") + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("timeout"), timeout); @@ -140,8 +147,7 @@ const QHash<QString, QJsonObject>& ClaimKeysJob::failures() const return d->failures; } -const QHash<QString, QHash<QString, QVariant>>& -ClaimKeysJob::oneTimeKeys() const +const QHash<QString, QHash<QString, QVariant>>& ClaimKeysJob::oneTimeKeys() const { return d->oneTimeKeys; } @@ -151,12 +157,13 @@ BaseJob::Status ClaimKeysJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("failures"_ls), d->failures); fromJson(json.value("one_time_keys"_ls), d->oneTimeKeys); + return Success; } class GetKeysChangesJob::Private { - public: +public: QStringList changed; QStringList left; }; @@ -181,10 +188,9 @@ static const auto GetKeysChangesJobName = QStringLiteral("GetKeysChangesJob"); GetKeysChangesJob::GetKeysChangesJob(const QString& from, const QString& to) : BaseJob(HttpVerb::Get, GetKeysChangesJobName, basePath % "/keys/changes", - queryToGetKeysChanges(from, to)), - d(new Private) -{ -} + queryToGetKeysChanges(from, to)) + , d(new Private) +{} GetKeysChangesJob::~GetKeysChangesJob() = default; @@ -197,5 +203,6 @@ BaseJob::Status GetKeysChangesJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("changed"_ls), d->changed); fromJson(json.value("left"_ls), d->left); + return Success; } diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h index a01cd33b..f69028fd 100644 --- a/lib/csapi/keys.h +++ b/lib/csapi/keys.h @@ -4,223 +4,231 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/device_keys.h" + +#include "jobs/basejob.h" + #include <QtCore/QHash> #include <QtCore/QJsonObject> #include <QtCore/QVariant> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Upload end-to-end encryption keys. - /// - /// Publishes end-to-end encryption keys for the device. - class UploadKeysJob : public BaseJob - { - public: - /*! Upload end-to-end encryption keys. - * \param deviceKeys - * Identity keys for the device. May be absent if no new - * identity keys are required. - * \param oneTimeKeys - * One-time public keys for "pre-key" messages. The names of - * the properties should be in the format - * ``<algorithm>:<key_id>``. The format of the key is determined - * by the key algorithm. - * - * May be absent if no new one-time keys are required. - */ - explicit UploadKeysJob( - const Omittable<DeviceKeys>& deviceKeys = none, - const QHash<QString, QVariant>& oneTimeKeys = {}); - ~UploadKeysJob() override; - - // Result properties - - /// For each key algorithm, the number of unclaimed one-time keys - /// of that type currently held on the server for this device. - const QHash<QString, int>& oneTimeKeyCounts() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +// Operations - /// Download device identity keys. - /// - /// Returns the current devices and identity keys for the given users. - class QueryKeysJob : public BaseJob +/// Upload end-to-end encryption keys. +/*! + * Publishes end-to-end encryption keys for the device. + */ +class UploadKeysJob : public BaseJob +{ +public: + /*! Upload end-to-end encryption keys. + * \param deviceKeys + * Identity keys for the device. May be absent if no new + * identity keys are required. + * \param oneTimeKeys + * One-time public keys for "pre-key" messages. The names of + * the properties should be in the format + * ``<algorithm>:<key_id>``. The format of the key is determined + * by the key algorithm. + * + * May be absent if no new one-time keys are required. + */ + explicit UploadKeysJob(const Omittable<DeviceKeys>& deviceKeys = none, + const QHash<QString, QVariant>& oneTimeKeys = {}); + + ~UploadKeysJob() override; + + // Result properties + + /// For each key algorithm, the number of unclaimed one-time keys + /// of that type currently held on the server for this device. + const QHash<QString, int>& oneTimeKeyCounts() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Download device identity keys. +/*! + * Returns the current devices and identity keys for the given users. + */ +class QueryKeysJob : public BaseJob +{ +public: + // Inner data structures + + /// Additional data added to the device key informationby intermediate + /// servers, and not covered by thesignatures. + struct UnsignedDeviceInfo { - public: - // Inner data structures - - /// Additional data added to the device key information - /// by intermediate servers, and not covered by the - /// signatures. - struct UnsignedDeviceInfo { - /// The display name which the user set on the device. - QString deviceDisplayName; - }; - - /// Returns the current devices and identity keys for the given users. - struct DeviceInformation : DeviceKeys { - /// Additional data added to the device key information - /// by intermediate servers, and not covered by the - /// signatures. - Omittable<UnsignedDeviceInfo> unsignedData; - }; - - // Construction/destruction - - /*! Download device identity keys. - * \param deviceKeys - * The keys to be downloaded. A map from user ID, to a list of - * device IDs, or to an empty list to indicate all devices for the - * corresponding user. - * \param timeout - * The time (in milliseconds) to wait when downloading keys from - * remote servers. 10 seconds is the recommended default. - * \param token - * If the client is fetching keys as a result of a device update - * received in a sync request, this should be the 'since' token of that - * sync request, or any later sync token. This allows the server to - * ensure its response contains the keys advertised by the notification - * in that sync. - */ - explicit QueryKeysJob(const QHash<QString, QStringList>& deviceKeys, - Omittable<int> timeout = none, - const QString& token = {}); - ~QueryKeysJob() override; - - // Result properties - - /// If any remote homeservers could not be reached, they are - /// recorded here. The names of the properties are the names of - /// the unreachable servers. - /// - /// If the homeserver could be reached, but the user or device - /// was unknown, no failure is recorded. Instead, the corresponding - /// user or device is missing from the ``device_keys`` result. - const QHash<QString, QJsonObject>& failures() const; - /// Information on the queried devices. A map from user ID, to a - /// map from device ID to device information. For each device, - /// the information returned will be the same as uploaded via - /// ``/keys/upload``, with the addition of an ``unsigned`` - /// property. - const QHash<QString, QHash<QString, DeviceInformation>>& - deviceKeys() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The display name which the user set on the device. + QString deviceDisplayName; }; - /// Claim one-time encryption keys. - /// - /// Claims one-time keys for use in pre-key messages. - class ClaimKeysJob : public BaseJob + /// Returns the current devices and identity keys for the given users. + struct DeviceInformation : DeviceKeys { - public: - /*! Claim one-time encryption keys. - * \param oneTimeKeys - * The keys to be claimed. A map from user ID, to a map from - * device ID to algorithm name. - * \param timeout - * The time (in milliseconds) to wait when downloading keys from - * remote servers. 10 seconds is the recommended default. - */ - explicit ClaimKeysJob( - const QHash<QString, QHash<QString, QString>>& oneTimeKeys, - Omittable<int> timeout = none); - ~ClaimKeysJob() override; - - // Result properties - - /// If any remote homeservers could not be reached, they are - /// recorded here. The names of the properties are the names of - /// the unreachable servers. - /// - /// If the homeserver could be reached, but the user or device - /// was unknown, no failure is recorded. Instead, the corresponding - /// user or device is missing from the ``one_time_keys`` result. - const QHash<QString, QJsonObject>& failures() const; - /// One-time keys for the queried devices. A map from user ID, to a - /// map from devices to a map from ``<algorithm>:<key_id>`` to the key - /// object. - const QHash<QString, QHash<QString, QVariant>>& oneTimeKeys() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// Additional data added to the device key informationby intermediate + /// servers, and not covered by thesignatures. + Omittable<UnsignedDeviceInfo> unsignedData; }; - /// Query users with recent device key updates. - /// - /// Gets a list of users who have updated their device identity keys since a - /// previous sync token. + // Construction/destruction + + /*! Download device identity keys. + * \param deviceKeys + * The keys to be downloaded. A map from user ID, to a list of + * device IDs, or to an empty list to indicate all devices for the + * corresponding user. + * \param timeout + * The time (in milliseconds) to wait when downloading keys from + * remote servers. 10 seconds is the recommended default. + * \param token + * If the client is fetching keys as a result of a device update received + * in a sync request, this should be the 'since' token of that sync + * request, or any later sync token. This allows the server to ensure its + * response contains the keys advertised by the notification in that sync. + */ + explicit QueryKeysJob(const QHash<QString, QStringList>& deviceKeys, + Omittable<int> timeout = none, + const QString& token = {}); + + ~QueryKeysJob() override; + + // Result properties + + /// If any remote homeservers could not be reached, they are + /// recorded here. The names of the properties are the names of + /// the unreachable servers. /// - /// The server should include in the results any users who: + /// If the homeserver could be reached, but the user or device + /// was unknown, no failure is recorded. Instead, the corresponding + /// user or device is missing from the ``device_keys`` result. + const QHash<QString, QJsonObject>& failures() const; + /// Information on the queried devices. A map from user ID, to a + /// map from device ID to device information. For each device, + /// the information returned will be the same as uploaded via + /// ``/keys/upload``, with the addition of an ``unsigned`` + /// property. + const QHash<QString, QHash<QString, DeviceInformation>>& deviceKeys() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Claim one-time encryption keys. +/*! + * Claims one-time keys for use in pre-key messages. + */ +class ClaimKeysJob : public BaseJob +{ +public: + /*! Claim one-time encryption keys. + * \param oneTimeKeys + * The keys to be claimed. A map from user ID, to a map from + * device ID to algorithm name. + * \param timeout + * The time (in milliseconds) to wait when downloading keys from + * remote servers. 10 seconds is the recommended default. + */ + explicit ClaimKeysJob( + const QHash<QString, QHash<QString, QString>>& oneTimeKeys, + Omittable<int> timeout = none); + + ~ClaimKeysJob() override; + + // Result properties + + /// If any remote homeservers could not be reached, they are + /// recorded here. The names of the properties are the names of + /// the unreachable servers. /// - /// * currently share a room with the calling user (ie, both users have - /// membership state ``join``); *and* - /// * added new device identity keys or removed an existing device with - /// identity keys, between ``from`` and ``to``. - class GetKeysChangesJob : public BaseJob - { - public: - /*! Query users with recent device key updates. - * \param from - * The desired start point of the list. Should be the ``next_batch`` - * field from a response to an earlier call to |/sync|. Users who have - * not uploaded new device identity keys since this point, nor deleted - * existing devices with identity keys since then, will be excluded - * from the results. - * \param to - * The desired end point of the list. Should be the ``next_batch`` - * field from a recent call to |/sync| - typically the most recent - * such call. This may be used by the server as a hint to check its - * caches are up to date. - */ - explicit GetKeysChangesJob(const QString& from, const QString& to); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetKeysChangesJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& from, - const QString& to); - - ~GetKeysChangesJob() override; - - // Result properties - - /// The Matrix User IDs of all users who updated their device - /// identity keys. - const QStringList& changed() const; - /// The Matrix User IDs of all users who may have left all - /// the end-to-end encrypted rooms they previously shared - /// with the user. - const QStringList& left() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + /// If the homeserver could be reached, but the user or device + /// was unknown, no failure is recorded. Instead, the corresponding + /// user or device is missing from the ``one_time_keys`` result. + const QHash<QString, QJsonObject>& failures() const; + /// One-time keys for the queried devices. A map from user ID, to a + /// map from devices to a map from ``<algorithm>:<key_id>`` to the key object. + const QHash<QString, QHash<QString, QVariant>>& oneTimeKeys() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Query users with recent device key updates. +/*! + * Gets a list of users who have updated their device identity keys since a + * previous sync token. + * + * The server should include in the results any users who: + * + * * currently share a room with the calling user (ie, both users have + * membership state ``join``); *and* + * * added new device identity keys or removed an existing device with + * identity keys, between ``from`` and ``to``. + */ +class GetKeysChangesJob : public BaseJob +{ +public: + /*! Query users with recent device key updates. + * \param from + * The desired start point of the list. Should be the ``next_batch`` field + * from a response to an earlier call to |/sync|. Users who have not + * uploaded new device identity keys since this point, nor deleted + * existing devices with identity keys since then, will be excluded + * from the results. + * \param to + * The desired end point of the list. Should be the ``next_batch`` + * field from a recent call to |/sync| - typically the most recent + * such call. This may be used by the server as a hint to check its + * caches are up to date. + */ + explicit GetKeysChangesJob(const QString& from, const QString& to); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetKeysChangesJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& from, + const QString& to); + + ~GetKeysChangesJob() override; + + // Result properties + + /// The Matrix User IDs of all users who updated their device + /// identity keys. + const QStringList& changed() const; + /// The Matrix User IDs of all users who may have left all + /// the end-to-end encrypted rooms they previously shared + /// with the user. + const QStringList& left() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/kicking.h b/lib/csapi/kicking.h index d75b8df3..9566a9a4 100644 --- a/lib/csapi/kicking.h +++ b/lib/csapi/kicking.h @@ -6,32 +6,37 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Kick a user from the room. +/*! + * Kick a user from the room. + * + * The caller must have the required power level in order to perform this + * operation. + * + * Kicking a user adjusts the target member's membership state to be ``leave`` + * with an optional ``reason``. Like with other membership changes, a user can + * directly adjust the target member's state by making a request to + * ``/rooms/<room id>/state/m.room.member/<user id>``. + */ +class KickJob : public BaseJob +{ +public: + /*! Kick a user from the room. + * \param roomId + * The room identifier (not alias) from which the user should be kicked. + * \param userId + * The fully qualified user ID of the user being kicked. + * \param reason + * The reason the user has been kicked. This will be supplied as the + * ``reason`` on the target's updated `m.room.member`_ event. + */ + explicit KickJob(const QString& roomId, const QString& userId, + const QString& reason = {}); +}; - /// Kick a user from the room. - /// - /// Kick a user from the room. - /// - /// The caller must have the required power level in order to perform this - /// operation. - /// - /// Kicking a user adjusts the target member's membership state to be - /// ``leave`` with an optional ``reason``. Like with other membership - /// changes, a user can directly adjust the target member's state by making - /// a request to ``/rooms/<room id>/state/m.room.member/<user id>``. - class KickJob : public BaseJob - { - public: - /*! Kick a user from the room. - * \param roomId - * The room identifier (not alias) from which the user should be - * kicked. \param userId The fully qualified user ID of the user being - * kicked. \param reason The reason the user has been kicked. This will - * be supplied as the - * ``reason`` on the target's updated `m.room.member`_ event. - */ - explicit KickJob(const QString& roomId, const QString& userId, - const QString& reason = {}); - }; } // namespace QMatrixClient diff --git a/lib/csapi/leaving.cpp b/lib/csapi/leaving.cpp index c46567d7..325b1e04 100644 --- a/lib/csapi/leaving.cpp +++ b/lib/csapi/leaving.cpp @@ -23,8 +23,7 @@ static const auto LeaveRoomJobName = QStringLiteral("LeaveRoomJob"); LeaveRoomJob::LeaveRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, LeaveRoomJobName, basePath % "/rooms/" % roomId % "/leave") -{ -} +{} QUrl ForgetRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { @@ -37,5 +36,4 @@ static const auto ForgetRoomJobName = QStringLiteral("ForgetRoomJob"); ForgetRoomJob::ForgetRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, ForgetRoomJobName, basePath % "/rooms/" % roomId % "/forget") -{ -} +{} diff --git a/lib/csapi/leaving.h b/lib/csapi/leaving.h index 374d27d4..2ed6c8e7 100644 --- a/lib/csapi/leaving.h +++ b/lib/csapi/leaving.h @@ -6,66 +6,71 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Stop the requesting user participating in a particular room. - /// - /// This API stops a user participating in a particular room. - /// - /// If the user was already in the room, they will no longer be able to see - /// new events in the room. If the room requires an invite to join, they - /// will need to be re-invited before they can re-join. - /// - /// If the user was invited to the room, but had not joined, this call - /// serves to reject the invite. - /// - /// The user will still be allowed to retrieve history from the room which - /// they were previously allowed to see. - class LeaveRoomJob : public BaseJob - { - public: - /*! Stop the requesting user participating in a particular room. - * \param roomId - * The room identifier to leave. - */ - explicit LeaveRoomJob(const QString& roomId); +// Operations - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * LeaveRoomJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - }; +/// Stop the requesting user participating in a particular room. +/*! + * This API stops a user participating in a particular room. + * + * If the user was already in the room, they will no longer be able to see + * new events in the room. If the room requires an invite to join, they + * will need to be re-invited before they can re-join. + * + * If the user was invited to the room, but had not joined, this call + * serves to reject the invite. + * + * The user will still be allowed to retrieve history from the room which + * they were previously allowed to see. + */ +class LeaveRoomJob : public BaseJob +{ +public: + /*! Stop the requesting user participating in a particular room. + * \param roomId + * The room identifier to leave. + */ + explicit LeaveRoomJob(const QString& roomId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * LeaveRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); +}; + +/// Stop the requesting user remembering about a particular room. +/*! + * This API stops a user remembering about a particular room. + * + * In general, history is a first class citizen in Matrix. After this API + * is called, however, a user will no longer be able to retrieve history + * for this room. If all users on a homeserver forget a room, the room is + * eligible for deletion from that homeserver. + * + * If the user is currently joined to the room, they must leave the room + * before calling this API. + */ +class ForgetRoomJob : public BaseJob +{ +public: + /*! Stop the requesting user remembering about a particular room. + * \param roomId + * The room identifier to forget. + */ + explicit ForgetRoomJob(const QString& roomId); - /// Stop the requesting user remembering about a particular room. - /// - /// This API stops a user remembering about a particular room. - /// - /// In general, history is a first class citizen in Matrix. After this API - /// is called, however, a user will no longer be able to retrieve history - /// for this room. If all users on a homeserver forget a room, the room is - /// eligible for deletion from that homeserver. - /// - /// If the user is currently joined to the room, they must leave the room - /// before calling this API. - class ForgetRoomJob : public BaseJob - { - public: - /*! Stop the requesting user remembering about a particular room. - * \param roomId - * The room identifier to forget. - */ - explicit ForgetRoomJob(const QString& roomId); + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * ForgetRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); +}; - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * ForgetRoomJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - }; } // namespace QMatrixClient diff --git a/lib/csapi/list_joined_rooms.cpp b/lib/csapi/list_joined_rooms.cpp index 145e91ad..43c948f7 100644 --- a/lib/csapi/list_joined_rooms.cpp +++ b/lib/csapi/list_joined_rooms.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetJoinedRoomsJob::Private { - public: +public: QStringList joinedRooms; }; @@ -27,10 +27,9 @@ QUrl GetJoinedRoomsJob::makeRequestUrl(QUrl baseUrl) static const auto GetJoinedRoomsJobName = QStringLiteral("GetJoinedRoomsJob"); GetJoinedRoomsJob::GetJoinedRoomsJob() - : BaseJob(HttpVerb::Get, GetJoinedRoomsJobName, basePath % "/joined_rooms"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetJoinedRoomsJobName, basePath % "/joined_rooms") + , d(new Private) +{} GetJoinedRoomsJob::~GetJoinedRoomsJob() = default; @@ -43,8 +42,9 @@ BaseJob::Status GetJoinedRoomsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("joined_rooms"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'joined_rooms' not found in the response" }; fromJson(json.value("joined_rooms"_ls), d->joinedRooms); + return Success; } diff --git a/lib/csapi/list_joined_rooms.h b/lib/csapi/list_joined_rooms.h index 6f4169e8..1b64a004 100644 --- a/lib/csapi/list_joined_rooms.h +++ b/lib/csapi/list_joined_rooms.h @@ -6,37 +6,41 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Lists the user's current rooms. - /// - /// This API returns a list of the user's current rooms. - class GetJoinedRoomsJob : public BaseJob - { - public: - explicit GetJoinedRoomsJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetJoinedRoomsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetJoinedRoomsJob() override; - - // Result properties - - /// The ID of each room in which the user has ``joined`` membership. - const QStringList& joinedRooms() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Lists the user's current rooms. +/*! + * This API returns a list of the user's current rooms. + */ +class GetJoinedRoomsJob : public BaseJob +{ +public: + explicit GetJoinedRoomsJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetJoinedRoomsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetJoinedRoomsJob() override; + + // Result properties + + /// The ID of each room in which the user has ``joined`` membership. + const QStringList& joinedRooms() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/list_public_rooms.cpp b/lib/csapi/list_public_rooms.cpp index 2649b9f8..4d96dac3 100644 --- a/lib/csapi/list_public_rooms.cpp +++ b/lib/csapi/list_public_rooms.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetRoomVisibilityOnDirectoryJob::Private { - public: +public: QString visibility; }; @@ -26,15 +26,14 @@ QUrl GetRoomVisibilityOnDirectoryJob::makeRequestUrl(QUrl baseUrl, } static const auto GetRoomVisibilityOnDirectoryJobName = - QStringLiteral("GetRoomVisibilityOnDirectoryJob"); + QStringLiteral("GetRoomVisibilityOnDirectoryJob"); GetRoomVisibilityOnDirectoryJob::GetRoomVisibilityOnDirectoryJob( - const QString& roomId) + const QString& roomId) : BaseJob(HttpVerb::Get, GetRoomVisibilityOnDirectoryJobName, - basePath % "/directory/list/room/" % roomId, false), - d(new Private) -{ -} + basePath % "/directory/list/room/" % roomId, false) + , d(new Private) +{} GetRoomVisibilityOnDirectoryJob::~GetRoomVisibilityOnDirectoryJob() = default; @@ -48,14 +47,15 @@ GetRoomVisibilityOnDirectoryJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("visibility"_ls), d->visibility); + return Success; } static const auto SetRoomVisibilityOnDirectoryJobName = - QStringLiteral("SetRoomVisibilityOnDirectoryJob"); + QStringLiteral("SetRoomVisibilityOnDirectoryJob"); SetRoomVisibilityOnDirectoryJob::SetRoomVisibilityOnDirectoryJob( - const QString& roomId, const QString& visibility) + const QString& roomId, const QString& visibility) : BaseJob(HttpVerb::Put, SetRoomVisibilityOnDirectoryJobName, basePath % "/directory/list/room/" % roomId) { @@ -66,7 +66,7 @@ SetRoomVisibilityOnDirectoryJob::SetRoomVisibilityOnDirectoryJob( class GetPublicRoomsJob::Private { - public: +public: PublicRoomsResponse data; }; @@ -84,8 +84,7 @@ QUrl GetPublicRoomsJob::makeRequestUrl(QUrl baseUrl, Omittable<int> limit, const QString& since, const QString& server) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/publicRooms", + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/publicRooms", queryToGetPublicRooms(limit, since, server)); } @@ -94,10 +93,9 @@ static const auto GetPublicRoomsJobName = QStringLiteral("GetPublicRoomsJob"); GetPublicRoomsJob::GetPublicRoomsJob(Omittable<int> limit, const QString& since, const QString& server) : BaseJob(HttpVerb::Get, GetPublicRoomsJobName, basePath % "/publicRooms", - queryToGetPublicRooms(limit, since, server), {}, false), - d(new Private) -{ -} + queryToGetPublicRooms(limit, since, server), {}, false) + , d(new Private) +{} GetPublicRoomsJob::~GetPublicRoomsJob() = default; @@ -109,22 +107,25 @@ BaseJob::Status GetPublicRoomsJob::parseJson(const QJsonDocument& data) return Success; } -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<QueryPublicRoomsJob::Filter> { - static void dumpTo(QJsonObject& jo, - const QueryPublicRoomsJob::Filter& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("generic_search_term"), - pod.genericSearchTerm); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<QueryPublicRoomsJob::Filter> +{ + static void dumpTo(QJsonObject& jo, const QueryPublicRoomsJob::Filter& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("generic_search_term"), + pod.genericSearchTerm); + } +}; + } // namespace QMatrixClient class QueryPublicRoomsJob::Private { - public: +public: PublicRoomsResponse data; }; @@ -136,7 +137,7 @@ BaseJob::Query queryToQueryPublicRooms(const QString& server) } static const auto QueryPublicRoomsJobName = - QStringLiteral("QueryPublicRoomsJob"); + QStringLiteral("QueryPublicRoomsJob"); QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, Omittable<int> limit, @@ -145,8 +146,8 @@ QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, Omittable<bool> includeAllNetworks, const QString& thirdPartyInstanceId) : BaseJob(HttpVerb::Post, QueryPublicRoomsJobName, - basePath % "/publicRooms", queryToQueryPublicRooms(server)), - d(new Private) + basePath % "/publicRooms", queryToQueryPublicRooms(server)) + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("limit"), limit); diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h index 1a0af880..da68416d 100644 --- a/lib/csapi/list_public_rooms.h +++ b/lib/csapi/list_public_rooms.h @@ -4,180 +4,189 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/public_rooms_response.h" -namespace QMatrixClient { - // Operations +#include "jobs/basejob.h" - /// Gets the visibility of a room in the directory - /// - /// Gets the visibility of a given room on the server's public room - /// directory. - class GetRoomVisibilityOnDirectoryJob : public BaseJob - { - public: - /*! Gets the visibility of a room in the directory - * \param roomId - * The room ID. - */ - explicit GetRoomVisibilityOnDirectoryJob(const QString& roomId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomVisibilityOnDirectoryJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - - ~GetRoomVisibilityOnDirectoryJob() override; - - // Result properties - - /// The visibility of the room in the directory. - const QString& visibility() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ - /// Sets the visibility of a room in the room directory - /// - /// Sets the visibility of a given room in the server's public room - /// directory. - /// - /// Servers may choose to implement additional access control checks - /// here, for instance that room visibility can only be changed by - /// the room creator or a server administrator. - class SetRoomVisibilityOnDirectoryJob : public BaseJob - { - public: - /*! Sets the visibility of a room in the room directory - * \param roomId - * The room ID. - * \param visibility - * The new visibility setting for the room. - * Defaults to 'public'. - */ - explicit SetRoomVisibilityOnDirectoryJob( - const QString& roomId, const QString& visibility = {}); - }; +// Operations - /// Lists the public rooms on the server. - /// - /// Lists the public rooms on the server. - /// - /// This API returns paginated responses. The rooms are ordered by the - /// number of joined members, with the largest rooms first. - class GetPublicRoomsJob : public BaseJob - { - public: - /*! Lists the public rooms on the server. - * \param limit - * Limit the number of results returned. - * \param since - * A pagination token from a previous request, allowing clients to - * get the next (or previous) batch of rooms. - * The direction of pagination is specified solely by which token - * is supplied, rather than via an explicit flag. - * \param server - * The server to fetch the public room lists from. Defaults to the - * local server. - */ - explicit GetPublicRoomsJob(Omittable<int> limit = none, - const QString& since = {}, - const QString& server = {}); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPublicRoomsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, Omittable<int> limit = none, - const QString& since = {}, - const QString& server = {}); - - ~GetPublicRoomsJob() override; - - // Result properties - - /// A list of the rooms on the server. - const PublicRoomsResponse& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +/// Gets the visibility of a room in the directory +/*! + * Gets the visibility of a given room on the server's public room directory. + */ +class GetRoomVisibilityOnDirectoryJob : public BaseJob +{ +public: + /*! Gets the visibility of a room in the directory + * \param roomId + * The room ID. + */ + explicit GetRoomVisibilityOnDirectoryJob(const QString& roomId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomVisibilityOnDirectoryJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); + + ~GetRoomVisibilityOnDirectoryJob() override; + + // Result properties + + /// The visibility of the room in the directory. + const QString& visibility() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Sets the visibility of a room in the room directory +/*! + * Sets the visibility of a given room in the server's public room + * directory. + * + * Servers may choose to implement additional access control checks + * here, for instance that room visibility can only be changed by + * the room creator or a server administrator. + */ +class SetRoomVisibilityOnDirectoryJob : public BaseJob +{ +public: + /*! Sets the visibility of a room in the room directory + * \param roomId + * The room ID. + * \param visibility + * The new visibility setting for the room. + * Defaults to 'public'. + */ + explicit SetRoomVisibilityOnDirectoryJob(const QString& roomId, + const QString& visibility = {}); +}; + +/// Lists the public rooms on the server. +/*! + * Lists the public rooms on the server. + * + * This API returns paginated responses. The rooms are ordered by the number + * of joined members, with the largest rooms first. + */ +class GetPublicRoomsJob : public BaseJob +{ +public: + /*! Lists the public rooms on the server. + * \param limit + * Limit the number of results returned. + * \param since + * A pagination token from a previous request, allowing clients to + * get the next (or previous) batch of rooms. + * The direction of pagination is specified solely by which token + * is supplied, rather than via an explicit flag. + * \param server + * The server to fetch the public room lists from. Defaults to the + * local server. + */ + explicit GetPublicRoomsJob(Omittable<int> limit = none, + const QString& since = {}, + const QString& server = {}); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPublicRoomsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, Omittable<int> limit = none, + const QString& since = {}, + const QString& server = {}); + + ~GetPublicRoomsJob() override; + + // Result properties + + /// A list of the rooms on the server. + const PublicRoomsResponse& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Lists the public rooms on the server with optional filter. +/*! + * Lists the public rooms on the server, with optional filter. + * + * This API returns paginated responses. The rooms are ordered by the number + * of joined members, with the largest rooms first. + */ +class QueryPublicRoomsJob : public BaseJob +{ +public: + // Inner data structures - /// Lists the public rooms on the server with optional filter. - /// - /// Lists the public rooms on the server, with optional filter. - /// - /// This API returns paginated responses. The rooms are ordered by the - /// number of joined members, with the largest rooms first. - class QueryPublicRoomsJob : public BaseJob + /// Filter to apply to the results. + struct Filter { - public: - // Inner data structures - - /// Filter to apply to the results. - struct Filter { - /// A string to search for in the room metadata, e.g. name, - /// topic, canonical alias etc. (Optional). - QString genericSearchTerm; - }; - - // Construction/destruction - - /*! Lists the public rooms on the server with optional filter. - * \param server - * The server to fetch the public room lists from. Defaults to the - * local server. - * \param limit - * Limit the number of results returned. - * \param since - * A pagination token from a previous request, allowing clients - * to get the next (or previous) batch of rooms. The direction - * of pagination is specified solely by which token is supplied, - * rather than via an explicit flag. - * \param filter - * Filter to apply to the results. - * \param includeAllNetworks - * Whether or not to include all known networks/protocols from - * application services on the homeserver. Defaults to false. - * \param thirdPartyInstanceId - * The specific third party network/protocol to request from the - * homeserver. Can only be used if ``include_all_networks`` is false. - */ - explicit QueryPublicRoomsJob(const QString& server = {}, - Omittable<int> limit = none, - const QString& since = {}, - const Omittable<Filter>& filter = none, - Omittable<bool> includeAllNetworks = none, - const QString& thirdPartyInstanceId = {}); - ~QueryPublicRoomsJob() override; - - // Result properties - - /// A list of the rooms on the server. - const PublicRoomsResponse& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// A string to search for in the room metadata, e.g. name,topic, + /// canonical alias etc. (Optional). + QString genericSearchTerm; }; + + // Construction/destruction + + /*! Lists the public rooms on the server with optional filter. + * \param server + * The server to fetch the public room lists from. Defaults to the + * local server. + * \param limit + * Limit the number of results returned. + * \param since + * A pagination token from a previous request, allowing clients + * to get the next (or previous) batch of rooms. The direction + * of pagination is specified solely by which token is supplied, + * rather than via an explicit flag. + * \param filter + * Filter to apply to the results. + * \param includeAllNetworks + * Whether or not to include all known networks/protocols from + * application services on the homeserver. Defaults to false. + * \param thirdPartyInstanceId + * The specific third party network/protocol to request from the + * homeserver. Can only be used if ``include_all_networks`` is false. + */ + explicit QueryPublicRoomsJob(const QString& server = {}, + Omittable<int> limit = none, + const QString& since = {}, + const Omittable<Filter>& filter = none, + Omittable<bool> includeAllNetworks = none, + const QString& thirdPartyInstanceId = {}); + + ~QueryPublicRoomsJob() override; + + // Result properties + + /// A list of the rooms on the server. + const PublicRoomsResponse& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/login.cpp b/lib/csapi/login.cpp index 77652e64..29ee4ab5 100644 --- a/lib/csapi/login.cpp +++ b/lib/csapi/login.cpp @@ -12,21 +12,25 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<GetLoginFlowsJob::LoginFlow> { - static void fillFrom(const QJsonObject& jo, - GetLoginFlowsJob::LoginFlow& result) - { - fromJson(jo.value("type"_ls), result.type); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetLoginFlowsJob::LoginFlow> +{ + static void fillFrom(const QJsonObject& jo, + GetLoginFlowsJob::LoginFlow& result) + { + fromJson(jo.value("type"_ls), result.type); + } +}; + } // namespace QMatrixClient class GetLoginFlowsJob::Private { - public: +public: QVector<LoginFlow> flows; }; @@ -38,10 +42,9 @@ QUrl GetLoginFlowsJob::makeRequestUrl(QUrl baseUrl) static const auto GetLoginFlowsJobName = QStringLiteral("GetLoginFlowsJob"); GetLoginFlowsJob::GetLoginFlowsJob() - : BaseJob(HttpVerb::Get, GetLoginFlowsJobName, basePath % "/login", false), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetLoginFlowsJobName, basePath % "/login", false) + , d(new Private) +{} GetLoginFlowsJob::~GetLoginFlowsJob() = default; @@ -54,12 +57,13 @@ BaseJob::Status GetLoginFlowsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("flows"_ls), d->flows); + return Success; } class LoginJob::Private { - public: +public: QString userId; QString accessToken; QString homeServer; @@ -75,8 +79,8 @@ LoginJob::LoginJob(const QString& type, const QString& deviceId, const QString& initialDeviceDisplayName, const QString& user, const QString& medium, const QString& address) - : BaseJob(HttpVerb::Post, LoginJobName, basePath % "/login", false), - d(new Private) + : BaseJob(HttpVerb::Post, LoginJobName, basePath % "/login", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("type"), type); @@ -115,5 +119,6 @@ BaseJob::Status LoginJob::parseJson(const QJsonDocument& data) fromJson(json.value("home_server"_ls), d->homeServer); fromJson(json.value("device_id"_ls), d->deviceId); fromJson(json.value("well_known"_ls), d->wellKnown); + return Success; } diff --git a/lib/csapi/login.h b/lib/csapi/login.h index 89c8ca79..3ab0648f 100644 --- a/lib/csapi/login.h +++ b/lib/csapi/login.h @@ -4,140 +4,143 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/user_identifier.h" #include "csapi/definitions/wellknown/full.h" -#include <QtCore/QVector> - -namespace QMatrixClient { - // Operations - - /// Get the supported login types to authenticate users - /// - /// Gets the homeserver's supported login types to authenticate users. - /// Clients should pick one of these and supply it as the ``type`` when - /// logging in. - class GetLoginFlowsJob : public BaseJob - { - public: - // Inner data structures - - /// Gets the homeserver's supported login types to authenticate users. - /// Clients should pick one of these and supply it as the ``type`` when - /// logging in. - struct LoginFlow { - /// The login type. This is supplied as the ``type`` when - /// logging in. - QString type; - }; - - // Construction/destruction - - explicit GetLoginFlowsJob(); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetLoginFlowsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); +#include "jobs/basejob.h" - ~GetLoginFlowsJob() override; +#include <QtCore/QVector> - // Result properties +namespace QMatrixClient +{ - /// The homeserver's supported login types - const QVector<LoginFlow>& flows() const; +// Operations - protected: - Status parseJson(const QJsonDocument& data) override; +/// Get the supported login types to authenticate users +/*! + * Gets the homeserver's supported login types to authenticate users. Clients + * should pick one of these and supply it as the ``type`` when logging in. + */ +class GetLoginFlowsJob : public BaseJob +{ +public: + // Inner data structures - private: - class Private; - QScopedPointer<Private> d; + /// Gets the homeserver's supported login types to authenticate users. + /// Clientsshould pick one of these and supply it as the ``type`` when + /// logging in. + struct LoginFlow + { + /// The login type. This is supplied as the ``type`` whenlogging in. + QString type; }; - /// Authenticates the user. - /// - /// Authenticates the user, and issues an access token they can - /// use to authorize themself in subsequent requests. - /// - /// If the client does not supply a ``device_id``, the server must - /// auto-generate one. + // Construction/destruction + + explicit GetLoginFlowsJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetLoginFlowsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetLoginFlowsJob() override; + + // Result properties + + /// The homeserver's supported login types + const QVector<LoginFlow>& flows() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Authenticates the user. +/*! + * Authenticates the user, and issues an access token they can + * use to authorize themself in subsequent requests. + * + * If the client does not supply a ``device_id``, the server must + * auto-generate one. + * + * The returned access token must be associated with the ``device_id`` + * supplied by the client or generated by the server. The server may + * invalidate any access token previously associated with that device. See + * `Relationship between access tokens and devices`_. + */ +class LoginJob : public BaseJob +{ +public: + /*! Authenticates the user. + * \param type + * The login type being used. + * \param identifier + * Identification information for the user. + * \param password + * Required when ``type`` is ``m.login.password``. The user's + * password. + * \param token + * Required when ``type`` is ``m.login.token``. Part of `Token-based`_ + * login. \param deviceId ID of the client device. If this does not + * correspond to a known client device, a new device will be created. The + * server will auto-generate a device_id if this is not specified. \param + * initialDeviceDisplayName A display name to assign to the newly-created + * device. Ignored if ``device_id`` corresponds to a known device. \param + * user The fully qualified user ID or just local part of the user ID, to + * log in. Deprecated in favour of ``identifier``. \param medium When + * logging in using a third party identifier, the medium of the identifier. + * Must be 'email'. Deprecated in favour of ``identifier``. \param address + * Third party identifier for the user. Deprecated in favour of + * ``identifier``. + */ + explicit LoginJob(const QString& type, + const Omittable<UserIdentifier>& identifier = none, + const QString& password = {}, const QString& token = {}, + const QString& deviceId = {}, + const QString& initialDeviceDisplayName = {}, + const QString& user = {}, const QString& medium = {}, + const QString& address = {}); + + ~LoginJob() override; + + // Result properties + + /// The fully-qualified Matrix ID that has been registered. + const QString& userId() const; + /// An access token for the account. + /// This access token can then be used to authorize other requests. + const QString& accessToken() const; + /// The server_name of the homeserver on which the account has + /// been registered. /// - /// The returned access token must be associated with the ``device_id`` - /// supplied by the client or generated by the server. The server may - /// invalidate any access token previously associated with that device. See - /// `Relationship between access tokens and devices`_. - class LoginJob : public BaseJob - { - public: - /*! Authenticates the user. - * \param type - * The login type being used. - * \param identifier - * Identification information for the user. - * \param password - * Required when ``type`` is ``m.login.password``. The user's - * password. - * \param token - * Required when ``type`` is ``m.login.token``. Part of `Token-based`_ - * login. \param deviceId ID of the client device. If this does not - * correspond to a known client device, a new device will be created. - * The server will auto-generate a device_id if this is not specified. - * \param initialDeviceDisplayName - * A display name to assign to the newly-created device. Ignored - * if ``device_id`` corresponds to a known device. - * \param user - * The fully qualified user ID or just local part of the user ID, to - * log in. Deprecated in favour of ``identifier``. \param medium When - * logging in using a third party identifier, the medium of the - * identifier. Must be 'email'. Deprecated in favour of ``identifier``. - * \param address - * Third party identifier for the user. Deprecated in favour of - * ``identifier``. - */ - explicit LoginJob(const QString& type, - const Omittable<UserIdentifier>& identifier = none, - const QString& password = {}, - const QString& token = {}, - const QString& deviceId = {}, - const QString& initialDeviceDisplayName = {}, - const QString& user = {}, const QString& medium = {}, - const QString& address = {}); - ~LoginJob() override; - - // Result properties - - /// The fully-qualified Matrix ID that has been registered. - const QString& userId() const; - /// An access token for the account. - /// This access token can then be used to authorize other requests. - const QString& accessToken() const; - /// The server_name of the homeserver on which the account has - /// been registered. - /// - /// **Deprecated**. Clients should extract the server_name from - /// ``user_id`` (by splitting at the first colon) if they require - /// it. Note also that ``homeserver`` is not spelt this way. - const QString& homeServer() const; - /// ID of the logged-in device. Will be the same as the - /// corresponding parameter in the request, if one was specified. - const QString& deviceId() const; - /// Optional client configuration provided by the server. If present, - /// clients SHOULD use the provided object to reconfigure themselves, - /// optionally validating the URLs within. This object takes the same - /// form as the one returned from .well-known autodiscovery. - const Omittable<DiscoveryInformation>& wellKnown() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + /// **Deprecated**. Clients should extract the server_name from + /// ``user_id`` (by splitting at the first colon) if they require + /// it. Note also that ``homeserver`` is not spelt this way. + const QString& homeServer() const; + /// ID of the logged-in device. Will be the same as the + /// corresponding parameter in the request, if one was specified. + const QString& deviceId() const; + /// Optional client configuration provided by the server. If present, + /// clients SHOULD use the provided object to reconfigure themselves, + /// optionally validating the URLs within. This object takes the same + /// form as the one returned from .well-known autodiscovery. + const Omittable<DiscoveryInformation>& wellKnown() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/logout.cpp b/lib/csapi/logout.cpp index f2d2e130..d0bef20e 100644 --- a/lib/csapi/logout.cpp +++ b/lib/csapi/logout.cpp @@ -21,18 +21,15 @@ static const auto LogoutJobName = QStringLiteral("LogoutJob"); LogoutJob::LogoutJob() : BaseJob(HttpVerb::Post, LogoutJobName, basePath % "/logout") -{ -} +{} QUrl LogoutAllJob::makeRequestUrl(QUrl baseUrl) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/logout/all"); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/logout/all"); } static const auto LogoutAllJobName = QStringLiteral("LogoutAllJob"); LogoutAllJob::LogoutAllJob() : BaseJob(HttpVerb::Post, LogoutAllJobName, basePath % "/logout/all") -{ -} +{} diff --git a/lib/csapi/logout.h b/lib/csapi/logout.h index 4bbb8526..c03af180 100644 --- a/lib/csapi/logout.h +++ b/lib/csapi/logout.h @@ -6,50 +6,53 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Invalidates a user access token - /// - /// Invalidates an existing access token, so that it can no longer be used - /// for authorization. - class LogoutJob : public BaseJob - { - public: - explicit LogoutJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * LogoutJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - }; - - /// Invalidates all access tokens for a user - /// - /// Invalidates all access tokens for a user, so that they can no longer be - /// used for authorization. This includes the access token that made this - /// request. - /// - /// This endpoint does not require UI authorization because UI authorization - /// is designed to protect against attacks where the someone gets hold of a - /// single access token then takes over the account. This endpoint - /// invalidates all access tokens for the user, including the token used in - /// the request, and therefore the attacker is unable to take over the - /// account in this way. - class LogoutAllJob : public BaseJob - { - public: - explicit LogoutAllJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * LogoutAllJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - }; +namespace QMatrixClient +{ + +// Operations + +/// Invalidates a user access token +/*! + * Invalidates an existing access token, so that it can no longer be used for + * authorization. + */ +class LogoutJob : public BaseJob +{ +public: + explicit LogoutJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * LogoutJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); +}; + +/// Invalidates all access tokens for a user +/*! + * Invalidates all access tokens for a user, so that they can no longer be used + * for authorization. This includes the access token that made this request. + * + * This endpoint does not require UI authorization because UI authorization is + * designed to protect against attacks where the someone gets hold of a single + * access token then takes over the account. This endpoint invalidates all + * access tokens for the user, including the token used in the request, and + * therefore the attacker is unable to take over the account in this way. + */ +class LogoutAllJob : public BaseJob +{ +public: + explicit LogoutAllJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * LogoutAllJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/message_pagination.cpp b/lib/csapi/message_pagination.cpp index 272c1c20..3f09bd85 100644 --- a/lib/csapi/message_pagination.cpp +++ b/lib/csapi/message_pagination.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetRoomEventsJob::Private { - public: +public: QString begin; QString end; RoomEvents chunk; @@ -39,8 +39,8 @@ QUrl GetRoomEventsJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& filter) { return BaseJob::makeRequestUrl( - std::move(baseUrl), basePath % "/rooms/" % roomId % "/messages", - queryToGetRoomEvents(from, to, dir, limit, filter)); + std::move(baseUrl), basePath % "/rooms/" % roomId % "/messages", + queryToGetRoomEvents(from, to, dir, limit, filter)); } static const auto GetRoomEventsJobName = QStringLiteral("GetRoomEventsJob"); @@ -50,10 +50,9 @@ GetRoomEventsJob::GetRoomEventsJob(const QString& roomId, const QString& from, Omittable<int> limit, const QString& filter) : BaseJob(HttpVerb::Get, GetRoomEventsJobName, basePath % "/rooms/" % roomId % "/messages", - queryToGetRoomEvents(from, to, dir, limit, filter)), - d(new Private) -{ -} + queryToGetRoomEvents(from, to, dir, limit, filter)) + , d(new Private) +{} GetRoomEventsJob::~GetRoomEventsJob() = default; @@ -69,5 +68,6 @@ BaseJob::Status GetRoomEventsJob::parseJson(const QJsonDocument& data) fromJson(json.value("start"_ls), d->begin); fromJson(json.value("end"_ls), d->end); fromJson(json.value("chunk"_ls), d->chunk); + return Success; } diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h index 8ce1962e..03b3d42a 100644 --- a/lib/csapi/message_pagination.h +++ b/lib/csapi/message_pagination.h @@ -4,71 +4,80 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "events/eventloader.h" +#include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Get a list of events for this room +/*! + * This API returns a list of message and state events for a room. It uses + * pagination query parameters to paginate history in the room. + */ +class GetRoomEventsJob : public BaseJob +{ +public: + /*! Get a list of events for this room + * \param roomId + * The room to get events from. + * \param from + * The token to start returning events from. This token can be obtained + * from a ``prev_batch`` token returned for each room by the sync API, + * or from a ``start`` or ``end`` token returned by a previous request + * to this endpoint. + * \param dir + * The direction to return events from. + * \param to + * The token to stop returning events at. This token can be obtained from + * a ``prev_batch`` token returned for each room by the sync endpoint, + * or from a ``start`` or ``end`` token returned by a previous request to + * this endpoint. + * \param limit + * The maximum number of events to return. Default: 10. + * \param filter + * A JSON RoomEventFilter to filter returned events with. + */ + explicit GetRoomEventsJob(const QString& roomId, const QString& from, + const QString& dir, const QString& to = {}, + Omittable<int> limit = none, + const QString& filter = {}); - /// Get a list of events for this room - /// - /// This API returns a list of message and state events for a room. It uses - /// pagination query parameters to paginate history in the room. - class GetRoomEventsJob : public BaseJob - { - public: - /*! Get a list of events for this room - * \param roomId - * The room to get events from. - * \param from - * The token to start returning events from. This token can be - * obtained from a ``prev_batch`` token returned for each room by the - * sync API, or from a ``start`` or ``end`` token returned by a previous - * request to this endpoint. \param dir The direction to return events - * from. \param to The token to stop returning events at. This token can - * be obtained from a ``prev_batch`` token returned for each room by the - * sync endpoint, or from a ``start`` or ``end`` token returned by a - * previous request to this endpoint. \param limit The maximum number of - * events to return. Default: 10. \param filter A JSON RoomEventFilter - * to filter returned events with. - */ - explicit GetRoomEventsJob(const QString& roomId, const QString& from, - const QString& dir, const QString& to = {}, - Omittable<int> limit = none, - const QString& filter = {}); + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomEventsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& from, const QString& dir, + const QString& to = {}, + Omittable<int> limit = none, + const QString& filter = {}); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomEventsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& from, const QString& dir, - const QString& to = {}, - Omittable<int> limit = none, - const QString& filter = {}); + ~GetRoomEventsJob() override; - ~GetRoomEventsJob() override; + // Result properties - // Result properties + /// The token the pagination starts from. If ``dir=b`` this will be + /// the token supplied in ``from``. + const QString& begin() const; + /// The token the pagination ends at. If ``dir=b`` this token should + /// be used again to request even earlier events. + const QString& end() const; + /// A list of room events. + RoomEvents&& chunk(); - /// The token the pagination starts from. If ``dir=b`` this will be - /// the token supplied in ``from``. - const QString& begin() const; - /// The token the pagination ends at. If ``dir=b`` this token should - /// be used again to request even earlier events. - const QString& end() const; - /// A list of room events. - RoomEvents&& chunk(); +protected: + Status parseJson(const QJsonDocument& data) override; - protected: - Status parseJson(const QJsonDocument& data) override; +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/notifications.cpp b/lib/csapi/notifications.cpp index b7f252ac..3a05a0b2 100644 --- a/lib/csapi/notifications.cpp +++ b/lib/csapi/notifications.cpp @@ -12,33 +12,36 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<GetNotificationsJob::Notification> { - static void fillFrom(const QJsonObject& jo, - GetNotificationsJob::Notification& result) - { - fromJson(jo.value("actions"_ls), result.actions); - fromJson(jo.value("event"_ls), result.event); - fromJson(jo.value("profile_tag"_ls), result.profileTag); - fromJson(jo.value("read"_ls), result.read); - fromJson(jo.value("room_id"_ls), result.roomId); - fromJson(jo.value("ts"_ls), result.ts); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetNotificationsJob::Notification> +{ + static void fillFrom(const QJsonObject& jo, + GetNotificationsJob::Notification& result) + { + fromJson(jo.value("actions"_ls), result.actions); + fromJson(jo.value("event"_ls), result.event); + fromJson(jo.value("profile_tag"_ls), result.profileTag); + fromJson(jo.value("read"_ls), result.read); + fromJson(jo.value("room_id"_ls), result.roomId); + fromJson(jo.value("ts"_ls), result.ts); + } +}; + } // namespace QMatrixClient class GetNotificationsJob::Private { - public: +public: QString nextToken; std::vector<Notification> notifications; }; BaseJob::Query queryToGetNotifications(const QString& from, - Omittable<int> limit, - const QString& only) + Omittable<int> limit, const QString& only) { BaseJob::Query _q; addParam<IfNotEmpty>(_q, QStringLiteral("from"), from); @@ -57,17 +60,16 @@ QUrl GetNotificationsJob::makeRequestUrl(QUrl baseUrl, const QString& from, } static const auto GetNotificationsJobName = - QStringLiteral("GetNotificationsJob"); + QStringLiteral("GetNotificationsJob"); GetNotificationsJob::GetNotificationsJob(const QString& from, Omittable<int> limit, const QString& only) : BaseJob(HttpVerb::Get, GetNotificationsJobName, basePath % "/notifications", - queryToGetNotifications(from, limit, only)), - d(new Private) -{ -} + queryToGetNotifications(from, limit, only)) + , d(new Private) +{} GetNotificationsJob::~GetNotificationsJob() = default; @@ -84,8 +86,9 @@ BaseJob::Status GetNotificationsJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("next_token"_ls), d->nextToken); if (!json.contains("notifications"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'notifications' not found in the response" }; fromJson(json.value("notifications"_ls), d->notifications); + return Success; } diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h index 49bc33e9..4170d539 100644 --- a/lib/csapi/notifications.h +++ b/lib/csapi/notifications.h @@ -4,88 +4,94 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "events/eventloader.h" +#include "jobs/basejob.h" + #include <QtCore/QJsonObject> #include <QtCore/QVariant> #include <QtCore/QVector> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Gets a list of events that the user has been notified about - /// - /// This API is used to paginate through the list of events that the - /// user has been, or would have been notified about. - class GetNotificationsJob : public BaseJob +// Operations + +/// Gets a list of events that the user has been notified about +/*! + * This API is used to paginate through the list of events that the + * user has been, or would have been notified about. + */ +class GetNotificationsJob : public BaseJob +{ +public: + // Inner data structures + + /// This API is used to paginate through the list of events that theuser has + /// been, or would have been notified about. + struct Notification { - public: - // Inner data structures - - /// This API is used to paginate through the list of events that the - /// user has been, or would have been notified about. - struct Notification { - /// The action(s) to perform when the conditions for this rule are - /// met. See `Push Rules: API`_. - QVector<QVariant> actions; - /// The Event object for the event that triggered the notification. - EventPtr event; - /// The profile tag of the rule that matched this event. - QString profileTag; - /// Indicates whether the user has sent a read receipt indicating - /// that they have read this message. - bool read; - /// The ID of the room in which the event was posted. - QString roomId; - /// The unix timestamp at which the event notification was sent, - /// in milliseconds. - int ts; - }; - - // Construction/destruction - - /*! Gets a list of events that the user has been notified about - * \param from - * Pagination token given to retrieve the next set of events. - * \param limit - * Limit on the number of events to return in this request. - * \param only - * Allows basic filtering of events returned. Supply ``highlight`` - * to return only events where the notification had the highlight - * tweak set. - */ - explicit GetNotificationsJob(const QString& from = {}, - Omittable<int> limit = none, - const QString& only = {}); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetNotificationsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, - Omittable<int> limit = none, - const QString& only = {}); - - ~GetNotificationsJob() override; - - // Result properties - - /// The token to supply in the ``from`` param of the next - /// ``/notifications`` request in order to request more - /// events. If this is absent, there are no more results. - const QString& nextToken() const; - /// The list of events that triggered notifications. - std::vector<Notification>&& notifications(); - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The action(s) to perform when the conditions for this rule are + /// met.See `Push Rules: API`_. + QVector<QVariant> actions; + /// The Event object for the event that triggered the notification. + EventPtr event; + /// The profile tag of the rule that matched this event. + QString profileTag; + /// Indicates whether the user has sent a read receipt indicatingthat + /// they have read this message. + bool read; + /// The ID of the room in which the event was posted. + QString roomId; + /// The unix timestamp at which the event notification was sent,in + /// milliseconds. + int ts; }; + + // Construction/destruction + + /*! Gets a list of events that the user has been notified about + * \param from + * Pagination token given to retrieve the next set of events. + * \param limit + * Limit on the number of events to return in this request. + * \param only + * Allows basic filtering of events returned. Supply ``highlight`` + * to return only events where the notification had the highlight + * tweak set. + */ + explicit GetNotificationsJob(const QString& from = {}, + Omittable<int> limit = none, + const QString& only = {}); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetNotificationsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, + Omittable<int> limit = none, + const QString& only = {}); + + ~GetNotificationsJob() override; + + // Result properties + + /// The token to supply in the ``from`` param of the next + /// ``/notifications`` request in order to request more + /// events. If this is absent, there are no more results. + const QString& nextToken() const; + /// The list of events that triggered notifications. + std::vector<Notification>&& notifications(); + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/openid.cpp b/lib/csapi/openid.cpp index 82a3b055..39ba3b9e 100644 --- a/lib/csapi/openid.cpp +++ b/lib/csapi/openid.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class RequestOpenIdTokenJob::Private { - public: +public: QString accessToken; QString tokenType; QString matrixServerName; @@ -22,13 +22,13 @@ class RequestOpenIdTokenJob::Private }; static const auto RequestOpenIdTokenJobName = - QStringLiteral("RequestOpenIdTokenJob"); + QStringLiteral("RequestOpenIdTokenJob"); RequestOpenIdTokenJob::RequestOpenIdTokenJob(const QString& userId, const QJsonObject& body) : BaseJob(HttpVerb::Post, RequestOpenIdTokenJobName, - basePath % "/user/" % userId % "/openid/request_token"), - d(new Private) + basePath % "/user/" % userId % "/openid/request_token") + , d(new Private) { setRequestData(Data(toJson(body))); } @@ -53,20 +53,21 @@ BaseJob::Status RequestOpenIdTokenJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("access_token"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'access_token' not found in the response" }; fromJson(json.value("access_token"_ls), d->accessToken); if (!json.contains("token_type"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'token_type' not found in the response" }; fromJson(json.value("token_type"_ls), d->tokenType); if (!json.contains("matrix_server_name"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'matrix_server_name' not found in the response" }; fromJson(json.value("matrix_server_name"_ls), d->matrixServerName); if (!json.contains("expires_in"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'expires_in' not found in the response" }; fromJson(json.value("expires_in"_ls), d->expiresIn); + return Success; } diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h index 6e3c744d..40f1bc40 100644 --- a/lib/csapi/openid.h +++ b/lib/csapi/openid.h @@ -4,58 +4,64 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Get an OpenID token object to verify the requester's identity. - /// - /// Gets an OpenID token object that the requester may supply to another - /// service to verify their identity in Matrix. The generated token is only - /// valid for exchanging for user information from the federation API for - /// OpenID. - /// - /// The access token generated is only valid for the OpenID API. It cannot - /// be used to request another OpenID access token or call ``/sync``, for - /// example. - class RequestOpenIdTokenJob : public BaseJob - { - public: - /*! Get an OpenID token object to verify the requester's identity. - * \param userId - * The user to request and OpenID token for. Should be the user who - * is authenticated for the request. - * \param body - * An empty object. Reserved for future expansion. - */ - explicit RequestOpenIdTokenJob(const QString& userId, - const QJsonObject& body = {}); - ~RequestOpenIdTokenJob() override; - - // Result properties - - /// An access token the consumer may use to verify the identity of - /// the person who generated the token. This is given to the federation - /// API ``GET /openid/userinfo``. - const QString& accessToken() const; - /// The string ``Bearer``. - const QString& tokenType() const; - /// The homeserver domain the consumer should use when attempting to - /// verify the user's identity. - const QString& matrixServerName() const; - /// The number of seconds before this token expires and a new one must - /// be generated. - int expiresIn() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Get an OpenID token object to verify the requester's identity. +/*! + * Gets an OpenID token object that the requester may supply to another + * service to verify their identity in Matrix. The generated token is only + * valid for exchanging for user information from the federation API for + * OpenID. + * + * The access token generated is only valid for the OpenID API. It cannot + * be used to request another OpenID access token or call ``/sync``, for + * example. + */ +class RequestOpenIdTokenJob : public BaseJob +{ +public: + /*! Get an OpenID token object to verify the requester's identity. + * \param userId + * The user to request and OpenID token for. Should be the user who + * is authenticated for the request. + * \param body + * An empty object. Reserved for future expansion. + */ + explicit RequestOpenIdTokenJob(const QString& userId, + const QJsonObject& body = {}); + + ~RequestOpenIdTokenJob() override; + + // Result properties + + /// An access token the consumer may use to verify the identity of + /// the person who generated the token. This is given to the federation + /// API ``GET /openid/userinfo``. + const QString& accessToken() const; + /// The string ``Bearer``. + const QString& tokenType() const; + /// The homeserver domain the consumer should use when attempting to + /// verify the user's identity. + const QString& matrixServerName() const; + /// The number of seconds before this token expires and a new one must + /// be generated. + int expiresIn() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/peeking_events.cpp b/lib/csapi/peeking_events.cpp index d48eca3f..6962d363 100644 --- a/lib/csapi/peeking_events.cpp +++ b/lib/csapi/peeking_events.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class PeekEventsJob::Private { - public: +public: QString begin; QString end; RoomEvents chunk; @@ -31,8 +31,7 @@ BaseJob::Query queryToPeekEvents(const QString& from, Omittable<int> timeout, } QUrl PeekEventsJob::makeRequestUrl(QUrl baseUrl, const QString& from, - Omittable<int> timeout, - const QString& roomId) + Omittable<int> timeout, const QString& roomId) { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/events", queryToPeekEvents(from, timeout, roomId)); @@ -43,10 +42,9 @@ static const auto PeekEventsJobName = QStringLiteral("PeekEventsJob"); PeekEventsJob::PeekEventsJob(const QString& from, Omittable<int> timeout, const QString& roomId) : BaseJob(HttpVerb::Get, PeekEventsJobName, basePath % "/events", - queryToPeekEvents(from, timeout, roomId)), - d(new Private) -{ -} + queryToPeekEvents(from, timeout, roomId)) + , d(new Private) +{} PeekEventsJob::~PeekEventsJob() = default; @@ -62,5 +60,6 @@ BaseJob::Status PeekEventsJob::parseJson(const QJsonDocument& data) fromJson(json.value("start"_ls), d->begin); fromJson(json.value("end"_ls), d->end); fromJson(json.value("chunk"_ls), d->chunk); + return Success; } diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h index 00552af0..1fe63c4d 100644 --- a/lib/csapi/peeking_events.h +++ b/lib/csapi/peeking_events.h @@ -4,70 +4,74 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "events/eventloader.h" +#include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Listen on the event stream. +/*! + * This will listen for new events related to a particular room and return + * them to the caller. This will block until an event is received, or until + * the ``timeout`` is reached. + * + * This API is the same as the normal ``/events`` endpoint, but can be + * called by users who have not joined the room. + * + * Note that the normal ``/events`` endpoint has been deprecated. This + * API will also be deprecated at some point, but its replacement is not + * yet known. + */ +class PeekEventsJob : public BaseJob +{ +public: + /*! Listen on the event stream. + * \param from + * The token to stream from. This token is either from a previous + * request to this API or from the initial sync API. + * \param timeout + * The maximum time in milliseconds to wait for an event. + * \param roomId + * The room ID for which events should be returned. + */ + explicit PeekEventsJob(const QString& from = {}, + Omittable<int> timeout = none, + const QString& roomId = {}); - /// Listen on the event stream. - /// - /// This will listen for new events related to a particular room and return - /// them to the caller. This will block until an event is received, or until - /// the ``timeout`` is reached. - /// - /// This API is the same as the normal ``/events`` endpoint, but can be - /// called by users who have not joined the room. - /// - /// Note that the normal ``/events`` endpoint has been deprecated. This - /// API will also be deprecated at some point, but its replacement is not - /// yet known. - class PeekEventsJob : public BaseJob - { - public: - /*! Listen on the event stream. - * \param from - * The token to stream from. This token is either from a previous - * request to this API or from the initial sync API. - * \param timeout - * The maximum time in milliseconds to wait for an event. - * \param roomId - * The room ID for which events should be returned. - */ - explicit PeekEventsJob(const QString& from = {}, + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * PeekEventsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, Omittable<int> timeout = none, const QString& roomId = {}); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * PeekEventsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& from = {}, - Omittable<int> timeout = none, - const QString& roomId = {}); + ~PeekEventsJob() override; - ~PeekEventsJob() override; + // Result properties - // Result properties + /// A token which correlates to the first value in ``chunk``. This + /// is usually the same token supplied to ``from=``. + const QString& begin() const; + /// A token which correlates to the last value in ``chunk``. This + /// token should be used in the next request to ``/events``. + const QString& end() const; + /// An array of events. + RoomEvents&& chunk(); - /// A token which correlates to the first value in ``chunk``. This - /// is usually the same token supplied to ``from=``. - const QString& begin() const; - /// A token which correlates to the last value in ``chunk``. This - /// token should be used in the next request to ``/events``. - const QString& end() const; - /// An array of events. - RoomEvents&& chunk(); +protected: + Status parseJson(const QJsonDocument& data) override; - protected: - Status parseJson(const QJsonDocument& data) override; +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/presence.cpp b/lib/csapi/presence.cpp index 4f9b9c78..b6e8caf2 100644 --- a/lib/csapi/presence.cpp +++ b/lib/csapi/presence.cpp @@ -27,7 +27,7 @@ SetPresenceJob::SetPresenceJob(const QString& userId, const QString& presence, class GetPresenceJob::Private { - public: +public: QString presence; Omittable<int> lastActiveAgo; QString statusMsg; @@ -36,18 +36,17 @@ class GetPresenceJob::Private QUrl GetPresenceJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { - return BaseJob::makeRequestUrl( - std::move(baseUrl), basePath % "/presence/" % userId % "/status"); + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/presence/" % userId % "/status"); } static const auto GetPresenceJobName = QStringLiteral("GetPresenceJob"); GetPresenceJob::GetPresenceJob(const QString& userId) : BaseJob(HttpVerb::Get, GetPresenceJobName, - basePath % "/presence/" % userId % "/status"), - d(new Private) -{ -} + basePath % "/presence/" % userId % "/status") + , d(new Private) +{} GetPresenceJob::~GetPresenceJob() = default; @@ -69,11 +68,12 @@ BaseJob::Status GetPresenceJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("presence"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'presence' not found in the response" }; fromJson(json.value("presence"_ls), d->presence); fromJson(json.value("last_active_ago"_ls), d->lastActiveAgo); fromJson(json.value("status_msg"_ls), d->statusMsg); fromJson(json.value("currently_active"_ls), d->currentlyActive); + return Success; } diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h index 881d9d5e..82c3a300 100644 --- a/lib/csapi/presence.h +++ b/lib/csapi/presence.h @@ -4,73 +4,78 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" +namespace QMatrixClient +{ -namespace QMatrixClient { - // Operations +// Operations - /// Update this user's presence state. - /// - /// This API sets the given user's presence state. When setting the status, - /// the activity time is updated to reflect that activity; the client does - /// not need to specify the ``last_active_ago`` field. You cannot set the - /// presence state of another user. - class SetPresenceJob : public BaseJob - { - public: - /*! Update this user's presence state. - * \param userId - * The user whose presence state to update. - * \param presence - * The new presence state. - * \param statusMsg - * The status message to attach to this state. - */ - explicit SetPresenceJob(const QString& userId, const QString& presence, - const QString& statusMsg = {}); - }; +/// Update this user's presence state. +/*! + * This API sets the given user's presence state. When setting the status, + * the activity time is updated to reflect that activity; the client does + * not need to specify the ``last_active_ago`` field. You cannot set the + * presence state of another user. + */ +class SetPresenceJob : public BaseJob +{ +public: + /*! Update this user's presence state. + * \param userId + * The user whose presence state to update. + * \param presence + * The new presence state. + * \param statusMsg + * The status message to attach to this state. + */ + explicit SetPresenceJob(const QString& userId, const QString& presence, + const QString& statusMsg = {}); +}; + +/// Get this user's presence state. +/*! + * Get the given user's presence state. + */ +class GetPresenceJob : public BaseJob +{ +public: + /*! Get this user's presence state. + * \param userId + * The user whose presence state to get. + */ + explicit GetPresenceJob(const QString& userId); - /// Get this user's presence state. - /// - /// Get the given user's presence state. - class GetPresenceJob : public BaseJob - { - public: - /*! Get this user's presence state. - * \param userId - * The user whose presence state to get. - */ - explicit GetPresenceJob(const QString& userId); + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPresenceJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPresenceJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); + ~GetPresenceJob() override; - ~GetPresenceJob() override; + // Result properties - // Result properties + /// This user's presence. + const QString& presence() const; + /// The length of time in milliseconds since an action was performed + /// by this user. + Omittable<int> lastActiveAgo() const; + /// The state message for this user if one was set. + const QString& statusMsg() const; + /// Whether the user is currently active + Omittable<bool> currentlyActive() const; - /// This user's presence. - const QString& presence() const; - /// The length of time in milliseconds since an action was performed - /// by this user. - Omittable<int> lastActiveAgo() const; - /// The state message for this user if one was set. - const QString& statusMsg() const; - /// Whether the user is currently active - Omittable<bool> currentlyActive() const; +protected: + Status parseJson(const QJsonDocument& data) override; - protected: - Status parseJson(const QJsonDocument& data) override; +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/profile.cpp b/lib/csapi/profile.cpp index fefc18f2..630452f6 100644 --- a/lib/csapi/profile.cpp +++ b/lib/csapi/profile.cpp @@ -26,25 +26,23 @@ SetDisplayNameJob::SetDisplayNameJob(const QString& userId, class GetDisplayNameJob::Private { - public: +public: QString displayname; }; QUrl GetDisplayNameJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/profile/" % userId - % "/displayname"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), basePath % "/profile/" % userId % "/displayname"); } static const auto GetDisplayNameJobName = QStringLiteral("GetDisplayNameJob"); GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, GetDisplayNameJobName, - basePath % "/profile/" % userId % "/displayname", false), - d(new Private) -{ -} + basePath % "/profile/" % userId % "/displayname", false) + , d(new Private) +{} GetDisplayNameJob::~GetDisplayNameJob() = default; @@ -54,13 +52,13 @@ BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("displayname"_ls), d->displayname); + return Success; } static const auto SetAvatarUrlJobName = QStringLiteral("SetAvatarUrlJob"); -SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, - const QString& avatarUrl) +SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl) : BaseJob(HttpVerb::Put, SetAvatarUrlJobName, basePath % "/profile/" % userId % "/avatar_url") { @@ -71,25 +69,23 @@ SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, class GetAvatarUrlJob::Private { - public: +public: QString avatarUrl; }; QUrl GetAvatarUrlJob::makeRequestUrl(QUrl baseUrl, const QString& userId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/profile/" % userId - % "/avatar_url"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), basePath % "/profile/" % userId % "/avatar_url"); } static const auto GetAvatarUrlJobName = QStringLiteral("GetAvatarUrlJob"); GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, GetAvatarUrlJobName, - basePath % "/profile/" % userId % "/avatar_url", false), - d(new Private) -{ -} + basePath % "/profile/" % userId % "/avatar_url", false) + , d(new Private) +{} GetAvatarUrlJob::~GetAvatarUrlJob() = default; @@ -99,12 +95,13 @@ BaseJob::Status GetAvatarUrlJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("avatar_url"_ls), d->avatarUrl); + return Success; } class GetUserProfileJob::Private { - public: +public: QString avatarUrl; QString displayname; }; @@ -119,10 +116,9 @@ static const auto GetUserProfileJobName = QStringLiteral("GetUserProfileJob"); GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, GetUserProfileJobName, - basePath % "/profile/" % userId, false), - d(new Private) -{ -} + basePath % "/profile/" % userId, false) + , d(new Private) +{} GetUserProfileJob::~GetUserProfileJob() = default; @@ -135,5 +131,6 @@ BaseJob::Status GetUserProfileJob::parseJson(const QJsonDocument& data) auto json = data.object(); fromJson(json.value("avatar_url"_ls), d->avatarUrl); fromJson(json.value("displayname"_ls), d->displayname); + return Success; } diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h index 9a6ab56b..95d3ec97 100644 --- a/lib/csapi/profile.h +++ b/lib/csapi/profile.h @@ -6,156 +6,162 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Set the user's display name. - /// - /// This API sets the given user's display name. You must have permission to - /// set this user's display name, e.g. you need to have their - /// ``access_token``. - class SetDisplayNameJob : public BaseJob - { - public: - /*! Set the user's display name. - * \param userId - * The user whose display name to set. - * \param displayname - * The new display name for this user. - */ - explicit SetDisplayNameJob(const QString& userId, - const QString& displayname = {}); - }; - - /// Get the user's display name. - /// - /// Get the user's display name. This API may be used to fetch the user's - /// own displayname or to query the name of other users; either locally or - /// on remote homeservers. - class GetDisplayNameJob : public BaseJob - { - public: - /*! Get the user's display name. - * \param userId - * The user whose display name to get. - */ - explicit GetDisplayNameJob(const QString& userId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetDisplayNameJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - - ~GetDisplayNameJob() override; - - // Result properties - - /// The user's display name if they have set one, otherwise not present. - const QString& displayname() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Set the user's avatar URL. - /// - /// This API sets the given user's avatar URL. You must have permission to - /// set this user's avatar URL, e.g. you need to have their - /// ``access_token``. - class SetAvatarUrlJob : public BaseJob - { - public: - /*! Set the user's avatar URL. - * \param userId - * The user whose avatar URL to set. - * \param avatarUrl - * The new avatar URL for this user. - */ - explicit SetAvatarUrlJob(const QString& userId, - const QString& avatarUrl = {}); - }; - - /// Get the user's avatar URL. - /// - /// Get the user's avatar URL. This API may be used to fetch the user's - /// own avatar URL or to query the URL of other users; either locally or - /// on remote homeservers. - class GetAvatarUrlJob : public BaseJob - { - public: - /*! Get the user's avatar URL. - * \param userId - * The user whose avatar URL to get. - */ - explicit GetAvatarUrlJob(const QString& userId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetAvatarUrlJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - - ~GetAvatarUrlJob() override; - - // Result properties - - /// The user's avatar URL if they have set one, otherwise not present. - const QString& avatarUrl() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Get this user's profile information. - /// - /// Get the combined profile information for this user. This API may be used - /// to fetch the user's own profile information or other users; either - /// locally or on remote homeservers. This API may return keys which are not - /// limited to ``displayname`` or ``avatar_url``. - class GetUserProfileJob : public BaseJob - { - public: - /*! Get this user's profile information. - * \param userId - * The user whose profile information to get. - */ - explicit GetUserProfileJob(const QString& userId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetUserProfileJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); - - ~GetUserProfileJob() override; - - // Result properties - - /// The user's avatar URL if they have set one, otherwise not present. - const QString& avatarUrl() const; - /// The user's display name if they have set one, otherwise not present. - const QString& displayname() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Set the user's display name. +/*! + * This API sets the given user's display name. You must have permission to + * set this user's display name, e.g. you need to have their ``access_token``. + */ +class SetDisplayNameJob : public BaseJob +{ +public: + /*! Set the user's display name. + * \param userId + * The user whose display name to set. + * \param displayname + * The new display name for this user. + */ + explicit SetDisplayNameJob(const QString& userId, + const QString& displayname = {}); +}; + +/// Get the user's display name. +/*! + * Get the user's display name. This API may be used to fetch the user's + * own displayname or to query the name of other users; either locally or + * on remote homeservers. + */ +class GetDisplayNameJob : public BaseJob +{ +public: + /*! Get the user's display name. + * \param userId + * The user whose display name to get. + */ + explicit GetDisplayNameJob(const QString& userId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetDisplayNameJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); + + ~GetDisplayNameJob() override; + + // Result properties + + /// The user's display name if they have set one, otherwise not present. + const QString& displayname() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Set the user's avatar URL. +/*! + * This API sets the given user's avatar URL. You must have permission to + * set this user's avatar URL, e.g. you need to have their ``access_token``. + */ +class SetAvatarUrlJob : public BaseJob +{ +public: + /*! Set the user's avatar URL. + * \param userId + * The user whose avatar URL to set. + * \param avatarUrl + * The new avatar URL for this user. + */ + explicit SetAvatarUrlJob(const QString& userId, + const QString& avatarUrl = {}); +}; + +/// Get the user's avatar URL. +/*! + * Get the user's avatar URL. This API may be used to fetch the user's + * own avatar URL or to query the URL of other users; either locally or + * on remote homeservers. + */ +class GetAvatarUrlJob : public BaseJob +{ +public: + /*! Get the user's avatar URL. + * \param userId + * The user whose avatar URL to get. + */ + explicit GetAvatarUrlJob(const QString& userId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetAvatarUrlJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); + + ~GetAvatarUrlJob() override; + + // Result properties + + /// The user's avatar URL if they have set one, otherwise not present. + const QString& avatarUrl() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get this user's profile information. +/*! + * Get the combined profile information for this user. This API may be used + * to fetch the user's own profile information or other users; either + * locally or on remote homeservers. This API may return keys which are not + * limited to ``displayname`` or ``avatar_url``. + */ +class GetUserProfileJob : public BaseJob +{ +public: + /*! Get this user's profile information. + * \param userId + * The user whose profile information to get. + */ + explicit GetUserProfileJob(const QString& userId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetUserProfileJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId); + + ~GetUserProfileJob() override; + + // Result properties + + /// The user's avatar URL if they have set one, otherwise not present. + const QString& avatarUrl() const; + /// The user's display name if they have set one, otherwise not present. + const QString& displayname() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/pusher.cpp b/lib/csapi/pusher.cpp index 86b36c6d..41a0cffe 100644 --- a/lib/csapi/pusher.cpp +++ b/lib/csapi/pusher.cpp @@ -12,38 +12,42 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<GetPushersJob::PusherData> { - static void fillFrom(const QJsonObject& jo, - GetPushersJob::PusherData& result) - { - fromJson(jo.value("url"_ls), result.url); - fromJson(jo.value("format"_ls), result.format); - } - }; - - template <> struct JsonObjectConverter<GetPushersJob::Pusher> { - static void fillFrom(const QJsonObject& jo, - GetPushersJob::Pusher& result) - { - fromJson(jo.value("pushkey"_ls), result.pushkey); - fromJson(jo.value("kind"_ls), result.kind); - fromJson(jo.value("app_id"_ls), result.appId); - fromJson(jo.value("app_display_name"_ls), result.appDisplayName); - fromJson(jo.value("device_display_name"_ls), - result.deviceDisplayName); - fromJson(jo.value("profile_tag"_ls), result.profileTag); - fromJson(jo.value("lang"_ls), result.lang); - fromJson(jo.value("data"_ls), result.data); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetPushersJob::PusherData> +{ + static void fillFrom(const QJsonObject& jo, + GetPushersJob::PusherData& result) + { + fromJson(jo.value("url"_ls), result.url); + fromJson(jo.value("format"_ls), result.format); + } +}; + +template <> +struct JsonObjectConverter<GetPushersJob::Pusher> +{ + static void fillFrom(const QJsonObject& jo, GetPushersJob::Pusher& result) + { + fromJson(jo.value("pushkey"_ls), result.pushkey); + fromJson(jo.value("kind"_ls), result.kind); + fromJson(jo.value("app_id"_ls), result.appId); + fromJson(jo.value("app_display_name"_ls), result.appDisplayName); + fromJson(jo.value("device_display_name"_ls), result.deviceDisplayName); + fromJson(jo.value("profile_tag"_ls), result.profileTag); + fromJson(jo.value("lang"_ls), result.lang); + fromJson(jo.value("data"_ls), result.data); + } +}; + } // namespace QMatrixClient class GetPushersJob::Private { - public: +public: QVector<Pusher> pushers; }; @@ -55,10 +59,9 @@ QUrl GetPushersJob::makeRequestUrl(QUrl baseUrl) static const auto GetPushersJobName = QStringLiteral("GetPushersJob"); GetPushersJob::GetPushersJob() - : BaseJob(HttpVerb::Get, GetPushersJobName, basePath % "/pushers"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetPushersJobName, basePath % "/pushers") + , d(new Private) +{} GetPushersJob::~GetPushersJob() = default; @@ -71,27 +74,30 @@ BaseJob::Status GetPushersJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("pushers"_ls), d->pushers); + return Success; } -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<PostPusherJob::PusherData> { - static void dumpTo(QJsonObject& jo, - const PostPusherJob::PusherData& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("url"), pod.url); - addParam<IfNotEmpty>(jo, QStringLiteral("format"), pod.format); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<PostPusherJob::PusherData> +{ + static void dumpTo(QJsonObject& jo, const PostPusherJob::PusherData& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("url"), pod.url); + addParam<IfNotEmpty>(jo, QStringLiteral("format"), pod.format); + } +}; + } // namespace QMatrixClient static const auto PostPusherJobName = QStringLiteral("PostPusherJob"); PostPusherJob::PostPusherJob(const QString& pushkey, const QString& kind, - const QString& appId, - const QString& appDisplayName, + const QString& appId, const QString& appDisplayName, const QString& deviceDisplayName, const QString& lang, const PusherData& data, const QString& profileTag, Omittable<bool> append) diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h index 947d7fc8..a909b9a0 100644 --- a/lib/csapi/pusher.h +++ b/lib/csapi/pusher.h @@ -4,168 +4,169 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QVector> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Gets the current pushers for the authenticated user +/*! + * Gets all currently active pushers for the authenticated user. + */ +class GetPushersJob : public BaseJob +{ +public: + // Inner data structures + + /// A dictionary of information for the pusher implementationitself. + struct PusherData + { + /// Required if ``kind`` is ``http``. The URL to use to + /// sendnotifications to. + QString url; + /// The format to use when sending notifications to the PushGateway. + QString format; + }; - /// Gets the current pushers for the authenticated user - /// /// Gets all currently active pushers for the authenticated user. - class GetPushersJob : public BaseJob + struct Pusher { - public: - // Inner data structures - - /// A dictionary of information for the pusher implementation - /// itself. - struct PusherData { - /// Required if ``kind`` is ``http``. The URL to use to send - /// notifications to. - QString url; - /// The format to use when sending notifications to the Push - /// Gateway. - QString format; - }; - - /// Gets all currently active pushers for the authenticated user. - struct Pusher { - /// This is a unique identifier for this pusher. See ``/set`` for - /// more detail. - /// Max length, 512 bytes. - QString pushkey; - /// The kind of pusher. ``"http"`` is a pusher that - /// sends HTTP pokes. - QString kind; - /// This is a reverse-DNS style identifier for the application. - /// Max length, 64 chars. - QString appId; - /// A string that will allow the user to identify what application - /// owns this pusher. - QString appDisplayName; - /// A string that will allow the user to identify what device owns - /// this pusher. - QString deviceDisplayName; - /// This string determines which set of device specific rules this - /// pusher executes. - QString profileTag; - /// The preferred language for receiving notifications (e.g. 'en' - /// or 'en-US') - QString lang; - /// A dictionary of information for the pusher implementation - /// itself. - PusherData data; - }; - - // Construction/destruction - - explicit GetPushersJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPushersJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetPushersJob() override; - - // Result properties - - /// An array containing the current pushers for the user - const QVector<Pusher>& pushers() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// This is a unique identifier for this pusher. See ``/set`` formore + /// detail.Max length, 512 bytes. + QString pushkey; + /// The kind of pusher. ``"http"`` is a pusher thatsends HTTP pokes. + QString kind; + /// This is a reverse-DNS style identifier for the application.Max + /// length, 64 chars. + QString appId; + /// A string that will allow the user to identify what applicationowns + /// this pusher. + QString appDisplayName; + /// A string that will allow the user to identify what device ownsthis + /// pusher. + QString deviceDisplayName; + /// This string determines which set of device specific rules thispusher + /// executes. + QString profileTag; + /// The preferred language for receiving notifications (e.g. 'en'or + /// 'en-US') + QString lang; + /// A dictionary of information for the pusher implementationitself. + PusherData data; }; - /// Modify a pusher for this user on the homeserver. - /// - /// This endpoint allows the creation, modification and deletion of - /// `pushers`_ for this user ID. The behaviour of this endpoint varies - /// depending on the values in the JSON body. - class PostPusherJob : public BaseJob + // Construction/destruction + + explicit GetPushersJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPushersJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetPushersJob() override; + + // Result properties + + /// An array containing the current pushers for the user + const QVector<Pusher>& pushers() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Modify a pusher for this user on the homeserver. +/*! + * This endpoint allows the creation, modification and deletion of `pushers`_ + * for this user ID. The behaviour of this endpoint varies depending on the + * values in the JSON body. + */ +class PostPusherJob : public BaseJob +{ +public: + // Inner data structures + + /// A dictionary of information for the pusher implementationitself. If + /// ``kind`` is ``http``, this should contain ``url``which is the URL to use + /// to send notifications to. + struct PusherData { - public: - // Inner data structures - - /// A dictionary of information for the pusher implementation - /// itself. If ``kind`` is ``http``, this should contain ``url`` - /// which is the URL to use to send notifications to. - struct PusherData { - /// Required if ``kind`` is ``http``. The URL to use to send - /// notifications to. MUST be an HTTPS URL with a path of - /// ``/_matrix/push/v1/notify``. - QString url; - /// The format to send notifications in to Push Gateways if the - /// ``kind`` is ``http``. The details about what fields the - /// homeserver should send to the push gateway are defined in the - /// `Push Gateway Specification`_. Currently the only format - /// available is 'event_id_only'. - QString format; - }; - - // Construction/destruction - - /*! Modify a pusher for this user on the homeserver. - * \param pushkey - * This is a unique identifier for this pusher. The value you - * should use for this is the routing or destination address - * information for the notification, for example, the APNS token - * for APNS or the Registration ID for GCM. If your notification - * client has no such concept, use any unique identifier. - * Max length, 512 bytes. - * - * If the ``kind`` is ``"email"``, this is the email address to - * send notifications to. - * \param kind - * The kind of pusher to configure. ``"http"`` makes a pusher that - * sends HTTP pokes. ``"email"`` makes a pusher that emails the - * user with unread notifications. ``null`` deletes the pusher. - * \param appId - * This is a reverse-DNS style identifier for the application. - * It is recommended that this end with the platform, such that - * different platform versions get different app identifiers. - * Max length, 64 chars. - * - * If the ``kind`` is ``"email"``, this is ``"m.email"``. - * \param appDisplayName - * A string that will allow the user to identify what application - * owns this pusher. - * \param deviceDisplayName - * A string that will allow the user to identify what device owns - * this pusher. - * \param lang - * The preferred language for receiving notifications (e.g. 'en' - * or 'en-US'). - * \param data - * A dictionary of information for the pusher implementation - * itself. If ``kind`` is ``http``, this should contain ``url`` - * which is the URL to use to send notifications to. - * \param profileTag - * This string determines which set of device specific rules this - * pusher executes. - * \param append - * If true, the homeserver should add another pusher with the - * given pushkey and App ID in addition to any others with - * different user IDs. Otherwise, the homeserver must remove any - * other pushers with the same App ID and pushkey for different - * users. The default is ``false``. - */ - explicit PostPusherJob(const QString& pushkey, const QString& kind, - const QString& appId, - const QString& appDisplayName, - const QString& deviceDisplayName, - const QString& lang, const PusherData& data, - const QString& profileTag = {}, - Omittable<bool> append = none); + /// Required if ``kind`` is ``http``. The URL to use to sendnotifications + /// to. MUST be an HTTPS URL with a path of ``/_matrix/push/v1/notify``. + QString url; + /// The format to send notifications in to Push Gateways if the``kind`` + /// is ``http``. The details about what fields thehomeserver should send + /// to the push gateway are defined in the`Push Gateway Specification`_. + /// Currently the only formatavailable is 'event_id_only'. + QString format; }; + + // Construction/destruction + + /*! Modify a pusher for this user on the homeserver. + * \param pushkey + * This is a unique identifier for this pusher. The value you + * should use for this is the routing or destination address + * information for the notification, for example, the APNS token + * for APNS or the Registration ID for GCM. If your notification + * client has no such concept, use any unique identifier. + * Max length, 512 bytes. + * + * If the ``kind`` is ``"email"``, this is the email address to + * send notifications to. + * \param kind + * The kind of pusher to configure. ``"http"`` makes a pusher that + * sends HTTP pokes. ``"email"`` makes a pusher that emails the + * user with unread notifications. ``null`` deletes the pusher. + * \param appId + * This is a reverse-DNS style identifier for the application. + * It is recommended that this end with the platform, such that + * different platform versions get different app identifiers. + * Max length, 64 chars. + * + * If the ``kind`` is ``"email"``, this is ``"m.email"``. + * \param appDisplayName + * A string that will allow the user to identify what application + * owns this pusher. + * \param deviceDisplayName + * A string that will allow the user to identify what device owns + * this pusher. + * \param lang + * The preferred language for receiving notifications (e.g. 'en' + * or 'en-US'). + * \param data + * A dictionary of information for the pusher implementation + * itself. If ``kind`` is ``http``, this should contain ``url`` + * which is the URL to use to send notifications to. + * \param profileTag + * This string determines which set of device specific rules this + * pusher executes. + * \param append + * If true, the homeserver should add another pusher with the + * given pushkey and App ID in addition to any others with + * different user IDs. Otherwise, the homeserver must remove any + * other pushers with the same App ID and pushkey for different + * users. The default is ``false``. + */ + explicit PostPusherJob(const QString& pushkey, const QString& kind, + const QString& appId, const QString& appDisplayName, + const QString& deviceDisplayName, + const QString& lang, const PusherData& data, + const QString& profileTag = {}, + Omittable<bool> append = none); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/pushrules.cpp b/lib/csapi/pushrules.cpp index e70cbbde..5842369f 100644 --- a/lib/csapi/pushrules.cpp +++ b/lib/csapi/pushrules.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetPushRulesJob::Private { - public: +public: PushRuleset global; }; @@ -26,10 +26,9 @@ QUrl GetPushRulesJob::makeRequestUrl(QUrl baseUrl) static const auto GetPushRulesJobName = QStringLiteral("GetPushRulesJob"); GetPushRulesJob::GetPushRulesJob() - : BaseJob(HttpVerb::Get, GetPushRulesJobName, basePath % "/pushrules"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetPushRulesJobName, basePath % "/pushrules") + , d(new Private) +{} GetPushRulesJob::~GetPushRulesJob() = default; @@ -39,23 +38,25 @@ BaseJob::Status GetPushRulesJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("global"_ls)) - return { JsonParseError, "The key 'global' not found in the response" }; + return { IncorrectResponse, + "The key 'global' not found in the response" }; fromJson(json.value("global"_ls), d->global); + return Success; } class GetPushRuleJob::Private { - public: +public: PushRule data; }; QUrl GetPushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/pushrules/" % scope % "/" % kind - % "/" % ruleId); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" + % scope % "/" % kind + % "/" % ruleId); } static const auto GetPushRuleJobName = QStringLiteral("GetPushRuleJob"); @@ -63,10 +64,9 @@ static const auto GetPushRuleJobName = QStringLiteral("GetPushRuleJob"); GetPushRuleJob::GetPushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, GetPushRuleJobName, - basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId), - d(new Private) -{ -} + basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId) + , d(new Private) +{} GetPushRuleJob::~GetPushRuleJob() = default; @@ -82,9 +82,9 @@ QUrl DeletePushRuleJob::makeRequestUrl(QUrl baseUrl, const QString& scope, const QString& kind, const QString& ruleId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/pushrules/" % scope % "/" % kind - % "/" % ruleId); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" + % scope % "/" % kind + % "/" % ruleId); } static const auto DeletePushRuleJobName = QStringLiteral("DeletePushRuleJob"); @@ -93,8 +93,7 @@ DeletePushRuleJob::DeletePushRuleJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Delete, DeletePushRuleJobName, basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId) -{ -} +{} BaseJob::Query queryToSetPushRule(const QString& before, const QString& after) { @@ -107,8 +106,7 @@ BaseJob::Query queryToSetPushRule(const QString& before, const QString& after) static const auto SetPushRuleJobName = QStringLiteral("SetPushRuleJob"); SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, - const QString& ruleId, - const QStringList& actions, + const QString& ruleId, const QStringList& actions, const QString& before, const QString& after, const QVector<PushCondition>& conditions, const QString& pattern) @@ -125,7 +123,7 @@ SetPushRuleJob::SetPushRuleJob(const QString& scope, const QString& kind, class IsPushRuleEnabledJob::Private { - public: +public: bool enabled; }; @@ -135,21 +133,20 @@ QUrl IsPushRuleEnabledJob::makeRequestUrl(QUrl baseUrl, const QString& scope, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" % scope % "/" % kind - % "/" % ruleId % "/enabled"); + % "/" % ruleId % "/enabled"); } static const auto IsPushRuleEnabledJobName = - QStringLiteral("IsPushRuleEnabledJob"); + QStringLiteral("IsPushRuleEnabledJob"); IsPushRuleEnabledJob::IsPushRuleEnabledJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, IsPushRuleEnabledJobName, basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/enabled"), - d(new Private) -{ -} + % "/enabled") + , d(new Private) +{} IsPushRuleEnabledJob::~IsPushRuleEnabledJob() = default; @@ -159,22 +156,22 @@ BaseJob::Status IsPushRuleEnabledJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("enabled"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'enabled' not found in the response" }; fromJson(json.value("enabled"_ls), d->enabled); + return Success; } static const auto SetPushRuleEnabledJobName = - QStringLiteral("SetPushRuleEnabledJob"); + QStringLiteral("SetPushRuleEnabledJob"); SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& scope, const QString& kind, - const QString& ruleId, - bool enabled) + const QString& ruleId, bool enabled) : BaseJob(HttpVerb::Put, SetPushRuleEnabledJobName, basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/enabled") + % "/enabled") { QJsonObject _data; addParam<>(_data, QStringLiteral("enabled"), enabled); @@ -183,7 +180,7 @@ SetPushRuleEnabledJob::SetPushRuleEnabledJob(const QString& scope, class GetPushRuleActionsJob::Private { - public: +public: QStringList actions; }; @@ -193,21 +190,20 @@ QUrl GetPushRuleActionsJob::makeRequestUrl(QUrl baseUrl, const QString& scope, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/pushrules/" % scope % "/" % kind - % "/" % ruleId % "/actions"); + % "/" % ruleId % "/actions"); } static const auto GetPushRuleActionsJobName = - QStringLiteral("GetPushRuleActionsJob"); + QStringLiteral("GetPushRuleActionsJob"); GetPushRuleActionsJob::GetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId) : BaseJob(HttpVerb::Get, GetPushRuleActionsJobName, basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/actions"), - d(new Private) -{ -} + % "/actions") + , d(new Private) +{} GetPushRuleActionsJob::~GetPushRuleActionsJob() = default; @@ -217,14 +213,15 @@ BaseJob::Status GetPushRuleActionsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("actions"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'actions' not found in the response" }; fromJson(json.value("actions"_ls), d->actions); + return Success; } static const auto SetPushRuleActionsJobName = - QStringLiteral("SetPushRuleActionsJob"); + QStringLiteral("SetPushRuleActionsJob"); SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, const QString& kind, @@ -232,7 +229,7 @@ SetPushRuleActionsJob::SetPushRuleActionsJob(const QString& scope, const QStringList& actions) : BaseJob(HttpVerb::Put, SetPushRuleActionsJobName, basePath % "/pushrules/" % scope % "/" % kind % "/" % ruleId - % "/actions") + % "/actions") { QJsonObject _data; addParam<>(_data, QStringLiteral("actions"), actions); diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h index 5191d129..9074addb 100644 --- a/lib/csapi/pushrules.h +++ b/lib/csapi/pushrules.h @@ -4,287 +4,297 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/push_condition.h" #include "csapi/definitions/push_rule.h" #include "csapi/definitions/push_ruleset.h" + +#include "jobs/basejob.h" + #include <QtCore/QVector> -namespace QMatrixClient { - // Operations - - /// Retrieve all push rulesets. - /// - /// Retrieve all push rulesets for this user. Clients can "drill-down" on - /// the rulesets by suffixing a ``scope`` to this path e.g. - /// ``/pushrules/global/``. This will return a subset of this data under the - /// specified key e.g. the ``global`` key. - class GetPushRulesJob : public BaseJob - { - public: - explicit GetPushRulesJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPushRulesJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetPushRulesJob() override; - - // Result properties - - /// The global ruleset. - const PushRuleset& global() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Retrieve a push rule. - /// - /// Retrieve a single specified push rule. - class GetPushRuleJob : public BaseJob - { - public: - /*! Retrieve a push rule. - * \param scope - * ``global`` to specify global rules. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - */ - explicit GetPushRuleJob(const QString& scope, const QString& kind, - const QString& ruleId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPushRuleJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, - const QString& kind, const QString& ruleId); - - ~GetPushRuleJob() override; - - // Result properties - - /// The push rule. - const PushRule& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Delete a push rule. - /// - /// This endpoint removes the push rule defined in the path. - class DeletePushRuleJob : public BaseJob - { - public: - /*! Delete a push rule. - * \param scope - * ``global`` to specify global rules. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - */ - explicit DeletePushRuleJob(const QString& scope, const QString& kind, +namespace QMatrixClient +{ + +// Operations + +/// Retrieve all push rulesets. +/*! + * Retrieve all push rulesets for this user. Clients can "drill-down" on + * the rulesets by suffixing a ``scope`` to this path e.g. + * ``/pushrules/global/``. This will return a subset of this data under the + * specified key e.g. the ``global`` key. + */ +class GetPushRulesJob : public BaseJob +{ +public: + explicit GetPushRulesJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPushRulesJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetPushRulesJob() override; + + // Result properties + + /// The global ruleset. + const PushRuleset& global() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Retrieve a push rule. +/*! + * Retrieve a single specified push rule. + */ +class GetPushRuleJob : public BaseJob +{ +public: + /*! Retrieve a push rule. + * \param scope + * ``global`` to specify global rules. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + */ + explicit GetPushRuleJob(const QString& scope, const QString& kind, + const QString& ruleId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPushRuleJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, + const QString& kind, const QString& ruleId); + + ~GetPushRuleJob() override; + + // Result properties + + /// The push rule. + const PushRule& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Delete a push rule. +/*! + * This endpoint removes the push rule defined in the path. + */ +class DeletePushRuleJob : public BaseJob +{ +public: + /*! Delete a push rule. + * \param scope + * ``global`` to specify global rules. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + */ + explicit DeletePushRuleJob(const QString& scope, const QString& kind, + const QString& ruleId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * DeletePushRuleJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, + const QString& kind, const QString& ruleId); +}; + +/// Add or change a push rule. +/*! + * This endpoint allows the creation, modification and deletion of pushers + * for this user ID. The behaviour of this endpoint varies depending on the + * values in the JSON body. + * + * When creating push rules, they MUST be enabled by default. + */ +class SetPushRuleJob : public BaseJob +{ +public: + /*! Add or change a push rule. + * \param scope + * ``global`` to specify global rules. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + * \param actions + * The action(s) to perform when the conditions for this rule are met. + * \param before + * Use 'before' with a ``rule_id`` as its value to make the new rule the + * next-most important rule with respect to the given user defined rule. + * It is not possible to add a rule relative to a predefined server rule. + * \param after + * This makes the new rule the next-less important rule relative to the + * given user defined rule. It is not possible to add a rule relative + * to a predefined server rule. + * \param conditions + * The conditions that must hold true for an event in order for a + * rule to be applied to an event. A rule with no conditions + * always matches. Only applicable to ``underride`` and ``override`` + * rules. \param pattern Only applicable to ``content`` rules. The + * glob-style pattern to match against. + */ + explicit SetPushRuleJob(const QString& scope, const QString& kind, + const QString& ruleId, const QStringList& actions, + const QString& before = {}, + const QString& after = {}, + const QVector<PushCondition>& conditions = {}, + const QString& pattern = {}); +}; + +/// Get whether a push rule is enabled +/*! + * This endpoint gets whether the specified push rule is enabled. + */ +class IsPushRuleEnabledJob : public BaseJob +{ +public: + /*! Get whether a push rule is enabled + * \param scope + * Either ``global`` or ``device/<profile_tag>`` to specify global + * rules or device rules for the given ``profile_tag``. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + */ + explicit IsPushRuleEnabledJob(const QString& scope, const QString& kind, + const QString& ruleId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * IsPushRuleEnabledJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, + const QString& kind, const QString& ruleId); + + ~IsPushRuleEnabledJob() override; + + // Result properties + + /// Whether the push rule is enabled or not. + bool enabled() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Enable or disable a push rule. +/*! + * This endpoint allows clients to enable or disable the specified push rule. + */ +class SetPushRuleEnabledJob : public BaseJob +{ +public: + /*! Enable or disable a push rule. + * \param scope + * ``global`` to specify global rules. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + * \param enabled + * Whether the push rule is enabled or not. + */ + explicit SetPushRuleEnabledJob(const QString& scope, const QString& kind, + const QString& ruleId, bool enabled); +}; + +/// The actions for a push rule +/*! + * This endpoint get the actions for the specified push rule. + */ +class GetPushRuleActionsJob : public BaseJob +{ +public: + /*! The actions for a push rule + * \param scope + * Either ``global`` or ``device/<profile_tag>`` to specify global + * rules or device rules for the given ``profile_tag``. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + */ + explicit GetPushRuleActionsJob(const QString& scope, const QString& kind, const QString& ruleId); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * DeletePushRuleJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, - const QString& kind, const QString& ruleId); - }; - - /// Add or change a push rule. - /// - /// This endpoint allows the creation, modification and deletion of pushers - /// for this user ID. The behaviour of this endpoint varies depending on the - /// values in the JSON body. - /// - /// When creating push rules, they MUST be enabled by default. - class SetPushRuleJob : public BaseJob - { - public: - /*! Add or change a push rule. - * \param scope - * ``global`` to specify global rules. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - * \param actions - * The action(s) to perform when the conditions for this rule are met. - * \param before - * Use 'before' with a ``rule_id`` as its value to make the new rule - * the next-most important rule with respect to the given user defined - * rule. It is not possible to add a rule relative to a predefined - * server rule. \param after This makes the new rule the next-less - * important rule relative to the given user defined rule. It is not - * possible to add a rule relative to a predefined server rule. \param - * conditions The conditions that must hold true for an event in order - * for a rule to be applied to an event. A rule with no conditions - * always matches. Only applicable to ``underride`` and ``override`` - * rules. \param pattern Only applicable to ``content`` rules. The - * glob-style pattern to match against. - */ - explicit SetPushRuleJob(const QString& scope, const QString& kind, - const QString& ruleId, - const QStringList& actions, - const QString& before = {}, - const QString& after = {}, - const QVector<PushCondition>& conditions = {}, - const QString& pattern = {}); - }; - - /// Get whether a push rule is enabled - /// - /// This endpoint gets whether the specified push rule is enabled. - class IsPushRuleEnabledJob : public BaseJob - { - public: - /*! Get whether a push rule is enabled - * \param scope - * Either ``global`` or ``device/<profile_tag>`` to specify global - * rules or device rules for the given ``profile_tag``. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - */ - explicit IsPushRuleEnabledJob(const QString& scope, const QString& kind, - const QString& ruleId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * IsPushRuleEnabledJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, - const QString& kind, const QString& ruleId); - - ~IsPushRuleEnabledJob() override; - - // Result properties - - /// Whether the push rule is enabled or not. - bool enabled() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Enable or disable a push rule. - /// - /// This endpoint allows clients to enable or disable the specified push - /// rule. - class SetPushRuleEnabledJob : public BaseJob - { - public: - /*! Enable or disable a push rule. - * \param scope - * ``global`` to specify global rules. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - * \param enabled - * Whether the push rule is enabled or not. - */ - explicit SetPushRuleEnabledJob(const QString& scope, - const QString& kind, - const QString& ruleId, bool enabled); - }; - - /// The actions for a push rule - /// - /// This endpoint get the actions for the specified push rule. - class GetPushRuleActionsJob : public BaseJob - { - public: - /*! The actions for a push rule - * \param scope - * Either ``global`` or ``device/<profile_tag>`` to specify global - * rules or device rules for the given ``profile_tag``. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - */ - explicit GetPushRuleActionsJob(const QString& scope, - const QString& kind, - const QString& ruleId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetPushRuleActionsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, - const QString& kind, const QString& ruleId); - - ~GetPushRuleActionsJob() override; - - // Result properties - - /// The action(s) to perform for this rule. - const QStringList& actions() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Set the actions for a push rule. - /// - /// This endpoint allows clients to change the actions of a push rule. - /// This can be used to change the actions of builtin rules. - class SetPushRuleActionsJob : public BaseJob - { - public: - /*! Set the actions for a push rule. - * \param scope - * ``global`` to specify global rules. - * \param kind - * The kind of rule - * \param ruleId - * The identifier for the rule. - * \param actions - * The action(s) to perform for this rule. - */ - explicit SetPushRuleActionsJob(const QString& scope, - const QString& kind, - const QString& ruleId, - const QStringList& actions); - }; + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetPushRuleActionsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& scope, + const QString& kind, const QString& ruleId); + + ~GetPushRuleActionsJob() override; + + // Result properties + + /// The action(s) to perform for this rule. + const QStringList& actions() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Set the actions for a push rule. +/*! + * This endpoint allows clients to change the actions of a push rule. + * This can be used to change the actions of builtin rules. + */ +class SetPushRuleActionsJob : public BaseJob +{ +public: + /*! Set the actions for a push rule. + * \param scope + * ``global`` to specify global rules. + * \param kind + * The kind of rule + * \param ruleId + * The identifier for the rule. + * \param actions + * The action(s) to perform for this rule. + */ + explicit SetPushRuleActionsJob(const QString& scope, const QString& kind, + const QString& ruleId, + const QStringList& actions); +}; + } // namespace QMatrixClient diff --git a/lib/csapi/read_markers.h b/lib/csapi/read_markers.h index 6afa5572..e1fbc263 100644 --- a/lib/csapi/read_markers.h +++ b/lib/csapi/read_markers.h @@ -6,29 +6,32 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Set the position of the read marker for a room. +/*! + * Sets the position of the read marker for a given room, and optionally + * the read receipt's location. + */ +class SetReadMarkerJob : public BaseJob +{ +public: + /*! Set the position of the read marker for a room. + * \param roomId + * The room ID to set the read marker in for the user. + * \param mFullyRead + * The event ID the read marker should be located at. The + * event MUST belong to the room. + * \param mRead + * The event ID to set the read receipt location at. This is + * equivalent to calling ``/receipt/m.read/$elsewhere:example.org`` + * and is provided here to save that extra call. + */ + explicit SetReadMarkerJob(const QString& roomId, const QString& mFullyRead, + const QString& mRead = {}); +}; - /// Set the position of the read marker for a room. - /// - /// Sets the position of the read marker for a given room, and optionally - /// the read receipt's location. - class SetReadMarkerJob : public BaseJob - { - public: - /*! Set the position of the read marker for a room. - * \param roomId - * The room ID to set the read marker in for the user. - * \param mFullyRead - * The event ID the read marker should be located at. The - * event MUST belong to the room. - * \param mRead - * The event ID to set the read receipt location at. This is - * equivalent to calling ``/receipt/m.read/$elsewhere:example.org`` - * and is provided here to save that extra call. - */ - explicit SetReadMarkerJob(const QString& roomId, - const QString& mFullyRead, - const QString& mRead = {}); - }; } // namespace QMatrixClient diff --git a/lib/csapi/receipts.cpp b/lib/csapi/receipts.cpp index 25d0dd8e..28d7032f 100644 --- a/lib/csapi/receipts.cpp +++ b/lib/csapi/receipts.cpp @@ -14,13 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); static const auto PostReceiptJobName = QStringLiteral("PostReceiptJob"); -PostReceiptJob::PostReceiptJob(const QString& roomId, - const QString& receiptType, +PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt) : BaseJob(HttpVerb::Post, PostReceiptJobName, basePath % "/rooms/" % roomId % "/receipt/" % receiptType % "/" - % eventId) + % eventId) { setRequestData(Data(toJson(receipt))); } diff --git a/lib/csapi/receipts.h b/lib/csapi/receipts.h index 4e40188a..bb037c08 100644 --- a/lib/csapi/receipts.h +++ b/lib/csapi/receipts.h @@ -8,30 +8,33 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Send a receipt for the given event ID. +/*! + * This API updates the marker for the given receipt type to the event ID + * specified. + */ +class PostReceiptJob : public BaseJob +{ +public: + /*! Send a receipt for the given event ID. + * \param roomId + * The room in which to send the event. + * \param receiptType + * The type of receipt to send. + * \param eventId + * The event ID to acknowledge up to. + * \param receipt + * Extra receipt information to attach to ``content`` if any. The + * server will automatically set the ``ts`` field. + */ + explicit PostReceiptJob(const QString& roomId, const QString& receiptType, + const QString& eventId, + const QJsonObject& receipt = {}); +}; - /// Send a receipt for the given event ID. - /// - /// This API updates the marker for the given receipt type to the event ID - /// specified. - class PostReceiptJob : public BaseJob - { - public: - /*! Send a receipt for the given event ID. - * \param roomId - * The room in which to send the event. - * \param receiptType - * The type of receipt to send. - * \param eventId - * The event ID to acknowledge up to. - * \param receipt - * Extra receipt information to attach to ``content`` if any. The - * server will automatically set the ``ts`` field. - */ - explicit PostReceiptJob(const QString& roomId, - const QString& receiptType, - const QString& eventId, - const QJsonObject& receipt = {}); - }; } // namespace QMatrixClient diff --git a/lib/csapi/redaction.cpp b/lib/csapi/redaction.cpp index 713c5102..f944cdd4 100644 --- a/lib/csapi/redaction.cpp +++ b/lib/csapi/redaction.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class RedactEventJob::Private { - public: +public: QString eventId; }; @@ -23,9 +23,8 @@ static const auto RedactEventJobName = QStringLiteral("RedactEventJob"); RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason) : BaseJob(HttpVerb::Put, RedactEventJobName, - basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" - % txnId), - d(new Private) + basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" % txnId) + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("reason"), reason); @@ -40,5 +39,6 @@ BaseJob::Status RedactEventJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("event_id"_ls), d->eventId); + return Success; } diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h index ab235b31..c75421cb 100644 --- a/lib/csapi/redaction.h +++ b/lib/csapi/redaction.h @@ -6,47 +6,51 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Strips all non-integrity-critical information out of an event. - /// - /// Strips all information out of an event which isn't critical to the - /// integrity of the server-side representation of the room. - /// - /// This cannot be undone. - /// - /// Users may redact their own events, and any user with a power level - /// greater than or equal to the `redact` power level of the room may - /// redact events there. - class RedactEventJob : public BaseJob - { - public: - /*! Strips all non-integrity-critical information out of an event. - * \param roomId - * The room from which to redact the event. - * \param eventId - * The ID of the event to redact - * \param txnId - * The transaction ID for this event. Clients should generate a - * unique ID; it will be used by the server to ensure idempotency of - * requests. \param reason The reason for the event being redacted. - */ - explicit RedactEventJob(const QString& roomId, const QString& eventId, - const QString& txnId, - const QString& reason = {}); - ~RedactEventJob() override; - - // Result properties - - /// A unique identifier for the event. - const QString& eventId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Strips all non-integrity-critical information out of an event. +/*! + * Strips all information out of an event which isn't critical to the + * integrity of the server-side representation of the room. + * + * This cannot be undone. + * + * Users may redact their own events, and any user with a power level + * greater than or equal to the `redact` power level of the room may + * redact events there. + */ +class RedactEventJob : public BaseJob +{ +public: + /*! Strips all non-integrity-critical information out of an event. + * \param roomId + * The room from which to redact the event. + * \param eventId + * The ID of the event to redact + * \param txnId + * The transaction ID for this event. Clients should generate a + * unique ID; it will be used by the server to ensure idempotency of + * requests. \param reason The reason for the event being redacted. + */ + explicit RedactEventJob(const QString& roomId, const QString& eventId, + const QString& txnId, const QString& reason = {}); + + ~RedactEventJob() override; + + // Result properties + + /// A unique identifier for the event. + const QString& eventId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/registration.cpp b/lib/csapi/registration.cpp index b5e7d985..52b4098d 100644 --- a/lib/csapi/registration.cpp +++ b/lib/csapi/registration.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class RegisterJob::Private { - public: +public: QString userId; QString accessToken; QString homeServer; @@ -37,8 +37,8 @@ RegisterJob::RegisterJob(const QString& kind, const QString& initialDeviceDisplayName, Omittable<bool> inhibitLogin) : BaseJob(HttpVerb::Post, RegisterJobName, basePath % "/register", - queryToRegister(kind), {}, false), - d(new Private) + queryToRegister(kind), {}, false) + , d(new Private) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth); @@ -66,30 +66,31 @@ BaseJob::Status RegisterJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("user_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'user_id' not found in the response" }; fromJson(json.value("user_id"_ls), d->userId); fromJson(json.value("access_token"_ls), d->accessToken); fromJson(json.value("home_server"_ls), d->homeServer); fromJson(json.value("device_id"_ls), d->deviceId); + return Success; } class RequestTokenToRegisterEmailJob::Private { - public: +public: Sid data; }; static const auto RequestTokenToRegisterEmailJobName = - QStringLiteral("RequestTokenToRegisterEmailJob"); + QStringLiteral("RequestTokenToRegisterEmailJob"); RequestTokenToRegisterEmailJob::RequestTokenToRegisterEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) + const QString& clientSecret, const QString& email, int sendAttempt, + const QString& idServer, const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenToRegisterEmailJobName, - basePath % "/register/email/requestToken", false), - d(new Private) + basePath % "/register/email/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -113,20 +114,20 @@ RequestTokenToRegisterEmailJob::parseJson(const QJsonDocument& data) class RequestTokenToRegisterMSISDNJob::Private { - public: +public: Sid data; }; static const auto RequestTokenToRegisterMSISDNJobName = - QStringLiteral("RequestTokenToRegisterMSISDNJob"); + QStringLiteral("RequestTokenToRegisterMSISDNJob"); RequestTokenToRegisterMSISDNJob::RequestTokenToRegisterMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const QString& clientSecret, const QString& country, + const QString& phoneNumber, int sendAttempt, const QString& idServer, + const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenToRegisterMSISDNJobName, - basePath % "/register/msisdn/requestToken", false), - d(new Private) + basePath % "/register/msisdn/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -164,19 +165,19 @@ ChangePasswordJob::ChangePasswordJob(const QString& newPassword, class RequestTokenToResetPasswordEmailJob::Private { - public: +public: Sid data; }; static const auto RequestTokenToResetPasswordEmailJobName = - QStringLiteral("RequestTokenToResetPasswordEmailJob"); + QStringLiteral("RequestTokenToResetPasswordEmailJob"); RequestTokenToResetPasswordEmailJob::RequestTokenToResetPasswordEmailJob( - const QString& clientSecret, const QString& email, int sendAttempt, - const QString& idServer, const QString& nextLink) + const QString& clientSecret, const QString& email, int sendAttempt, + const QString& idServer, const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenToResetPasswordEmailJobName, - basePath % "/account/password/email/requestToken", false), - d(new Private) + basePath % "/account/password/email/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -188,7 +189,7 @@ RequestTokenToResetPasswordEmailJob::RequestTokenToResetPasswordEmailJob( } RequestTokenToResetPasswordEmailJob::~RequestTokenToResetPasswordEmailJob() = - default; + default; const Sid& RequestTokenToResetPasswordEmailJob::data() const { return d->data; } @@ -201,20 +202,20 @@ RequestTokenToResetPasswordEmailJob::parseJson(const QJsonDocument& data) class RequestTokenToResetPasswordMSISDNJob::Private { - public: +public: Sid data; }; static const auto RequestTokenToResetPasswordMSISDNJobName = - QStringLiteral("RequestTokenToResetPasswordMSISDNJob"); + QStringLiteral("RequestTokenToResetPasswordMSISDNJob"); RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, const QString& idServer, - const QString& nextLink) + const QString& clientSecret, const QString& country, + const QString& phoneNumber, int sendAttempt, const QString& idServer, + const QString& nextLink) : BaseJob(HttpVerb::Post, RequestTokenToResetPasswordMSISDNJobName, - basePath % "/account/password/msisdn/requestToken", false), - d(new Private) + basePath % "/account/password/msisdn/requestToken", false) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("client_secret"), clientSecret); @@ -227,7 +228,7 @@ RequestTokenToResetPasswordMSISDNJob::RequestTokenToResetPasswordMSISDNJob( } RequestTokenToResetPasswordMSISDNJob::~RequestTokenToResetPasswordMSISDNJob() = - default; + default; const Sid& RequestTokenToResetPasswordMSISDNJob::data() const { @@ -242,10 +243,10 @@ RequestTokenToResetPasswordMSISDNJob::parseJson(const QJsonDocument& data) } static const auto DeactivateAccountJobName = - QStringLiteral("DeactivateAccountJob"); + QStringLiteral("DeactivateAccountJob"); DeactivateAccountJob::DeactivateAccountJob( - const Omittable<AuthenticationData>& auth) + const Omittable<AuthenticationData>& auth) : BaseJob(HttpVerb::Post, DeactivateAccountJobName, basePath % "/account/deactivate") { @@ -256,7 +257,7 @@ DeactivateAccountJob::DeactivateAccountJob( class CheckUsernameAvailabilityJob::Private { - public: +public: Omittable<bool> available; }; @@ -276,16 +277,14 @@ QUrl CheckUsernameAvailabilityJob::makeRequestUrl(QUrl baseUrl, } static const auto CheckUsernameAvailabilityJobName = - QStringLiteral("CheckUsernameAvailabilityJob"); + QStringLiteral("CheckUsernameAvailabilityJob"); -CheckUsernameAvailabilityJob::CheckUsernameAvailabilityJob( - const QString& username) +CheckUsernameAvailabilityJob::CheckUsernameAvailabilityJob(const QString& username) : BaseJob(HttpVerb::Get, CheckUsernameAvailabilityJobName, basePath % "/register/available", - queryToCheckUsernameAvailability(username), {}, false), - d(new Private) -{ -} + queryToCheckUsernameAvailability(username), {}, false) + , d(new Private) +{} CheckUsernameAvailabilityJob::~CheckUsernameAvailabilityJob() = default; @@ -294,10 +293,10 @@ Omittable<bool> CheckUsernameAvailabilityJob::available() const return d->available; } -BaseJob::Status -CheckUsernameAvailabilityJob::parseJson(const QJsonDocument& data) +BaseJob::Status CheckUsernameAvailabilityJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("available"_ls), d->available); + return Success; } diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h index 02f4ddaf..40f1caa6 100644 --- a/lib/csapi/registration.h +++ b/lib/csapi/registration.h @@ -4,463 +4,480 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/../identity/definitions/sid.h" #include "csapi/definitions/auth_data.h" -namespace QMatrixClient { - // Operations +#include "jobs/basejob.h" - /// Register for an account on this homeserver. - /// - /// This API endpoint uses the `User-Interactive Authentication API`_. - /// - /// Register for an account on this homeserver. - /// - /// There are two kinds of user account: - /// - /// - `user` accounts. These accounts may use the full API described in this - /// specification. - /// - /// - `guest` accounts. These accounts may have limited permissions and may - /// not be supported by all servers. - /// - /// If registration is successful, this endpoint will issue an access token - /// the client can use to authorize itself in subsequent requests. - /// - /// If the client does not supply a ``device_id``, the server must - /// auto-generate one. - /// - /// The server SHOULD register an account with a User ID based on the - /// ``username`` provided, if any. Note that the grammar of Matrix User ID - /// localparts is restricted, so the server MUST either map the provided - /// ``username`` onto a ``user_id`` in a logical manner, or reject - /// ``username``\s which do not comply to the grammar, with - /// ``M_INVALID_USERNAME``. - /// - /// Matrix clients MUST NOT assume that localpart of the registered - /// ``user_id`` matches the provided ``username``. - /// - /// The returned access token must be associated with the ``device_id`` - /// supplied by the client or generated by the server. The server may - /// invalidate any access token previously associated with that device. See - /// `Relationship between access tokens and devices`_. - class RegisterJob : public BaseJob - { - public: - /*! Register for an account on this homeserver. - * \param kind - * The kind of account to register. Defaults to `user`. - * \param auth - * Additional authentication information for the - * user-interactive authentication API. Note that this - * information is *not* used to define how the registered user - * should be authenticated, but is instead used to - * authenticate the ``register`` call itself. It should be - * left empty, or omitted, unless an earlier call returned an - * response with status code 401. - * \param bindEmail - * If true, the server binds the email used for authentication to - * the Matrix ID with the identity server. - * \param username - * The basis for the localpart of the desired Matrix ID. If omitted, - * the homeserver MUST generate a Matrix ID local part. - * \param password - * The desired password for the account. - * \param deviceId - * ID of the client device. If this does not correspond to a - * known client device, a new device will be created. The server - * will auto-generate a device_id if this is not specified. - * \param initialDeviceDisplayName - * A display name to assign to the newly-created device. Ignored - * if ``device_id`` corresponds to a known device. - * \param inhibitLogin - * If true, an ``access_token`` and ``device_id`` should not be - * returned from this call, therefore preventing an automatic - * login. Defaults to false. - */ - explicit RegisterJob(const QString& kind = QStringLiteral("user"), - const Omittable<AuthenticationData>& auth = none, - Omittable<bool> bindEmail = none, - const QString& username = {}, - const QString& password = {}, - const QString& deviceId = {}, - const QString& initialDeviceDisplayName = {}, - Omittable<bool> inhibitLogin = none); - ~RegisterJob() override; - - // Result properties - - /// The fully-qualified Matrix user ID (MXID) that has been registered. - /// - /// Any user ID returned by this API must conform to the grammar given - /// in the `Matrix specification - /// <https://matrix.org/docs/spec/appendices.html#user-identifiers>`_. - const QString& userId() const; - /// An access token for the account. - /// This access token can then be used to authorize other requests. - /// Required if the ``inhibit_login`` option is false. - const QString& accessToken() const; - /// The server_name of the homeserver on which the account has - /// been registered. - /// - /// **Deprecated**. Clients should extract the server_name from - /// ``user_id`` (by splitting at the first colon) if they require - /// it. Note also that ``homeserver`` is not spelt this way. - const QString& homeServer() const; - /// ID of the registered device. Will be the same as the - /// corresponding parameter in the request, if one was specified. - /// Required if the ``inhibit_login`` option is false. - const QString& deviceId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Begins the validation process for an email to be used during - /// registration. +namespace QMatrixClient +{ + +// Operations + +/// Register for an account on this homeserver. +/*! + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * Register for an account on this homeserver. + * + * There are two kinds of user account: + * + * - `user` accounts. These accounts may use the full API described in this + * specification. + * + * - `guest` accounts. These accounts may have limited permissions and may not + * be supported by all servers. + * + * If registration is successful, this endpoint will issue an access token + * the client can use to authorize itself in subsequent requests. + * + * If the client does not supply a ``device_id``, the server must + * auto-generate one. + * + * The server SHOULD register an account with a User ID based on the + * ``username`` provided, if any. Note that the grammar of Matrix User ID + * localparts is restricted, so the server MUST either map the provided + * ``username`` onto a ``user_id`` in a logical manner, or reject + * ``username``\s which do not comply to the grammar, with + * ``M_INVALID_USERNAME``. + * + * Matrix clients MUST NOT assume that localpart of the registered + * ``user_id`` matches the provided ``username``. + * + * The returned access token must be associated with the ``device_id`` + * supplied by the client or generated by the server. The server may + * invalidate any access token previously associated with that device. See + * `Relationship between access tokens and devices`_. + */ +class RegisterJob : public BaseJob +{ +public: + /*! Register for an account on this homeserver. + * \param kind + * The kind of account to register. Defaults to `user`. + * \param auth + * Additional authentication information for the + * user-interactive authentication API. Note that this + * information is *not* used to define how the registered user + * should be authenticated, but is instead used to + * authenticate the ``register`` call itself. It should be + * left empty, or omitted, unless an earlier call returned an + * response with status code 401. + * \param bindEmail + * If true, the server binds the email used for authentication to + * the Matrix ID with the identity server. + * \param username + * The basis for the localpart of the desired Matrix ID. If omitted, + * the homeserver MUST generate a Matrix ID local part. + * \param password + * The desired password for the account. + * \param deviceId + * ID of the client device. If this does not correspond to a + * known client device, a new device will be created. The server + * will auto-generate a device_id if this is not specified. + * \param initialDeviceDisplayName + * A display name to assign to the newly-created device. Ignored + * if ``device_id`` corresponds to a known device. + * \param inhibitLogin + * If true, an ``access_token`` and ``device_id`` should not be + * returned from this call, therefore preventing an automatic + * login. Defaults to false. + */ + explicit RegisterJob(const QString& kind = QStringLiteral("user"), + const Omittable<AuthenticationData>& auth = none, + Omittable<bool> bindEmail = none, + const QString& username = {}, + const QString& password = {}, + const QString& deviceId = {}, + const QString& initialDeviceDisplayName = {}, + Omittable<bool> inhibitLogin = none); + + ~RegisterJob() override; + + // Result properties + + /// The fully-qualified Matrix user ID (MXID) that has been registered. /// - /// Proxies the Identity Service API ``validate/email/requestToken``, but - /// first checks that the given email address is not already associated - /// with an account on this homeserver. See the Identity Service API for - /// further information. - class RequestTokenToRegisterEmailJob : public BaseJob - { - public: - /*! Begins the validation process for an email to be used during registration. - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param email The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenToRegisterEmailJob(const QString& clientSecret, - const QString& email, - int sendAttempt, - const QString& idServer, - const QString& nextLink = {}); - ~RequestTokenToRegisterEmailJob() override; - - // Result properties - - /// An email has been sent to the specified address. - /// Note that this may be an email containing the validation token or it - /// may be informing the user of an error. - const Sid& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Requests a validation token be sent to the given phone number for the - /// purpose of registering an account + /// Any user ID returned by this API must conform to the grammar given in + /// the `Matrix specification + /// <https://matrix.org/docs/spec/appendices.html#user-identifiers>`_. + const QString& userId() const; + /// An access token for the account. + /// This access token can then be used to authorize other requests. + /// Required if the ``inhibit_login`` option is false. + const QString& accessToken() const; + /// The server_name of the homeserver on which the account has + /// been registered. /// - /// Proxies the Identity Service API ``validate/msisdn/requestToken``, but - /// first checks that the given phone number is not already associated - /// with an account on this homeserver. See the Identity Service API for - /// further information. - class RequestTokenToRegisterMSISDNJob : public BaseJob - { - public: - /*! Requests a validation token be sent to the given phone number for the purpose of registering an account - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param country The two-letter uppercase ISO - * country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenToRegisterMSISDNJob(const QString& clientSecret, - const QString& country, - const QString& phoneNumber, + /// **Deprecated**. Clients should extract the server_name from + /// ``user_id`` (by splitting at the first colon) if they require + /// it. Note also that ``homeserver`` is not spelt this way. + const QString& homeServer() const; + /// ID of the registered device. Will be the same as the + /// corresponding parameter in the request, if one was specified. + /// Required if the ``inhibit_login`` option is false. + const QString& deviceId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Begins the validation process for an email to be used during registration. +/*! + * Proxies the Identity Service API ``validate/email/requestToken``, but + * first checks that the given email address is not already associated + * with an account on this homeserver. See the Identity Service API for + * further information. + */ +class RequestTokenToRegisterEmailJob : public BaseJob +{ +public: + /*! Begins the validation process for an email to be used during + * registration. \param clientSecret A unique string generated by the + * client, and used to identify the validation attempt. It must be a string + * consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param email + * The email address to validate. + * \param sendAttempt + * The server will only send an email if the ``send_attempt`` + * is a number greater than the most recent one which it has seen, + * scoped to that ``email`` + ``client_secret`` pair. This is to + * avoid repeatedly sending the same email in the case of request + * retries between the POSTing user and the identity server. + * The client should increment this value if they desire a new + * email (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenToRegisterEmailJob(const QString& clientSecret, + const QString& email, + int sendAttempt, + const QString& idServer, + const QString& nextLink = {}); + + ~RequestTokenToRegisterEmailJob() override; + + // Result properties + + /// An email has been sent to the specified address. + /// Note that this may be an email containing the validation token or it may + /// be informing the user of an error. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Requests a validation token be sent to the given phone number for the +/// purpose of registering an account +/*! + * Proxies the Identity Service API ``validate/msisdn/requestToken``, but + * first checks that the given phone number is not already associated + * with an account on this homeserver. See the Identity Service API for + * further information. + */ +class RequestTokenToRegisterMSISDNJob : public BaseJob +{ +public: + /*! Requests a validation token be sent to the given phone number for the + * purpose of registering an account \param clientSecret A unique string + * generated by the client, and used to identify the validation attempt. It + * must be a string consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param country + * The two-letter uppercase ISO country code that the number in + * ``phone_number`` should be parsed as if it were dialled from. + * \param phoneNumber + * The phone number to validate. + * \param sendAttempt + * The server will only send an SMS if the ``send_attempt`` is a + * number greater than the most recent one which it has seen, + * scoped to that ``country`` + ``phone_number`` + ``client_secret`` + * triple. This is to avoid repeatedly sending the same SMS in + * the case of request retries between the POSTing user and the + * identity server. The client should increment this value if + * they desire a new SMS (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenToRegisterMSISDNJob(const QString& clientSecret, + const QString& country, + const QString& phoneNumber, + int sendAttempt, + const QString& idServer, + const QString& nextLink = {}); + + ~RequestTokenToRegisterMSISDNJob() override; + + // Result properties + + /// An SMS message has been sent to the specified phone number. + /// Note that this may be an SMS message containing the validation token or + /// it may be informing the user of an error. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Changes a user's password. +/*! + * Changes the password for an account on this homeserver. + * + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * An access token should be submitted to this endpoint if the client has + * an active session. + * + * The homeserver may change the flows available depending on whether a + * valid access token is provided. + */ +class ChangePasswordJob : public BaseJob +{ +public: + /*! Changes a user's password. + * \param newPassword + * The new password for the account. + * \param auth + * Additional authentication information for the user-interactive + * authentication API. + */ + explicit ChangePasswordJob(const QString& newPassword, + const Omittable<AuthenticationData>& auth = none); +}; + +/// Requests a validation token be sent to the given email address for the +/// purpose of resetting a user's password +/*! + * Proxies the Identity Service API ``validate/email/requestToken``, but + * first checks that the given email address **is** associated with an account + * on this homeserver. This API should be used to request + * validation tokens when authenticating for the + * `account/password` endpoint. This API's parameters and response are + * identical to that of the HS API |/register/email/requestToken|_ except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the + * given email address could be found. The server may instead send an + * email to the given address prompting the user to create an account. + * `M_THREEPID_IN_USE` may not be returned. + * + * .. |/register/email/requestToken| replace:: ``/register/email/requestToken`` + * + * .. _/register/email/requestToken: + * #post-matrix-client-r0-register-email-requesttoken + */ +class RequestTokenToResetPasswordEmailJob : public BaseJob +{ +public: + /*! Requests a validation token be sent to the given email address for the + * purpose of resetting a user's password \param clientSecret A unique + * string generated by the client, and used to identify the validation + * attempt. It must be a string consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param email + * The email address to validate. + * \param sendAttempt + * The server will only send an email if the ``send_attempt`` + * is a number greater than the most recent one which it has seen, + * scoped to that ``email`` + ``client_secret`` pair. This is to + * avoid repeatedly sending the same email in the case of request + * retries between the POSTing user and the identity server. + * The client should increment this value if they desire a new + * email (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenToResetPasswordEmailJob(const QString& clientSecret, + const QString& email, int sendAttempt, const QString& idServer, const QString& nextLink = {}); - ~RequestTokenToRegisterMSISDNJob() override; - - // Result properties - - /// An SMS message has been sent to the specified phone number. - /// Note that this may be an SMS message containing the validation token - /// or it may be informing the user of an error. - const Sid& data() const; - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + ~RequestTokenToResetPasswordEmailJob() override; + + // Result properties + + /// An email was sent to the given address. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Requests a validation token be sent to the given phone number for the +/// purpose of resetting a user's password. +/*! + * Proxies the Identity Service API ``validate/msisdn/requestToken``, but + * first checks that the given phone number **is** associated with an account + * on this homeserver. This API should be used to request + * validation tokens when authenticating for the + * `account/password` endpoint. This API's parameters and response are + * identical to that of the HS API |/register/msisdn/requestToken|_ except that + * `M_THREEPID_NOT_FOUND` may be returned if no account matching the + * given phone number could be found. The server may instead send an + * SMS message to the given address prompting the user to create an account. + * `M_THREEPID_IN_USE` may not be returned. + * + * .. |/register/msisdn/requestToken| replace:: ``/register/msisdn/requestToken`` + * + * .. _/register/msisdn/requestToken: + * #post-matrix-client-r0-register-email-requesttoken + */ +class RequestTokenToResetPasswordMSISDNJob : public BaseJob +{ +public: + /*! Requests a validation token be sent to the given phone number for the + * purpose of resetting a user's password. \param clientSecret A unique + * string generated by the client, and used to identify the validation + * attempt. It must be a string consisting of the characters + * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and it + * must not be empty. + * \param country + * The two-letter uppercase ISO country code that the number in + * ``phone_number`` should be parsed as if it were dialled from. + * \param phoneNumber + * The phone number to validate. + * \param sendAttempt + * The server will only send an SMS if the ``send_attempt`` is a + * number greater than the most recent one which it has seen, + * scoped to that ``country`` + ``phone_number`` + ``client_secret`` + * triple. This is to avoid repeatedly sending the same SMS in + * the case of request retries between the POSTing user and the + * identity server. The client should increment this value if + * they desire a new SMS (e.g. a reminder) to be sent. + * \param idServer + * The hostname of the identity server to communicate with. May + * optionally include a port. + * \param nextLink + * Optional. When the validation is completed, the identity + * server will redirect the user to this URL. + */ + explicit RequestTokenToResetPasswordMSISDNJob(const QString& clientSecret, + const QString& country, + const QString& phoneNumber, + int sendAttempt, + const QString& idServer, + const QString& nextLink = {}); + + ~RequestTokenToResetPasswordMSISDNJob() override; + + // Result properties + + /// An SMS message was sent to the given phone number. + const Sid& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Deactivate a user's account. +/*! + * Deactivate the user's account, removing all ability for the user to + * login again. + * + * This API endpoint uses the `User-Interactive Authentication API`_. + * + * An access token should be submitted to this endpoint if the client has + * an active session. + * + * The homeserver may change the flows available depending on whether a + * valid access token is provided. + */ +class DeactivateAccountJob : public BaseJob +{ +public: + /*! Deactivate a user's account. + * \param auth + * Additional authentication information for the user-interactive + * authentication API. + */ + explicit DeactivateAccountJob( + const Omittable<AuthenticationData>& auth = none); +}; + +/// Checks to see if a username is available on the server. +/*! + * Checks to see if a username is available, and valid, for the server. + * + * The server should check to ensure that, at the time of the request, the + * username requested is available for use. This includes verifying that an + * application service has not claimed the username and that the username + * fits the server's desired requirements (for example, a server could dictate + * that it does not permit usernames with underscores). + * + * Matrix clients may wish to use this API prior to attempting registration, + * however the clients must also be aware that using this API does not normally + * reserve the username. This can mean that the username becomes unavailable + * between checking its availability and attempting to register it. + */ +class CheckUsernameAvailabilityJob : public BaseJob +{ +public: + /*! Checks to see if a username is available on the server. + * \param username + * The username to check the availability of. + */ + explicit CheckUsernameAvailabilityJob(const QString& username); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * CheckUsernameAvailabilityJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& username); + + ~CheckUsernameAvailabilityJob() override; + + // Result properties + + /// A flag to indicate that the username is available. This should always + /// be ``true`` when the server replies with 200 OK. + Omittable<bool> available() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; - /// Changes a user's password. - /// - /// Changes the password for an account on this homeserver. - /// - /// This API endpoint uses the `User-Interactive Authentication API`_. - /// - /// An access token should be submitted to this endpoint if the client has - /// an active session. - /// - /// The homeserver may change the flows available depending on whether a - /// valid access token is provided. - class ChangePasswordJob : public BaseJob - { - public: - /*! Changes a user's password. - * \param newPassword - * The new password for the account. - * \param auth - * Additional authentication information for the user-interactive - * authentication API. - */ - explicit ChangePasswordJob( - const QString& newPassword, - const Omittable<AuthenticationData>& auth = none); - }; - - /// Requests a validation token be sent to the given email address for the - /// purpose of resetting a user's password - /// - /// Proxies the Identity Service API ``validate/email/requestToken``, but - /// first checks that the given email address **is** associated with an - /// account on this homeserver. This API should be used to request - /// validation tokens when authenticating for the - /// `account/password` endpoint. This API's parameters and response are - /// identical to that of the HS API |/register/email/requestToken|_ except - /// that `M_THREEPID_NOT_FOUND` may be returned if no account matching the - /// given email address could be found. The server may instead send an - /// email to the given address prompting the user to create an account. - /// `M_THREEPID_IN_USE` may not be returned. - /// - /// .. |/register/email/requestToken| replace:: - /// ``/register/email/requestToken`` - /// - /// .. _/register/email/requestToken: - /// #post-matrix-client-r0-register-email-requesttoken - class RequestTokenToResetPasswordEmailJob : public BaseJob - { - public: - /*! Requests a validation token be sent to the given email address for the purpose of resetting a user's password - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param email The email address to validate. - * \param sendAttempt - * The server will only send an email if the ``send_attempt`` - * is a number greater than the most recent one which it has seen, - * scoped to that ``email`` + ``client_secret`` pair. This is to - * avoid repeatedly sending the same email in the case of request - * retries between the POSTing user and the identity server. - * The client should increment this value if they desire a new - * email (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenToResetPasswordEmailJob( - const QString& clientSecret, const QString& email, - int sendAttempt, const QString& idServer, - const QString& nextLink = {}); - ~RequestTokenToResetPasswordEmailJob() override; - - // Result properties - - /// An email was sent to the given address. - const Sid& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Requests a validation token be sent to the given phone number for the - /// purpose of resetting a user's password. - /// - /// Proxies the Identity Service API ``validate/msisdn/requestToken``, but - /// first checks that the given phone number **is** associated with an - /// account on this homeserver. This API should be used to request - /// validation tokens when authenticating for the - /// `account/password` endpoint. This API's parameters and response are - /// identical to that of the HS API |/register/msisdn/requestToken|_ except - /// that `M_THREEPID_NOT_FOUND` may be returned if no account matching the - /// given phone number could be found. The server may instead send an - /// SMS message to the given address prompting the user to create an - /// account. `M_THREEPID_IN_USE` may not be returned. - /// - /// .. |/register/msisdn/requestToken| replace:: - /// ``/register/msisdn/requestToken`` - /// - /// .. _/register/msisdn/requestToken: - /// #post-matrix-client-r0-register-email-requesttoken - class RequestTokenToResetPasswordMSISDNJob : public BaseJob - { - public: - /*! Requests a validation token be sent to the given phone number for the purpose of resetting a user's password. - * \param clientSecret - * A unique string generated by the client, and used to identify the - * validation attempt. It must be a string consisting of the - * characters - * ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - * it must not be empty. \param country The two-letter uppercase ISO - * country code that the number in - * ``phone_number`` should be parsed as if it were dialled from. - * \param phoneNumber - * The phone number to validate. - * \param sendAttempt - * The server will only send an SMS if the ``send_attempt`` is a - * number greater than the most recent one which it has seen, - * scoped to that ``country`` + ``phone_number`` + ``client_secret`` - * triple. This is to avoid repeatedly sending the same SMS in - * the case of request retries between the POSTing user and the - * identity server. The client should increment this value if - * they desire a new SMS (e.g. a reminder) to be sent. - * \param idServer - * The hostname of the identity server to communicate with. May - * optionally include a port. - * \param nextLink - * Optional. When the validation is completed, the identity - * server will redirect the user to this URL. - */ - explicit RequestTokenToResetPasswordMSISDNJob( - const QString& clientSecret, const QString& country, - const QString& phoneNumber, int sendAttempt, - const QString& idServer, const QString& nextLink = {}); - ~RequestTokenToResetPasswordMSISDNJob() override; - - // Result properties - - /// An SMS message was sent to the given phone number. - const Sid& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Deactivate a user's account. - /// - /// Deactivate the user's account, removing all ability for the user to - /// login again. - /// - /// This API endpoint uses the `User-Interactive Authentication API`_. - /// - /// An access token should be submitted to this endpoint if the client has - /// an active session. - /// - /// The homeserver may change the flows available depending on whether a - /// valid access token is provided. - class DeactivateAccountJob : public BaseJob - { - public: - /*! Deactivate a user's account. - * \param auth - * Additional authentication information for the user-interactive - * authentication API. - */ - explicit DeactivateAccountJob( - const Omittable<AuthenticationData>& auth = none); - }; - - /// Checks to see if a username is available on the server. - /// - /// Checks to see if a username is available, and valid, for the server. - /// - /// The server should check to ensure that, at the time of the request, the - /// username requested is available for use. This includes verifying that an - /// application service has not claimed the username and that the username - /// fits the server's desired requirements (for example, a server could - /// dictate that it does not permit usernames with underscores). - /// - /// Matrix clients may wish to use this API prior to attempting - /// registration, however the clients must also be aware that using this API - /// does not normally reserve the username. This can mean that the username - /// becomes unavailable between checking its availability and attempting to - /// register it. - class CheckUsernameAvailabilityJob : public BaseJob - { - public: - /*! Checks to see if a username is available on the server. - * \param username - * The username to check the availability of. - */ - explicit CheckUsernameAvailabilityJob(const QString& username); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * CheckUsernameAvailabilityJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& username); - - ~CheckUsernameAvailabilityJob() override; - - // Result properties - - /// A flag to indicate that the username is available. This should - /// always be ``true`` when the server replies with 200 OK. - Omittable<bool> available() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/report_content.cpp b/lib/csapi/report_content.cpp index f7b74834..eb62cd12 100644 --- a/lib/csapi/report_content.cpp +++ b/lib/csapi/report_content.cpp @@ -14,9 +14,8 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); static const auto ReportContentJobName = QStringLiteral("ReportContentJob"); -ReportContentJob::ReportContentJob(const QString& roomId, - const QString& eventId, int score, - const QString& reason) +ReportContentJob::ReportContentJob(const QString& roomId, const QString& eventId, + int score, const QString& reason) : BaseJob(HttpVerb::Post, ReportContentJobName, basePath % "/rooms/" % roomId % "/report/" % eventId) { diff --git a/lib/csapi/report_content.h b/lib/csapi/report_content.h index aca5b30b..c545d800 100644 --- a/lib/csapi/report_content.h +++ b/lib/csapi/report_content.h @@ -4,32 +4,36 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" +namespace QMatrixClient +{ -namespace QMatrixClient { - // Operations +// Operations + +/// Reports an event as inappropriate. +/*! + * Reports an event as inappropriate to the server, which may then notify + * the appropriate people. + */ +class ReportContentJob : public BaseJob +{ +public: + /*! Reports an event as inappropriate. + * \param roomId + * The room in which the event being reported is located. + * \param eventId + * The event to report. + * \param score + * The score to rate this content as where -100 is most offensive + * and 0 is inoffensive. + * \param reason + * The reason the content is being reported. May be blank. + */ + explicit ReportContentJob(const QString& roomId, const QString& eventId, + int score, const QString& reason); +}; - /// Reports an event as inappropriate. - /// - /// Reports an event as inappropriate to the server, which may then notify - /// the appropriate people. - class ReportContentJob : public BaseJob - { - public: - /*! Reports an event as inappropriate. - * \param roomId - * The room in which the event being reported is located. - * \param eventId - * The event to report. - * \param score - * The score to rate this content as where -100 is most offensive - * and 0 is inoffensive. - * \param reason - * The reason the content is being reported. May be blank. - */ - explicit ReportContentJob(const QString& roomId, const QString& eventId, - int score, const QString& reason); - }; } // namespace QMatrixClient diff --git a/lib/csapi/room_send.cpp b/lib/csapi/room_send.cpp index 2bf42522..a29dd99a 100644 --- a/lib/csapi/room_send.cpp +++ b/lib/csapi/room_send.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class SendMessageJob::Private { - public: +public: QString eventId; }; @@ -23,9 +23,8 @@ static const auto SendMessageJobName = QStringLiteral("SendMessageJob"); SendMessageJob::SendMessageJob(const QString& roomId, const QString& eventType, const QString& txnId, const QJsonObject& body) : BaseJob(HttpVerb::Put, SendMessageJobName, - basePath % "/rooms/" % roomId % "/send/" % eventType % "/" - % txnId), - d(new Private) + basePath % "/rooms/" % roomId % "/send/" % eventType % "/" % txnId) + , d(new Private) { setRequestData(Data(toJson(body))); } @@ -38,5 +37,6 @@ BaseJob::Status SendMessageJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("event_id"_ls), d->eventId); + return Success; } diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h index 38e246e1..aa2efd79 100644 --- a/lib/csapi/room_send.h +++ b/lib/csapi/room_send.h @@ -8,54 +8,58 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Send a message event to the given room. - /// - /// This endpoint is used to send a message event to a room. Message events - /// allow access to historical events and pagination, making them suited - /// for "once-off" activity in a room. - /// - /// The body of the request should be the content object of the event; the - /// fields in this object will vary depending on the type of event. See - /// `Room Events`_ for the m. event specification. - class SendMessageJob : public BaseJob - { - public: - /*! Send a message event to the given room. - * \param roomId - * The room to send the event to. - * \param eventType - * The type of event to send. - * \param txnId - * The transaction ID for this event. Clients should generate an - * ID unique across requests with the same access token; it will be - * used by the server to ensure idempotency of requests. - * \param body - * This endpoint is used to send a message event to a room. Message - * events allow access to historical events and pagination, making them - * suited for "once-off" activity in a room. - * - * The body of the request should be the content object of the event; - * the fields in this object will vary depending on the type of event. - * See `Room Events`_ for the m. event specification. - */ - explicit SendMessageJob(const QString& roomId, const QString& eventType, - const QString& txnId, - const QJsonObject& body = {}); - ~SendMessageJob() override; - - // Result properties - - /// A unique identifier for the event. - const QString& eventId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Send a message event to the given room. +/*! + * This endpoint is used to send a message event to a room. Message events + * allow access to historical events and pagination, making them suited + * for "once-off" activity in a room. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the m. event specification. + */ +class SendMessageJob : public BaseJob +{ +public: + /*! Send a message event to the given room. + * \param roomId + * The room to send the event to. + * \param eventType + * The type of event to send. + * \param txnId + * The transaction ID for this event. Clients should generate an + * ID unique across requests with the same access token; it will be + * used by the server to ensure idempotency of requests. + * \param body + * This endpoint is used to send a message event to a room. Message events + * allow access to historical events and pagination, making them suited + * for "once-off" activity in a room. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the m. event specification. + */ + explicit SendMessageJob(const QString& roomId, const QString& eventType, + const QString& txnId, const QJsonObject& body = {}); + + ~SendMessageJob() override; + + // Result properties + + /// A unique identifier for the event. + const QString& eventId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/room_state.cpp b/lib/csapi/room_state.cpp index 668a2931..ef4afcd0 100644 --- a/lib/csapi/room_state.cpp +++ b/lib/csapi/room_state.cpp @@ -14,12 +14,12 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class SetRoomStateWithKeyJob::Private { - public: +public: QString eventId; }; static const auto SetRoomStateWithKeyJobName = - QStringLiteral("SetRoomStateWithKeyJob"); + QStringLiteral("SetRoomStateWithKeyJob"); SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, @@ -27,8 +27,8 @@ SetRoomStateWithKeyJob::SetRoomStateWithKeyJob(const QString& roomId, const QJsonObject& body) : BaseJob(HttpVerb::Put, SetRoomStateWithKeyJobName, basePath % "/rooms/" % roomId % "/state/" % eventType % "/" - % stateKey), - d(new Private) + % stateKey) + , d(new Private) { setRequestData(Data(toJson(body))); } @@ -41,23 +41,23 @@ BaseJob::Status SetRoomStateWithKeyJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("event_id"_ls), d->eventId); + return Success; } class SetRoomStateJob::Private { - public: +public: QString eventId; }; static const auto SetRoomStateJobName = QStringLiteral("SetRoomStateJob"); -SetRoomStateJob::SetRoomStateJob(const QString& roomId, - const QString& eventType, +SetRoomStateJob::SetRoomStateJob(const QString& roomId, const QString& eventType, const QJsonObject& body) : BaseJob(HttpVerb::Put, SetRoomStateJobName, - basePath % "/rooms/" % roomId % "/state/" % eventType), - d(new Private) + basePath % "/rooms/" % roomId % "/state/" % eventType) + , d(new Private) { setRequestData(Data(toJson(body))); } @@ -70,5 +70,6 @@ BaseJob::Status SetRoomStateJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("event_id"_ls), d->eventId); + return Success; } diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h index 07407af2..6ddd5594 100644 --- a/lib/csapi/room_state.h +++ b/lib/csapi/room_state.h @@ -8,120 +8,122 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Send a state event to the given room. - /// - /// State events can be sent using this endpoint. These events will be - /// overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all - /// match. - /// - /// Requests to this endpoint **cannot use transaction IDs** - /// like other ``PUT`` paths because they cannot be differentiated from the - /// ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - /// - /// The body of the request should be the content object of the event; the - /// fields in this object will vary depending on the type of event. See - /// `Room Events`_ for the ``m.`` event specification. - class SetRoomStateWithKeyJob : public BaseJob - { - public: - /*! Send a state event to the given room. - * \param roomId - * The room to set the state in - * \param eventType - * The type of event to send. - * \param stateKey - * The state_key for the state to send. Defaults to the empty string. - * \param body - * State events can be sent using this endpoint. These events will be - * overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` - * all match. - * - * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from - * the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - * - * The body of the request should be the content object of the event; - * the fields in this object will vary depending on the type of event. - * See `Room Events`_ for the ``m.`` event specification. - */ - explicit SetRoomStateWithKeyJob(const QString& roomId, - const QString& eventType, - const QString& stateKey, - const QJsonObject& body = {}); - ~SetRoomStateWithKeyJob() override; - - // Result properties - - /// A unique identifier for the event. - const QString& eventId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Send a state event to the given room. - /// - /// State events can be sent using this endpoint. This endpoint is - /// equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` - /// with an empty `stateKey`. Previous state events with matching - /// `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be - /// overwritten. - /// - /// Requests to this endpoint **cannot use transaction IDs** - /// like other ``PUT`` paths because they cannot be differentiated from the - /// ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - /// - /// The body of the request should be the content object of the event; the - /// fields in this object will vary depending on the type of event. See - /// `Room Events`_ for the ``m.`` event specification. - class SetRoomStateJob : public BaseJob - { - public: - /*! Send a state event to the given room. - * \param roomId - * The room to set the state in - * \param eventType - * The type of event to send. - * \param body - * State events can be sent using this endpoint. This endpoint is - * equivalent to calling - * `/rooms/{roomId}/state/{eventType}/{stateKey}` with an empty - * `stateKey`. Previous state events with matching - * `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be - * overwritten. - * - * Requests to this endpoint **cannot use transaction IDs** - * like other ``PUT`` paths because they cannot be differentiated from - * the - * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. - * - * The body of the request should be the content object of the event; - * the fields in this object will vary depending on the type of event. - * See `Room Events`_ for the ``m.`` event specification. - */ - explicit SetRoomStateJob(const QString& roomId, - const QString& eventType, - const QJsonObject& body = {}); - ~SetRoomStateJob() override; - - // Result properties - - /// A unique identifier for the event. - const QString& eventId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Send a state event to the given room. +/*! + * State events can be sent using this endpoint. These events will be + * overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all + * match. + * + * Requests to this endpoint **cannot use transaction IDs** + * like other ``PUT`` paths because they cannot be differentiated from the + * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the ``m.`` event specification. + */ +class SetRoomStateWithKeyJob : public BaseJob +{ +public: + /*! Send a state event to the given room. + * \param roomId + * The room to set the state in + * \param eventType + * The type of event to send. + * \param stateKey + * The state_key for the state to send. Defaults to the empty string. + * \param body + * State events can be sent using this endpoint. These events will be + * overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all + * match. + * + * Requests to this endpoint **cannot use transaction IDs** + * like other ``PUT`` paths because they cannot be differentiated from the + * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the ``m.`` event specification. + */ + explicit SetRoomStateWithKeyJob(const QString& roomId, + const QString& eventType, + const QString& stateKey, + const QJsonObject& body = {}); + + ~SetRoomStateWithKeyJob() override; + + // Result properties + + /// A unique identifier for the event. + const QString& eventId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Send a state event to the given room. +/*! + * State events can be sent using this endpoint. This endpoint is + * equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` + * with an empty `stateKey`. Previous state events with matching + * `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be overwritten. + * + * Requests to this endpoint **cannot use transaction IDs** + * like other ``PUT`` paths because they cannot be differentiated from the + * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the ``m.`` event specification. + */ +class SetRoomStateJob : public BaseJob +{ +public: + /*! Send a state event to the given room. + * \param roomId + * The room to set the state in + * \param eventType + * The type of event to send. + * \param body + * State events can be sent using this endpoint. This endpoint is + * equivalent to calling `/rooms/{roomId}/state/{eventType}/{stateKey}` + * with an empty `stateKey`. Previous state events with matching + * `<roomId>` and `<eventType>`, and empty `<stateKey>`, will be + * overwritten. + * + * Requests to this endpoint **cannot use transaction IDs** + * like other ``PUT`` paths because they cannot be differentiated from the + * ``state_key``. Furthermore, ``POST`` is unsupported on state paths. + * + * The body of the request should be the content object of the event; the + * fields in this object will vary depending on the type of event. See + * `Room Events`_ for the ``m.`` event specification. + */ + explicit SetRoomStateJob(const QString& roomId, const QString& eventType, + const QJsonObject& body = {}); + + ~SetRoomStateJob() override; + + // Result properties + + /// A unique identifier for the event. + const QString& eventId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/room_upgrades.cpp b/lib/csapi/room_upgrades.cpp index b9f285e8..a72304d1 100644 --- a/lib/csapi/room_upgrades.cpp +++ b/lib/csapi/room_upgrades.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class UpgradeRoomJob::Private { - public: +public: QString replacementRoom; }; @@ -22,8 +22,8 @@ static const auto UpgradeRoomJobName = QStringLiteral("UpgradeRoomJob"); UpgradeRoomJob::UpgradeRoomJob(const QString& roomId, const QString& newVersion) : BaseJob(HttpVerb::Post, UpgradeRoomJobName, - basePath % "/rooms/" % roomId % "/upgrade"), - d(new Private) + basePath % "/rooms/" % roomId % "/upgrade") + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("new_version"), newVersion); @@ -41,8 +41,9 @@ BaseJob::Status UpgradeRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("replacement_room"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'replacement_room' not found in the response" }; fromJson(json.value("replacement_room"_ls), d->replacementRoom); + return Success; } diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h index df30a562..94520aca 100644 --- a/lib/csapi/room_upgrades.h +++ b/lib/csapi/room_upgrades.h @@ -6,37 +6,39 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Upgrades a room to a new room version. - /// - /// Upgrades the given room to a particular room version, migrating as much - /// data as possible over to the new room. See the `room_upgrades - /// <#room-upgrades>`_ module for more information on what this entails. - class UpgradeRoomJob : public BaseJob - { - public: - /*! Upgrades a room to a new room version. - * \param roomId - * The ID of the room to upgrade. - * \param newVersion - * The new version for the room. - */ - explicit UpgradeRoomJob(const QString& roomId, - const QString& newVersion); - ~UpgradeRoomJob() override; - - // Result properties - - /// The ID of the new room. - const QString& replacementRoom() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Upgrades a room to a new room version. +/*! + * Upgrades the given room to a particular room version. + */ +class UpgradeRoomJob : public BaseJob +{ +public: + /*! Upgrades a room to a new room version. + * \param roomId + * The ID of the room to upgrade. + * \param newVersion + * The new version for the room. + */ + explicit UpgradeRoomJob(const QString& roomId, const QString& newVersion); + + ~UpgradeRoomJob() override; + + // Result properties + + /// The ID of the new room. + const QString& replacementRoom() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/rooms.cpp b/lib/csapi/rooms.cpp index b9de276c..280e8d59 100644 --- a/lib/csapi/rooms.cpp +++ b/lib/csapi/rooms.cpp @@ -14,16 +14,16 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetOneRoomEventJob::Private { - public: +public: EventPtr data; }; QUrl GetOneRoomEventJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/event/" - % eventId); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" + % roomId % "/event/" + % eventId); } static const auto GetOneRoomEventJobName = QStringLiteral("GetOneRoomEventJob"); @@ -31,10 +31,9 @@ static const auto GetOneRoomEventJobName = QStringLiteral("GetOneRoomEventJob"); GetOneRoomEventJob::GetOneRoomEventJob(const QString& roomId, const QString& eventId) : BaseJob(HttpVerb::Get, GetOneRoomEventJobName, - basePath % "/rooms/" % roomId % "/event/" % eventId), - d(new Private) -{ -} + basePath % "/rooms/" % roomId % "/event/" % eventId) + , d(new Private) +{} GetOneRoomEventJob::~GetOneRoomEventJob() = default; @@ -52,42 +51,40 @@ QUrl GetRoomStateWithKeyJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" % roomId % "/state/" - % eventType % "/" % stateKey); + % eventType % "/" % stateKey); } static const auto GetRoomStateWithKeyJobName = - QStringLiteral("GetRoomStateWithKeyJob"); + QStringLiteral("GetRoomStateWithKeyJob"); GetRoomStateWithKeyJob::GetRoomStateWithKeyJob(const QString& roomId, const QString& eventType, const QString& stateKey) : BaseJob(HttpVerb::Get, GetRoomStateWithKeyJobName, basePath % "/rooms/" % roomId % "/state/" % eventType % "/" - % stateKey) -{ -} + % stateKey) +{} QUrl GetRoomStateByTypeJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& eventType) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId % "/state/" - % eventType); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/rooms/" + % roomId % "/state/" + % eventType); } static const auto GetRoomStateByTypeJobName = - QStringLiteral("GetRoomStateByTypeJob"); + QStringLiteral("GetRoomStateByTypeJob"); GetRoomStateByTypeJob::GetRoomStateByTypeJob(const QString& roomId, const QString& eventType) : BaseJob(HttpVerb::Get, GetRoomStateByTypeJobName, basePath % "/rooms/" % roomId % "/state/" % eventType) -{ -} +{} class GetRoomStateJob::Private { - public: +public: StateEvents data; }; @@ -101,10 +98,9 @@ static const auto GetRoomStateJobName = QStringLiteral("GetRoomStateJob"); GetRoomStateJob::GetRoomStateJob(const QString& roomId) : BaseJob(HttpVerb::Get, GetRoomStateJobName, - basePath % "/rooms/" % roomId % "/state"), - d(new Private) -{ -} + basePath % "/rooms/" % roomId % "/state") + , d(new Private) +{} GetRoomStateJob::~GetRoomStateJob() = default; @@ -118,7 +114,7 @@ BaseJob::Status GetRoomStateJob::parseJson(const QJsonDocument& data) class GetMembersByRoomJob::Private { - public: +public: EventsArray<RoomMemberEvent> chunk; }; @@ -139,12 +135,12 @@ QUrl GetMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId, const QString& notMembership) { return BaseJob::makeRequestUrl( - std::move(baseUrl), basePath % "/rooms/" % roomId % "/members", - queryToGetMembersByRoom(at, membership, notMembership)); + std::move(baseUrl), basePath % "/rooms/" % roomId % "/members", + queryToGetMembersByRoom(at, membership, notMembership)); } static const auto GetMembersByRoomJobName = - QStringLiteral("GetMembersByRoomJob"); + QStringLiteral("GetMembersByRoomJob"); GetMembersByRoomJob::GetMembersByRoomJob(const QString& roomId, const QString& at, @@ -152,10 +148,9 @@ GetMembersByRoomJob::GetMembersByRoomJob(const QString& roomId, const QString& notMembership) : BaseJob(HttpVerb::Get, GetMembersByRoomJobName, basePath % "/rooms/" % roomId % "/members", - queryToGetMembersByRoom(at, membership, notMembership)), - d(new Private) -{ -} + queryToGetMembersByRoom(at, membership, notMembership)) + , d(new Private) +{} GetMembersByRoomJob::~GetMembersByRoomJob() = default; @@ -168,46 +163,48 @@ BaseJob::Status GetMembersByRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("chunk"_ls), d->chunk); + return Success; } -namespace QMatrixClient { - // Converters - - template <> - struct JsonObjectConverter<GetJoinedMembersByRoomJob::RoomMember> { - static void fillFrom(const QJsonObject& jo, - GetJoinedMembersByRoomJob::RoomMember& result) - { - fromJson(jo.value("display_name"_ls), result.displayName); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetJoinedMembersByRoomJob::RoomMember> +{ + static void fillFrom(const QJsonObject& jo, + GetJoinedMembersByRoomJob::RoomMember& result) + { + fromJson(jo.value("display_name"_ls), result.displayName); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } +}; + } // namespace QMatrixClient class GetJoinedMembersByRoomJob::Private { - public: +public: QHash<QString, RoomMember> joined; }; QUrl GetJoinedMembersByRoomJob::makeRequestUrl(QUrl baseUrl, const QString& roomId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/rooms/" % roomId - % "/joined_members"); + return BaseJob::makeRequestUrl( + std::move(baseUrl), basePath % "/rooms/" % roomId % "/joined_members"); } static const auto GetJoinedMembersByRoomJobName = - QStringLiteral("GetJoinedMembersByRoomJob"); + QStringLiteral("GetJoinedMembersByRoomJob"); GetJoinedMembersByRoomJob::GetJoinedMembersByRoomJob(const QString& roomId) : BaseJob(HttpVerb::Get, GetJoinedMembersByRoomJobName, - basePath % "/rooms/" % roomId % "/joined_members"), - d(new Private) -{ -} + basePath % "/rooms/" % roomId % "/joined_members") + , d(new Private) +{} GetJoinedMembersByRoomJob::~GetJoinedMembersByRoomJob() = default; @@ -221,5 +218,6 @@ BaseJob::Status GetJoinedMembersByRoomJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("joined"_ls), d->joined); + return Success; } diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h index 20bdd20e..29d7808e 100644 --- a/lib/csapi/rooms.h +++ b/lib/csapi/rooms.h @@ -4,261 +4,269 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "events/eventloader.h" #include "events/roommemberevent.h" -#include <QtCore/QHash> +#include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +#include <QtCore/QHash> - /// Get a single event by event ID. - /// - /// Get a single event based on ``roomId/eventId``. You must have permission - /// to retrieve this event e.g. by being a member in the room for this - /// event. - class GetOneRoomEventJob : public BaseJob - { - public: - /*! Get a single event by event ID. - * \param roomId - * The ID of the room the event is in. - * \param eventId - * The event ID to get. - */ - explicit GetOneRoomEventJob(const QString& roomId, - const QString& eventId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetOneRoomEventJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& eventId); - - ~GetOneRoomEventJob() override; - - // Result properties - - /// The full event. - EventPtr&& data(); - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ - /// Get the state identified by the type and key. - /// - /// Looks up the contents of a state event in a room. If the user is - /// joined to the room then the state is taken from the current - /// state of the room. If the user has left the room then the state is - /// taken from the state of the room when they left. - class GetRoomStateWithKeyJob : public BaseJob - { - public: - /*! Get the state identified by the type and key. - * \param roomId - * The room to look up the state in. - * \param eventType - * The type of state to look up. - * \param stateKey - * The key of the state to look up. - */ - explicit GetRoomStateWithKeyJob(const QString& roomId, - const QString& eventType, - const QString& stateKey); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomStateWithKeyJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& eventType, - const QString& stateKey); - }; +// Operations - /// Get the state identified by the type, with the empty state key. - /// - /// Looks up the contents of a state event in a room. If the user is - /// joined to the room then the state is taken from the current - /// state of the room. If the user has left the room then the state is - /// taken from the state of the room when they left. - /// - /// This looks up the state event with the empty state key. - class GetRoomStateByTypeJob : public BaseJob - { - public: - /*! Get the state identified by the type, with the empty state key. - * \param roomId - * The room to look up the state in. - * \param eventType - * The type of state to look up. - */ - explicit GetRoomStateByTypeJob(const QString& roomId, - const QString& eventType); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomStateByTypeJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, +/// Get a single event by event ID. +/*! + * Get a single event based on ``roomId/eventId``. You must have permission to + * retrieve this event e.g. by being a member in the room for this event. + */ +class GetOneRoomEventJob : public BaseJob +{ +public: + /*! Get a single event by event ID. + * \param roomId + * The ID of the room the event is in. + * \param eventId + * The event ID to get. + */ + explicit GetOneRoomEventJob(const QString& roomId, const QString& eventId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetOneRoomEventJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& eventId); + + ~GetOneRoomEventJob() override; + + // Result properties + + /// The full event. + EventPtr&& data(); + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get the state identified by the type and key. +/*! + * Looks up the contents of a state event in a room. If the user is + * joined to the room then the state is taken from the current + * state of the room. If the user has left the room then the state is + * taken from the state of the room when they left. + */ +class GetRoomStateWithKeyJob : public BaseJob +{ +public: + /*! Get the state identified by the type and key. + * \param roomId + * The room to look up the state in. + * \param eventType + * The type of state to look up. + * \param stateKey + * The key of the state to look up. + */ + explicit GetRoomStateWithKeyJob(const QString& roomId, + const QString& eventType, + const QString& stateKey); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomStateWithKeyJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& eventType, + const QString& stateKey); +}; + +/// Get the state identified by the type, with the empty state key. +/*! + * Looks up the contents of a state event in a room. If the user is + * joined to the room then the state is taken from the current + * state of the room. If the user has left the room then the state is + * taken from the state of the room when they left. + * + * This looks up the state event with the empty state key. + */ +class GetRoomStateByTypeJob : public BaseJob +{ +public: + /*! Get the state identified by the type, with the empty state key. + * \param roomId + * The room to look up the state in. + * \param eventType + * The type of state to look up. + */ + explicit GetRoomStateByTypeJob(const QString& roomId, const QString& eventType); - }; - /// Get all state events in the current state of a room. - /// - /// Get the state events for the current state of a room. - class GetRoomStateJob : public BaseJob - { - public: - /*! Get all state events in the current state of a room. - * \param roomId - * The room to look up the state for. - */ - explicit GetRoomStateJob(const QString& roomId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomStateJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - - ~GetRoomStateJob() override; - - // Result properties - - /// If the user is a member of the room this will be the - /// current state of the room as a list of events. If the user - /// has left the room then this will be the state of the room - /// when they left as a list of events. - StateEvents&& data(); - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomStateByTypeJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& eventType); +}; + +/// Get all state events in the current state of a room. +/*! + * Get the state events for the current state of a room. + */ +class GetRoomStateJob : public BaseJob +{ +public: + /*! Get all state events in the current state of a room. + * \param roomId + * The room to look up the state for. + */ + explicit GetRoomStateJob(const QString& roomId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomStateJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); + + ~GetRoomStateJob() override; + + // Result properties + + /// If the user is a member of the room this will be the + /// current state of the room as a list of events. If the user + /// has left the room then this will be the state of the room + /// when they left as a list of events. + StateEvents&& data(); + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Get the m.room.member events for the room. +/*! + * Get the list of members for this room. + */ +class GetMembersByRoomJob : public BaseJob +{ +public: + /*! Get the m.room.member events for the room. + * \param roomId + * The room to get the member events for. + * \param at + * The token defining the timeline position as-of which to return + * the list of members. This token can be obtained from a batch token + * returned for each room by the sync API, or from + * a ``start``/``end`` token returned by a ``/messages`` request. + * \param membership + * Only return users with the specified membership + * \param notMembership + * Only return users with membership state other than specified + */ + explicit GetMembersByRoomJob(const QString& roomId, const QString& at = {}, + const QString& membership = {}, + const QString& notMembership = {}); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetMembersByRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, + const QString& at = {}, + const QString& membership = {}, + const QString& notMembership = {}); + + ~GetMembersByRoomJob() override; + + // Result properties - /// Get the m.room.member events for the room. - /// /// Get the list of members for this room. - class GetMembersByRoomJob : public BaseJob - { - public: - /*! Get the m.room.member events for the room. - * \param roomId - * The room to get the member events for. - * \param at - * The token defining the timeline position as-of which to return - * the list of members. This token can be obtained from a batch token - * returned for each room by the sync API, or from - * a ``start``/``end`` token returned by a ``/messages`` request. - * \param membership - * Only return users with the specified membership - * \param notMembership - * Only return users with membership state other than specified - */ - explicit GetMembersByRoomJob(const QString& roomId, - const QString& at = {}, - const QString& membership = {}, - const QString& notMembership = {}); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetMembersByRoomJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId, - const QString& at = {}, - const QString& membership = {}, - const QString& notMembership = {}); - - ~GetMembersByRoomJob() override; - - // Result properties - - /// Get the list of members for this room. - EventsArray<RoomMemberEvent>&& chunk(); - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; + EventsArray<RoomMemberEvent>&& chunk(); + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Gets the list of currently joined users and their profile data. +/*! + * This API returns a map of MXIDs to member info objects for members of the + * room. The current user must be in the room for it to work, unless it is an + * Application Service in which case any of the AS's users must be in the room. + * This API is primarily for Application Services and should be faster to + * respond than ``/members`` as it can be implemented more efficiently on the + * server. + */ +class GetJoinedMembersByRoomJob : public BaseJob +{ +public: + // Inner data structures - /// Gets the list of currently joined users and their profile data. - /// /// This API returns a map of MXIDs to member info objects for members of /// the room. The current user must be in the room for it to work, unless it /// is an Application Service in which case any of the AS's users must be in /// the room. This API is primarily for Application Services and should be /// faster to respond than ``/members`` as it can be implemented more /// efficiently on the server. - class GetJoinedMembersByRoomJob : public BaseJob + struct RoomMember { - public: - // Inner data structures - - /// This API returns a map of MXIDs to member info objects for members - /// of the room. The current user must be in the room for it to work, - /// unless it is an Application Service in which case any of the AS's - /// users must be in the room. This API is primarily for Application - /// Services and should be faster to respond than ``/members`` as it can - /// be implemented more efficiently on the server. - struct RoomMember { - /// The display name of the user this object is representing. - QString displayName; - /// The mxc avatar url of the user this object is representing. - QString avatarUrl; - }; - - // Construction/destruction - - /*! Gets the list of currently joined users and their profile data. - * \param roomId - * The room to get the members of. - */ - explicit GetJoinedMembersByRoomJob(const QString& roomId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetJoinedMembersByRoomJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); - - ~GetJoinedMembersByRoomJob() override; - - // Result properties - - /// A map from user ID to a RoomMember object. - const QHash<QString, RoomMember>& joined() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The display name of the user this object is representing. + QString displayName; + /// The mxc avatar url of the user this object is representing. + QString avatarUrl; }; + + // Construction/destruction + + /*! Gets the list of currently joined users and their profile data. + * \param roomId + * The room to get the members of. + */ + explicit GetJoinedMembersByRoomJob(const QString& roomId); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetJoinedMembersByRoomJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& roomId); + + ~GetJoinedMembersByRoomJob() override; + + // Result properties + + /// A map from user ID to a RoomMember object. + const QHash<QString, RoomMember>& joined() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/search.cpp b/lib/csapi/search.cpp index 22aaf616..ee1fa70c 100644 --- a/lib/csapi/search.cpp +++ b/lib/csapi/search.cpp @@ -12,126 +12,143 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<SearchJob::IncludeEventContext> { - static void dumpTo(QJsonObject& jo, - const SearchJob::IncludeEventContext& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("before_limit"), - pod.beforeLimit); - addParam<IfNotEmpty>(jo, QStringLiteral("after_limit"), - pod.afterLimit); - addParam<IfNotEmpty>(jo, QStringLiteral("include_profile"), - pod.includeProfile); - } - }; - - template <> struct JsonObjectConverter<SearchJob::Group> { - static void dumpTo(QJsonObject& jo, const SearchJob::Group& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key); - } - }; - - template <> struct JsonObjectConverter<SearchJob::Groupings> { - static void dumpTo(QJsonObject& jo, const SearchJob::Groupings& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("group_by"), pod.groupBy); - } - }; - - template <> struct JsonObjectConverter<SearchJob::RoomEventsCriteria> { - static void dumpTo(QJsonObject& jo, - const SearchJob::RoomEventsCriteria& pod) - { - addParam<>(jo, QStringLiteral("search_term"), pod.searchTerm); - addParam<IfNotEmpty>(jo, QStringLiteral("keys"), pod.keys); - addParam<IfNotEmpty>(jo, QStringLiteral("filter"), pod.filter); - addParam<IfNotEmpty>(jo, QStringLiteral("order_by"), pod.orderBy); - addParam<IfNotEmpty>(jo, QStringLiteral("event_context"), - pod.eventContext); - addParam<IfNotEmpty>(jo, QStringLiteral("include_state"), - pod.includeState); - addParam<IfNotEmpty>(jo, QStringLiteral("groupings"), - pod.groupings); - } - }; - - template <> struct JsonObjectConverter<SearchJob::Categories> { - static void dumpTo(QJsonObject& jo, const SearchJob::Categories& pod) - { - addParam<IfNotEmpty>(jo, QStringLiteral("room_events"), - pod.roomEvents); - } - }; - - template <> struct JsonObjectConverter<SearchJob::UserProfile> { - static void fillFrom(const QJsonObject& jo, - SearchJob::UserProfile& result) - { - fromJson(jo.value("displayname"_ls), result.displayname); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); - } - }; - - template <> struct JsonObjectConverter<SearchJob::EventContext> { - static void fillFrom(const QJsonObject& jo, - SearchJob::EventContext& result) - { - fromJson(jo.value("start"_ls), result.begin); - fromJson(jo.value("end"_ls), result.end); - fromJson(jo.value("profile_info"_ls), result.profileInfo); - fromJson(jo.value("events_before"_ls), result.eventsBefore); - fromJson(jo.value("events_after"_ls), result.eventsAfter); - } - }; - - template <> struct JsonObjectConverter<SearchJob::Result> { - static void fillFrom(const QJsonObject& jo, SearchJob::Result& result) - { - fromJson(jo.value("rank"_ls), result.rank); - fromJson(jo.value("result"_ls), result.result); - fromJson(jo.value("context"_ls), result.context); - } - }; - - template <> struct JsonObjectConverter<SearchJob::GroupValue> { - static void fillFrom(const QJsonObject& jo, - SearchJob::GroupValue& result) - { - fromJson(jo.value("next_batch"_ls), result.nextBatch); - fromJson(jo.value("order"_ls), result.order); - fromJson(jo.value("results"_ls), result.results); - } - }; - - template <> struct JsonObjectConverter<SearchJob::ResultRoomEvents> { - static void fillFrom(const QJsonObject& jo, - SearchJob::ResultRoomEvents& result) - { - fromJson(jo.value("count"_ls), result.count); - fromJson(jo.value("highlights"_ls), result.highlights); - fromJson(jo.value("results"_ls), result.results); - fromJson(jo.value("state"_ls), result.state); - fromJson(jo.value("groups"_ls), result.groups); - fromJson(jo.value("next_batch"_ls), result.nextBatch); - } - }; - - template <> struct JsonObjectConverter<SearchJob::ResultCategories> { - static void fillFrom(const QJsonObject& jo, - SearchJob::ResultCategories& result) - { - fromJson(jo.value("room_events"_ls), result.roomEvents); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<SearchJob::IncludeEventContext> +{ + static void dumpTo(QJsonObject& jo, + const SearchJob::IncludeEventContext& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("before_limit"), + pod.beforeLimit); + addParam<IfNotEmpty>(jo, QStringLiteral("after_limit"), pod.afterLimit); + addParam<IfNotEmpty>(jo, QStringLiteral("include_profile"), + pod.includeProfile); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::Group> +{ + static void dumpTo(QJsonObject& jo, const SearchJob::Group& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("key"), pod.key); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::Groupings> +{ + static void dumpTo(QJsonObject& jo, const SearchJob::Groupings& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("group_by"), pod.groupBy); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::RoomEventsCriteria> +{ + static void dumpTo(QJsonObject& jo, const SearchJob::RoomEventsCriteria& pod) + { + addParam<>(jo, QStringLiteral("search_term"), pod.searchTerm); + addParam<IfNotEmpty>(jo, QStringLiteral("keys"), pod.keys); + addParam<IfNotEmpty>(jo, QStringLiteral("filter"), pod.filter); + addParam<IfNotEmpty>(jo, QStringLiteral("order_by"), pod.orderBy); + addParam<IfNotEmpty>(jo, QStringLiteral("event_context"), + pod.eventContext); + addParam<IfNotEmpty>(jo, QStringLiteral("include_state"), + pod.includeState); + addParam<IfNotEmpty>(jo, QStringLiteral("groupings"), pod.groupings); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::Categories> +{ + static void dumpTo(QJsonObject& jo, const SearchJob::Categories& pod) + { + addParam<IfNotEmpty>(jo, QStringLiteral("room_events"), pod.roomEvents); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::UserProfile> +{ + static void fillFrom(const QJsonObject& jo, SearchJob::UserProfile& result) + { + fromJson(jo.value("displayname"_ls), result.displayname); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::EventContext> +{ + static void fillFrom(const QJsonObject& jo, SearchJob::EventContext& result) + { + fromJson(jo.value("start"_ls), result.begin); + fromJson(jo.value("end"_ls), result.end); + fromJson(jo.value("profile_info"_ls), result.profileInfo); + fromJson(jo.value("events_before"_ls), result.eventsBefore); + fromJson(jo.value("events_after"_ls), result.eventsAfter); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::Result> +{ + static void fillFrom(const QJsonObject& jo, SearchJob::Result& result) + { + fromJson(jo.value("rank"_ls), result.rank); + fromJson(jo.value("result"_ls), result.result); + fromJson(jo.value("context"_ls), result.context); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::GroupValue> +{ + static void fillFrom(const QJsonObject& jo, SearchJob::GroupValue& result) + { + fromJson(jo.value("next_batch"_ls), result.nextBatch); + fromJson(jo.value("order"_ls), result.order); + fromJson(jo.value("results"_ls), result.results); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::ResultRoomEvents> +{ + static void fillFrom(const QJsonObject& jo, + SearchJob::ResultRoomEvents& result) + { + fromJson(jo.value("count"_ls), result.count); + fromJson(jo.value("highlights"_ls), result.highlights); + fromJson(jo.value("results"_ls), result.results); + fromJson(jo.value("state"_ls), result.state); + fromJson(jo.value("groups"_ls), result.groups); + fromJson(jo.value("next_batch"_ls), result.nextBatch); + } +}; + +template <> +struct JsonObjectConverter<SearchJob::ResultCategories> +{ + static void fillFrom(const QJsonObject& jo, + SearchJob::ResultCategories& result) + { + fromJson(jo.value("room_events"_ls), result.roomEvents); + } +}; + } // namespace QMatrixClient class SearchJob::Private { - public: +public: ResultCategories searchCategories; }; @@ -147,8 +164,8 @@ static const auto SearchJobName = QStringLiteral("SearchJob"); SearchJob::SearchJob(const Categories& searchCategories, const QString& nextBatch) : BaseJob(HttpVerb::Post, SearchJobName, basePath % "/search", - queryToSearch(nextBatch)), - d(new Private) + queryToSearch(nextBatch)) + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("search_categories"), searchCategories); @@ -166,8 +183,9 @@ BaseJob::Status SearchJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("search_categories"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'search_categories' not found in the response" }; fromJson(json.value("search_categories"_ls), d->searchCategories); + return Success; } diff --git a/lib/csapi/search.h b/lib/csapi/search.h index 41761030..f965a72a 100644 --- a/lib/csapi/search.h +++ b/lib/csapi/search.h @@ -4,193 +4,200 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/room_event_filter.h" + #include "events/eventloader.h" +#include "jobs/basejob.h" + #include <QtCore/QHash> #include <QtCore/QVector> + #include <unordered_map> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Perform a server-side search. - /// - /// Performs a full text search across different categories. - class SearchJob : public BaseJob +// Operations + +/// Perform a server-side search. +/*! + * Performs a full text search across different categories. + */ +class SearchJob : public BaseJob +{ +public: + // Inner data structures + + /// Configures whether any context for the eventsreturned are included in + /// the response. + struct IncludeEventContext { - public: - // Inner data structures - - /// Configures whether any context for the events - /// returned are included in the response. - struct IncludeEventContext { - /// How many events before the result are - /// returned. By default, this is ``5``. - Omittable<int> beforeLimit; - /// How many events after the result are - /// returned. By default, this is ``5``. - Omittable<int> afterLimit; - /// Requests that the server returns the - /// historic profile information for the users - /// that sent the events that were returned. - /// By default, this is ``false``. - Omittable<bool> includeProfile; - }; - - /// Configuration for group. - struct Group { - /// Key that defines the group. - QString key; - }; - - /// Requests that the server partitions the result set - /// based on the provided list of keys. - struct Groupings { - /// List of groups to request. - QVector<Group> groupBy; - }; + /// How many events before the result arereturned. By default, this is + /// ``5``. + Omittable<int> beforeLimit; + /// How many events after the result arereturned. By default, this is + /// ``5``. + Omittable<int> afterLimit; + /// Requests that the server returns thehistoric profile information for + /// the usersthat sent the events that were returned.By default, this is + /// ``false``. + Omittable<bool> includeProfile; + }; + /// Configuration for group. + struct Group + { + /// Key that defines the group. + QString key; + }; + + /// Requests that the server partitions the result setbased on the provided + /// list of keys. + struct Groupings + { + /// List of groups to request. + QVector<Group> groupBy; + }; + + /// Mapping of category name to search criteria. + struct RoomEventsCriteria + { + /// The string to search events for + QString searchTerm; + /// The keys to search. Defaults to all. + QStringList keys; + /// This takes a `filter`_. + Omittable<RoomEventFilter> filter; + /// The order in which to search for results.By default, this is + /// ``"rank"``. + QString orderBy; + /// Configures whether any context for the eventsreturned are included + /// in the response. + Omittable<IncludeEventContext> eventContext; + /// Requests the server return the current state foreach room returned. + Omittable<bool> includeState; + /// Requests that the server partitions the result setbased on the + /// provided list of keys. + Omittable<Groupings> groupings; + }; + + /// Describes which categories to search in and their criteria. + struct Categories + { /// Mapping of category name to search criteria. - struct RoomEventsCriteria { - /// The string to search events for - QString searchTerm; - /// The keys to search. Defaults to all. - QStringList keys; - /// This takes a `filter`_. - Omittable<RoomEventFilter> filter; - /// The order in which to search for results. - /// By default, this is ``"rank"``. - QString orderBy; - /// Configures whether any context for the events - /// returned are included in the response. - Omittable<IncludeEventContext> eventContext; - /// Requests the server return the current state for - /// each room returned. - Omittable<bool> includeState; - /// Requests that the server partitions the result set - /// based on the provided list of keys. - Omittable<Groupings> groupings; - }; - - /// Describes which categories to search in and their criteria. - struct Categories { - /// Mapping of category name to search criteria. - Omittable<RoomEventsCriteria> roomEvents; - }; + Omittable<RoomEventsCriteria> roomEvents; + }; + /// Performs a full text search across different categories. + struct UserProfile + { + /// Performs a full text search across different categories. + QString displayname; /// Performs a full text search across different categories. - struct UserProfile { - /// Performs a full text search across different categories. - QString displayname; - /// Performs a full text search across different categories. - QString avatarUrl; - }; + QString avatarUrl; + }; + + /// Context for result, if requested. + struct EventContext + { + /// Pagination token for the start of the chunk + QString begin; + /// Pagination token for the end of the chunk + QString end; + /// The historic profile information of theusers that sent the events + /// returned.The ``string`` key is the user ID for whichthe profile + /// belongs to. + QHash<QString, UserProfile> profileInfo; + /// Events just before the result. + RoomEvents eventsBefore; + /// Events just after the result. + RoomEvents eventsAfter; + }; + /// The result object. + struct Result + { + /// A number that describes how closely this result matches the search. + /// Higher is closer. + Omittable<double> rank; + /// The event that matched. + RoomEventPtr result; /// Context for result, if requested. - struct EventContext { - /// Pagination token for the start of the chunk - QString begin; - /// Pagination token for the end of the chunk - QString end; - /// The historic profile information of the - /// users that sent the events returned. - /// - /// The ``string`` key is the user ID for which - /// the profile belongs to. - QHash<QString, UserProfile> profileInfo; - /// Events just before the result. - RoomEvents eventsBefore; - /// Events just after the result. - RoomEvents eventsAfter; - }; - - /// The result object. - struct Result { - /// A number that describes how closely this result matches the - /// search. Higher is closer. - Omittable<double> rank; - /// The event that matched. - RoomEventPtr result; - /// Context for result, if requested. - Omittable<EventContext> context; - }; - - /// The results for a particular group value. - struct GroupValue { - /// Token that can be used to get the next batch - /// of results in the group, by passing as the - /// `next_batch` parameter to the next call. If - /// this field is absent, there are no more - /// results in this group. - QString nextBatch; - /// Key that can be used to order different - /// groups. - Omittable<int> order; - /// Which results are in this group. - QStringList results; - }; + Omittable<EventContext> context; + }; + + /// The results for a particular group value. + struct GroupValue + { + /// Token that can be used to get the next batchof results in the group, + /// by passing as the`next_batch` parameter to the next call. Ifthis + /// field is absent, there are no moreresults in this group. + QString nextBatch; + /// Key that can be used to order differentgroups. + Omittable<int> order; + /// Which results are in this group. + QStringList results; + }; + /// Mapping of category name to search criteria. + struct ResultRoomEvents + { + /// An approximate count of the total number of results found. + Omittable<int> count; + /// List of words which should be highlighted, useful for stemming which + /// may change the query terms. + QStringList highlights; + /// List of results in the requested order. + std::vector<Result> results; + /// The current state for every room in the results.This is included if + /// the request had the``include_state`` key set with a value of + /// ``true``.The ``string`` key is the room ID for which the + /// ``StateEvent`` array belongs to. + std::unordered_map<QString, StateEvents> state; + /// Any groups that were requested.The outer ``string`` key is the group + /// key requested (eg: ``room_id``or ``sender``). The inner ``string`` + /// key is the grouped value (eg: a room's ID or a user's ID). + QHash<QString, QHash<QString, GroupValue>> groups; + /// Token that can be used to get the next batch ofresults, by passing + /// as the `next_batch` parameter tothe next call. If this field is + /// absent, there are nomore results. + QString nextBatch; + }; + + /// Describes which categories to search in and their criteria. + struct ResultCategories + { /// Mapping of category name to search criteria. - struct ResultRoomEvents { - /// An approximate count of the total number of results found. - Omittable<int> count; - /// List of words which should be highlighted, useful for stemming - /// which may change the query terms. - QStringList highlights; - /// List of results in the requested order. - std::vector<Result> results; - /// The current state for every room in the results. - /// This is included if the request had the - /// ``include_state`` key set with a value of ``true``. - /// - /// The ``string`` key is the room ID for which the ``State - /// Event`` array belongs to. - std::unordered_map<QString, StateEvents> state; - /// Any groups that were requested. - /// - /// The outer ``string`` key is the group key requested (eg: - /// ``room_id`` or ``sender``). The inner ``string`` key is the - /// grouped value (eg: a room's ID or a user's ID). - QHash<QString, QHash<QString, GroupValue>> groups; - /// Token that can be used to get the next batch of - /// results, by passing as the `next_batch` parameter to - /// the next call. If this field is absent, there are no - /// more results. - QString nextBatch; - }; - - /// Describes which categories to search in and their criteria. - struct ResultCategories { - /// Mapping of category name to search criteria. - Omittable<ResultRoomEvents> roomEvents; - }; - - // Construction/destruction - - /*! Perform a server-side search. - * \param searchCategories - * Describes which categories to search in and their criteria. - * \param nextBatch - * The point to return events from. If given, this should be a - * ``next_batch`` result from a previous call to this endpoint. - */ - explicit SearchJob(const Categories& searchCategories, - const QString& nextBatch = {}); - ~SearchJob() override; - - // Result properties - - /// Describes which categories to search in and their criteria. - const ResultCategories& searchCategories() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + Omittable<ResultRoomEvents> roomEvents; }; + + // Construction/destruction + + /*! Perform a server-side search. + * \param searchCategories + * Describes which categories to search in and their criteria. + * \param nextBatch + * The point to return events from. If given, this should be a + * ``next_batch`` result from a previous call to this endpoint. + */ + explicit SearchJob(const Categories& searchCategories, + const QString& nextBatch = {}); + + ~SearchJob() override; + + // Result properties + + /// Describes which categories to search in and their criteria. + const ResultCategories& searchCategories() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/sso_login_redirect.cpp b/lib/csapi/sso_login_redirect.cpp index 92084d52..b1dc3674 100644 --- a/lib/csapi/sso_login_redirect.cpp +++ b/lib/csapi/sso_login_redirect.cpp @@ -32,5 +32,4 @@ RedirectToSSOJob::RedirectToSSOJob(const QString& redirectUrl) : BaseJob(HttpVerb::Get, RedirectToSSOJobName, basePath % "/login/sso/redirect", queryToRedirectToSSO(redirectUrl), {}, false) -{ -} +{} diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h index 2b0d3f65..af9e7780 100644 --- a/lib/csapi/sso_login_redirect.h +++ b/lib/csapi/sso_login_redirect.h @@ -6,31 +6,35 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ - /// Redirect the user's browser to the SSO interface. - /// - /// A web-based Matrix client should instruct the user's browser to - /// navigate to this endpoint in order to log in via SSO. - /// - /// The server MUST respond with an HTTP redirect to the SSO interface. - class RedirectToSSOJob : public BaseJob - { - public: - /*! Redirect the user's browser to the SSO interface. - * \param redirectUrl - * URI to which the user will be redirected after the homeserver has - * authenticated the user with SSO. - */ - explicit RedirectToSSOJob(const QString& redirectUrl); +// Operations + +/// Redirect the user's browser to the SSO interface. +/*! + * A web-based Matrix client should instruct the user's browser to + * navigate to this endpoint in order to log in via SSO. + * + * The server MUST respond with an HTTP redirect to the SSO interface. + */ +class RedirectToSSOJob : public BaseJob +{ +public: + /*! Redirect the user's browser to the SSO interface. + * \param redirectUrl + * URI to which the user will be redirected after the homeserver has + * authenticated the user with SSO. + */ + explicit RedirectToSSOJob(const QString& redirectUrl); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * RedirectToSSOJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& redirectUrl); +}; - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * RedirectToSSOJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& redirectUrl); - }; } // namespace QMatrixClient diff --git a/lib/csapi/tags.cpp b/lib/csapi/tags.cpp index 9f591447..13c933e5 100644 --- a/lib/csapi/tags.cpp +++ b/lib/csapi/tags.cpp @@ -12,40 +12,43 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<GetRoomTagsJob::Tag> { - static void fillFrom(QJsonObject jo, GetRoomTagsJob::Tag& result) - { - fromJson(jo.take("order"_ls), result.order); - fromJson(jo, result.additionalProperties); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<GetRoomTagsJob::Tag> +{ + static void fillFrom(QJsonObject jo, GetRoomTagsJob::Tag& result) + { + fromJson(jo.take("order"_ls), result.order); + fromJson(jo, result.additionalProperties); + } +}; + } // namespace QMatrixClient class GetRoomTagsJob::Private { - public: +public: QHash<QString, Tag> tags; }; QUrl GetRoomTagsJob::makeRequestUrl(QUrl baseUrl, const QString& userId, const QString& roomId) { - return BaseJob::makeRequestUrl(std::move(baseUrl), - basePath % "/user/" % userId % "/rooms/" - % roomId % "/tags"); + return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/user/" + % userId % "/rooms/" + % roomId % "/tags"); } static const auto GetRoomTagsJobName = QStringLiteral("GetRoomTagsJob"); GetRoomTagsJob::GetRoomTagsJob(const QString& userId, const QString& roomId) : BaseJob(HttpVerb::Get, GetRoomTagsJobName, - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags"), - d(new Private) -{ -} + basePath % "/user/" % userId % "/rooms/" % roomId % "/tags") + , d(new Private) +{} GetRoomTagsJob::~GetRoomTagsJob() = default; @@ -58,6 +61,7 @@ BaseJob::Status GetRoomTagsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); fromJson(json.value("tags"_ls), d->tags); + return Success; } @@ -66,8 +70,7 @@ static const auto SetRoomTagJobName = QStringLiteral("SetRoomTagJob"); SetRoomTagJob::SetRoomTagJob(const QString& userId, const QString& roomId, const QString& tag, Omittable<float> order) : BaseJob(HttpVerb::Put, SetRoomTagJobName, - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" - % tag) + basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" % tag) { QJsonObject _data; addParam<IfNotEmpty>(_data, QStringLiteral("order"), order); @@ -79,7 +82,7 @@ QUrl DeleteRoomTagJob::makeRequestUrl(QUrl baseUrl, const QString& userId, { return BaseJob::makeRequestUrl(std::move(baseUrl), basePath % "/user/" % userId % "/rooms/" - % roomId % "/tags/" % tag); + % roomId % "/tags/" % tag); } static const auto DeleteRoomTagJobName = QStringLiteral("DeleteRoomTagJob"); @@ -87,7 +90,5 @@ static const auto DeleteRoomTagJobName = QStringLiteral("DeleteRoomTagJob"); DeleteRoomTagJob::DeleteRoomTagJob(const QString& userId, const QString& roomId, const QString& tag) : BaseJob(HttpVerb::Delete, DeleteRoomTagJobName, - basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" - % tag) -{ -} + basePath % "/user/" % userId % "/rooms/" % roomId % "/tags/" % tag) +{} diff --git a/lib/csapi/tags.h b/lib/csapi/tags.h index dc683ef7..dc20cd3d 100644 --- a/lib/csapi/tags.h +++ b/lib/csapi/tags.h @@ -4,115 +4,123 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QHash> #include <QtCore/QVariant> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// List the tags for a room. +/*! + * List the tags set by a user on a room. + */ +class GetRoomTagsJob : public BaseJob +{ +public: + // Inner data structures - /// List the tags for a room. - /// /// List the tags set by a user on a room. - class GetRoomTagsJob : public BaseJob + struct Tag { - public: - // Inner data structures + /// A number in a range ``[0,1]`` describing a relativeposition of the + /// room under the given tag. + Omittable<float> order; /// List the tags set by a user on a room. - struct Tag { - /// A number in a range ``[0,1]`` describing a relative - /// position of the room under the given tag. - Omittable<float> order; - /// List the tags set by a user on a room. - QVariantHash additionalProperties; - }; - - // Construction/destruction - - /*! List the tags for a room. - * \param userId - * The id of the user to get tags for. The access token must be - * authorized to make requests for this user ID. - * \param roomId - * The ID of the room to get tags for. - */ - explicit GetRoomTagsJob(const QString& userId, const QString& roomId); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetRoomTagsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, - const QString& roomId); - - ~GetRoomTagsJob() override; - - // Result properties + QVariantHash additionalProperties; + }; - /// List the tags set by a user on a room. - const QHash<QString, Tag>& tags() const; + // Construction/destruction - protected: - Status parseJson(const QJsonDocument& data) override; + /*! List the tags for a room. + * \param userId + * The id of the user to get tags for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to get tags for. + */ + explicit GetRoomTagsJob(const QString& userId, const QString& roomId); - private: - class Private; - QScopedPointer<Private> d; - }; + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetRoomTagsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& roomId); - /// Add a tag to a room. - /// - /// Add a tag to the room. - class SetRoomTagJob : public BaseJob - { - public: - /*! Add a tag to a room. - * \param userId - * The id of the user to add a tag for. The access token must be - * authorized to make requests for this user ID. - * \param roomId - * The ID of the room to add a tag to. - * \param tag - * The tag to add. - * \param order - * A number in a range ``[0,1]`` describing a relative - * position of the room under the given tag. - */ - explicit SetRoomTagJob(const QString& userId, const QString& roomId, - const QString& tag, - Omittable<float> order = none); - }; + ~GetRoomTagsJob() override; + + // Result properties + + /// List the tags set by a user on a room. + const QHash<QString, Tag>& tags() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Add a tag to a room. +/*! + * Add a tag to the room. + */ +class SetRoomTagJob : public BaseJob +{ +public: + /*! Add a tag to a room. + * \param userId + * The id of the user to add a tag for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to add a tag to. + * \param tag + * The tag to add. + * \param order + * A number in a range ``[0,1]`` describing a relative + * position of the room under the given tag. + */ + explicit SetRoomTagJob(const QString& userId, const QString& roomId, + const QString& tag, Omittable<float> order = none); +}; + +/// Remove a tag from the room. +/*! + * Remove a tag from the room. + */ +class DeleteRoomTagJob : public BaseJob +{ +public: + /*! Remove a tag from the room. + * \param userId + * The id of the user to remove a tag for. The access token must be + * authorized to make requests for this user ID. + * \param roomId + * The ID of the room to remove a tag from. + * \param tag + * The tag to remove. + */ + explicit DeleteRoomTagJob(const QString& userId, const QString& roomId, + const QString& tag); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * DeleteRoomTagJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, + const QString& roomId, const QString& tag); +}; - /// Remove a tag from the room. - /// - /// Remove a tag from the room. - class DeleteRoomTagJob : public BaseJob - { - public: - /*! Remove a tag from the room. - * \param userId - * The id of the user to remove a tag for. The access token must be - * authorized to make requests for this user ID. - * \param roomId - * The ID of the room to remove a tag from. - * \param tag - * The tag to remove. - */ - explicit DeleteRoomTagJob(const QString& userId, const QString& roomId, - const QString& tag); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * DeleteRoomTagJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userId, - const QString& roomId, const QString& tag); - }; } // namespace QMatrixClient diff --git a/lib/csapi/third_party_lookup.cpp b/lib/csapi/third_party_lookup.cpp index 339a0d90..986ead01 100644 --- a/lib/csapi/third_party_lookup.cpp +++ b/lib/csapi/third_party_lookup.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetProtocolsJob::Private { - public: +public: QHash<QString, ThirdPartyProtocol> data; }; @@ -28,10 +28,9 @@ static const auto GetProtocolsJobName = QStringLiteral("GetProtocolsJob"); GetProtocolsJob::GetProtocolsJob() : BaseJob(HttpVerb::Get, GetProtocolsJobName, - basePath % "/thirdparty/protocols"), - d(new Private) -{ -} + basePath % "/thirdparty/protocols") + , d(new Private) +{} GetProtocolsJob::~GetProtocolsJob() = default; @@ -48,7 +47,7 @@ BaseJob::Status GetProtocolsJob::parseJson(const QJsonDocument& data) class GetProtocolMetadataJob::Private { - public: +public: ThirdPartyProtocol data; }; @@ -56,18 +55,17 @@ QUrl GetProtocolMetadataJob::makeRequestUrl(QUrl baseUrl, const QString& protocol) { return BaseJob::makeRequestUrl( - std::move(baseUrl), basePath % "/thirdparty/protocol/" % protocol); + std::move(baseUrl), basePath % "/thirdparty/protocol/" % protocol); } static const auto GetProtocolMetadataJobName = - QStringLiteral("GetProtocolMetadataJob"); + QStringLiteral("GetProtocolMetadataJob"); GetProtocolMetadataJob::GetProtocolMetadataJob(const QString& protocol) : BaseJob(HttpVerb::Get, GetProtocolMetadataJobName, - basePath % "/thirdparty/protocol/" % protocol), - d(new Private) -{ -} + basePath % "/thirdparty/protocol/" % protocol) + , d(new Private) +{} GetProtocolMetadataJob::~GetProtocolMetadataJob() = default; @@ -84,7 +82,7 @@ BaseJob::Status GetProtocolMetadataJob::parseJson(const QJsonDocument& data) class QueryLocationByProtocolJob::Private { - public: +public: QVector<ThirdPartyLocation> data; }; @@ -99,22 +97,21 @@ QUrl QueryLocationByProtocolJob::makeRequestUrl(QUrl baseUrl, const QString& protocol, const QString& searchFields) { - return BaseJob::makeRequestUrl( - std::move(baseUrl), basePath % "/thirdparty/location/" % protocol, - queryToQueryLocationByProtocol(searchFields)); + return BaseJob::makeRequestUrl(std::move(baseUrl), + basePath % "/thirdparty/location/" % protocol, + queryToQueryLocationByProtocol(searchFields)); } static const auto QueryLocationByProtocolJobName = - QStringLiteral("QueryLocationByProtocolJob"); + QStringLiteral("QueryLocationByProtocolJob"); QueryLocationByProtocolJob::QueryLocationByProtocolJob( - const QString& protocol, const QString& searchFields) + const QString& protocol, const QString& searchFields) : BaseJob(HttpVerb::Get, QueryLocationByProtocolJobName, basePath % "/thirdparty/location/" % protocol, - queryToQueryLocationByProtocol(searchFields)), - d(new Private) -{ -} + queryToQueryLocationByProtocol(searchFields)) + , d(new Private) +{} QueryLocationByProtocolJob::~QueryLocationByProtocolJob() = default; @@ -131,7 +128,7 @@ BaseJob::Status QueryLocationByProtocolJob::parseJson(const QJsonDocument& data) class QueryUserByProtocolJob::Private { - public: +public: QVector<ThirdPartyUser> data; }; @@ -152,16 +149,15 @@ QUrl QueryUserByProtocolJob::makeRequestUrl(QUrl baseUrl, } static const auto QueryUserByProtocolJobName = - QStringLiteral("QueryUserByProtocolJob"); + QStringLiteral("QueryUserByProtocolJob"); QueryUserByProtocolJob::QueryUserByProtocolJob(const QString& protocol, const QString& fields) : BaseJob(HttpVerb::Get, QueryUserByProtocolJobName, basePath % "/thirdparty/user/" % protocol, - queryToQueryUserByProtocol(fields)), - d(new Private) -{ -} + queryToQueryUserByProtocol(fields)) + , d(new Private) +{} QueryUserByProtocolJob::~QueryUserByProtocolJob() = default; @@ -178,7 +174,7 @@ BaseJob::Status QueryUserByProtocolJob::parseJson(const QJsonDocument& data) class QueryLocationByAliasJob::Private { - public: +public: QVector<ThirdPartyLocation> data; }; @@ -197,15 +193,14 @@ QUrl QueryLocationByAliasJob::makeRequestUrl(QUrl baseUrl, const QString& alias) } static const auto QueryLocationByAliasJobName = - QStringLiteral("QueryLocationByAliasJob"); + QStringLiteral("QueryLocationByAliasJob"); QueryLocationByAliasJob::QueryLocationByAliasJob(const QString& alias) : BaseJob(HttpVerb::Get, QueryLocationByAliasJobName, basePath % "/thirdparty/location", - queryToQueryLocationByAlias(alias)), - d(new Private) -{ -} + queryToQueryLocationByAlias(alias)) + , d(new Private) +{} QueryLocationByAliasJob::~QueryLocationByAliasJob() = default; @@ -222,7 +217,7 @@ BaseJob::Status QueryLocationByAliasJob::parseJson(const QJsonDocument& data) class QueryUserByIDJob::Private { - public: +public: QVector<ThirdPartyUser> data; }; @@ -244,10 +239,9 @@ static const auto QueryUserByIDJobName = QStringLiteral("QueryUserByIDJob"); QueryUserByIDJob::QueryUserByIDJob(const QString& userid) : BaseJob(HttpVerb::Get, QueryUserByIDJobName, - basePath % "/thirdparty/user", queryToQueryUserByID(userid)), - d(new Private) -{ -} + basePath % "/thirdparty/user", queryToQueryUserByID(userid)) + , d(new Private) +{} QueryUserByIDJob::~QueryUserByIDJob() = default; diff --git a/lib/csapi/third_party_lookup.h b/lib/csapi/third_party_lookup.h index 91bc79e5..d25c1cf3 100644 --- a/lib/csapi/third_party_lookup.h +++ b/lib/csapi/third_party_lookup.h @@ -4,244 +4,253 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/../application-service/definitions/location.h" #include "csapi/../application-service/definitions/protocol.h" #include "csapi/../application-service/definitions/user.h" + +#include "jobs/basejob.h" + #include <QtCore/QHash> #include <QtCore/QVector> -namespace QMatrixClient { - // Operations - - /// Retrieve metadata about all protocols that a homeserver supports. - /// - /// Fetches the overall metadata about protocols supported by the - /// homeserver. Includes both the available protocols and all fields - /// required for queries against each protocol. - class GetProtocolsJob : public BaseJob - { - public: - explicit GetProtocolsJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetProtocolsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetProtocolsJob() override; - - // Result properties - - /// The protocols supported by the homeserver. - const QHash<QString, ThirdPartyProtocol>& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Retrieve metadata about a specific protocol that the homeserver - /// supports. - /// - /// Fetches the metadata from the homeserver about a particular third party - /// protocol. - class GetProtocolMetadataJob : public BaseJob - { - public: - /*! Retrieve metadata about a specific protocol that the homeserver supports. - * \param protocol - * The name of the protocol. - */ - explicit GetProtocolMetadataJob(const QString& protocol); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetProtocolMetadataJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol); - - ~GetProtocolMetadataJob() override; - - // Result properties - - /// The protocol was found and metadata returned. - const ThirdPartyProtocol& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Retrieve Matrix-side portals rooms leading to a third party location. - /// - /// Requesting this endpoint with a valid protocol name results in a list - /// of successful mapping results in a JSON array. Each result contains - /// objects to represent the Matrix room or rooms that represent a portal - /// to this third party network. Each has the Matrix room alias string, - /// an identifier for the particular third party network protocol, and an - /// object containing the network-specific fields that comprise this - /// identifier. It should attempt to canonicalise the identifier as much - /// as reasonably possible given the network type. - class QueryLocationByProtocolJob : public BaseJob - { - public: - /*! Retrieve Matrix-side portals rooms leading to a third party location. - * \param protocol - * The protocol used to communicate to the third party network. - * \param searchFields - * One or more custom fields to help identify the third party - * location. - */ - explicit QueryLocationByProtocolJob(const QString& protocol, - const QString& searchFields = {}); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * QueryLocationByProtocolJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, - const QString& searchFields = {}); - - ~QueryLocationByProtocolJob() override; - - // Result properties - - /// At least one portal room was found. - const QVector<ThirdPartyLocation>& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Retrieve the Matrix User ID of a corresponding third party user. - /// - /// Retrieve a Matrix User ID linked to a user on the third party service, - /// given a set of user parameters. - class QueryUserByProtocolJob : public BaseJob - { - public: - /*! Retrieve the Matrix User ID of a corresponding third party user. - * \param protocol - * The name of the protocol. - * \param fields - * One or more custom fields that are passed to the AS to help - * identify the user. - */ - explicit QueryUserByProtocolJob(const QString& protocol, - const QString& fields = {}); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * QueryUserByProtocolJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, - const QString& fields = {}); - - ~QueryUserByProtocolJob() override; - - // Result properties - - /// The Matrix User IDs found with the given parameters. - const QVector<ThirdPartyUser>& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Reverse-lookup third party locations given a Matrix room alias. - /// - /// Retrieve an array of third party network locations from a Matrix room - /// alias. - class QueryLocationByAliasJob : public BaseJob - { - public: - /*! Reverse-lookup third party locations given a Matrix room alias. - * \param alias - * The Matrix room alias to look up. - */ - explicit QueryLocationByAliasJob(const QString& alias); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * QueryLocationByAliasJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& alias); - - ~QueryLocationByAliasJob() override; - - // Result properties - - /// All found third party locations. - const QVector<ThirdPartyLocation>& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; - - /// Reverse-lookup third party users given a Matrix User ID. - /// - /// Retrieve an array of third party users from a Matrix User ID. - class QueryUserByIDJob : public BaseJob - { - public: - /*! Reverse-lookup third party users given a Matrix User ID. - * \param userid - * The Matrix User ID to look up. - */ - explicit QueryUserByIDJob(const QString& userid); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * QueryUserByIDJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& userid); - - ~QueryUserByIDJob() override; - - // Result properties - - /// An array of third party users. - const QVector<ThirdPartyUser>& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Retrieve metadata about all protocols that a homeserver supports. +/*! + * Fetches the overall metadata about protocols supported by the + * homeserver. Includes both the available protocols and all fields + * required for queries against each protocol. + */ +class GetProtocolsJob : public BaseJob +{ +public: + explicit GetProtocolsJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetProtocolsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetProtocolsJob() override; + + // Result properties + + /// The protocols supported by the homeserver. + const QHash<QString, ThirdPartyProtocol>& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Retrieve metadata about a specific protocol that the homeserver supports. +/*! + * Fetches the metadata from the homeserver about a particular third party + * protocol. + */ +class GetProtocolMetadataJob : public BaseJob +{ +public: + /*! Retrieve metadata about a specific protocol that the homeserver + * supports. \param protocol The name of the protocol. + */ + explicit GetProtocolMetadataJob(const QString& protocol); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetProtocolMetadataJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol); + + ~GetProtocolMetadataJob() override; + + // Result properties + + /// The protocol was found and metadata returned. + const ThirdPartyProtocol& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Retrieve Matrix-side portals rooms leading to a third party location. +/*! + * Requesting this endpoint with a valid protocol name results in a list + * of successful mapping results in a JSON array. Each result contains + * objects to represent the Matrix room or rooms that represent a portal + * to this third party network. Each has the Matrix room alias string, + * an identifier for the particular third party network protocol, and an + * object containing the network-specific fields that comprise this + * identifier. It should attempt to canonicalise the identifier as much + * as reasonably possible given the network type. + */ +class QueryLocationByProtocolJob : public BaseJob +{ +public: + /*! Retrieve Matrix-side portals rooms leading to a third party location. + * \param protocol + * The protocol used to communicate to the third party network. + * \param searchFields + * One or more custom fields to help identify the third party + * location. + */ + explicit QueryLocationByProtocolJob(const QString& protocol, + const QString& searchFields = {}); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * QueryLocationByProtocolJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, + const QString& searchFields = {}); + + ~QueryLocationByProtocolJob() override; + + // Result properties + + /// At least one portal room was found. + const QVector<ThirdPartyLocation>& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Retrieve the Matrix User ID of a corresponding third party user. +/*! + * Retrieve a Matrix User ID linked to a user on the third party service, given + * a set of user parameters. + */ +class QueryUserByProtocolJob : public BaseJob +{ +public: + /*! Retrieve the Matrix User ID of a corresponding third party user. + * \param protocol + * The name of the protocol. + * \param fields + * One or more custom fields that are passed to the AS to help identify + * the user. + */ + explicit QueryUserByProtocolJob(const QString& protocol, + const QString& fields = {}); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * QueryUserByProtocolJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& protocol, + const QString& fields = {}); + + ~QueryUserByProtocolJob() override; + + // Result properties + + /// The Matrix User IDs found with the given parameters. + const QVector<ThirdPartyUser>& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Reverse-lookup third party locations given a Matrix room alias. +/*! + * Retrieve an array of third party network locations from a Matrix room + * alias. + */ +class QueryLocationByAliasJob : public BaseJob +{ +public: + /*! Reverse-lookup third party locations given a Matrix room alias. + * \param alias + * The Matrix room alias to look up. + */ + explicit QueryLocationByAliasJob(const QString& alias); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * QueryLocationByAliasJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& alias); + + ~QueryLocationByAliasJob() override; + + // Result properties + + /// All found third party locations. + const QVector<ThirdPartyLocation>& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + +/// Reverse-lookup third party users given a Matrix User ID. +/*! + * Retrieve an array of third party users from a Matrix User ID. + */ +class QueryUserByIDJob : public BaseJob +{ +public: + /*! Reverse-lookup third party users given a Matrix User ID. + * \param userid + * The Matrix User ID to look up. + */ + explicit QueryUserByIDJob(const QString& userid); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * QueryUserByIDJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& userid); + + ~QueryUserByIDJob() override; + + // Result properties + + /// An array of third party users. + const QVector<ThirdPartyUser>& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/third_party_membership.h b/lib/csapi/third_party_membership.h index d1261567..36622c94 100644 --- a/lib/csapi/third_party_membership.h +++ b/lib/csapi/third_party_membership.h @@ -6,69 +6,72 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Invite a user to participate in a particular room. +/*! + * .. _invite-by-third-party-id-endpoint: + * + * *Note that there are two forms of this API, which are documented separately. + * This version of the API does not require that the inviter know the Matrix + * identifier of the invitee, and instead relies on third party identifiers. + * The homeserver uses an identity server to perform the mapping from + * third party identifier to a Matrix identifier. The other is documented in + * the* `joining rooms section`_. + * + * This API invites a user to participate in a particular room. + * They do not start participating in the room until they actually join the + * room. + * + * Only users currently in a particular room can invite other users to + * join that room. + * + * If the identity server did know the Matrix user identifier for the + * third party identifier, the homeserver will append a ``m.room.member`` + * event to the room. + * + * If the identity server does not know a Matrix user identifier for the + * passed third party identifier, the homeserver will issue an invitation + * which can be accepted upon providing proof of ownership of the third + * party identifier. This is achieved by the identity server generating a + * token, which it gives to the inviting homeserver. The homeserver will + * add an ``m.room.third_party_invite`` event into the graph for the room, + * containing that token. + * + * When the invitee binds the invited third party identifier to a Matrix + * user ID, the identity server will give the user a list of pending + * invitations, each containing: + * + * - The room ID to which they were invited + * + * - The token given to the homeserver + * + * - A signature of the token, signed with the identity server's private key + * + * - The matrix user ID who invited them to the room + * + * If a token is requested from the identity server, the homeserver will + * append a ``m.room.third_party_invite`` event to the room. + * + * .. _joining rooms section: `invite-by-user-id-endpoint`_ + */ +class InviteBy3PIDJob : public BaseJob +{ +public: + /*! Invite a user to participate in a particular room. + * \param roomId + * The room identifier (not alias) to which to invite the user. + * \param idServer + * The hostname+port of the identity server which should be used for third + * party identifier lookups. \param medium The kind of address being passed + * in the address field, for example ``email``. \param address The invitee's + * third party identifier. + */ + explicit InviteBy3PIDJob(const QString& roomId, const QString& idServer, + const QString& medium, const QString& address); +}; - /// Invite a user to participate in a particular room. - /// - /// .. _invite-by-third-party-id-endpoint: - /// - /// *Note that there are two forms of this API, which are documented - /// separately. This version of the API does not require that the inviter - /// know the Matrix identifier of the invitee, and instead relies on third - /// party identifiers. The homeserver uses an identity server to perform the - /// mapping from third party identifier to a Matrix identifier. The other is - /// documented in the* `joining rooms section`_. - /// - /// This API invites a user to participate in a particular room. - /// They do not start participating in the room until they actually join the - /// room. - /// - /// Only users currently in a particular room can invite other users to - /// join that room. - /// - /// If the identity server did know the Matrix user identifier for the - /// third party identifier, the homeserver will append a ``m.room.member`` - /// event to the room. - /// - /// If the identity server does not know a Matrix user identifier for the - /// passed third party identifier, the homeserver will issue an invitation - /// which can be accepted upon providing proof of ownership of the third - /// party identifier. This is achieved by the identity server generating a - /// token, which it gives to the inviting homeserver. The homeserver will - /// add an ``m.room.third_party_invite`` event into the graph for the room, - /// containing that token. - /// - /// When the invitee binds the invited third party identifier to a Matrix - /// user ID, the identity server will give the user a list of pending - /// invitations, each containing: - /// - /// - The room ID to which they were invited - /// - /// - The token given to the homeserver - /// - /// - A signature of the token, signed with the identity server's private - /// key - /// - /// - The matrix user ID who invited them to the room - /// - /// If a token is requested from the identity server, the homeserver will - /// append a ``m.room.third_party_invite`` event to the room. - /// - /// .. _joining rooms section: `invite-by-user-id-endpoint`_ - class InviteBy3PIDJob : public BaseJob - { - public: - /*! Invite a user to participate in a particular room. - * \param roomId - * The room identifier (not alias) to which to invite the user. - * \param idServer - * The hostname+port of the identity server which should be used for - * third party identifier lookups. \param medium The kind of address - * being passed in the address field, for example ``email``. \param - * address The invitee's third party identifier. - */ - explicit InviteBy3PIDJob(const QString& roomId, const QString& idServer, - const QString& medium, const QString& address); - }; } // namespace QMatrixClient diff --git a/lib/csapi/to_device.cpp b/lib/csapi/to_device.cpp index 241869f3..3fb17109 100644 --- a/lib/csapi/to_device.cpp +++ b/lib/csapi/to_device.cpp @@ -15,8 +15,8 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); static const auto SendToDeviceJobName = QStringLiteral("SendToDeviceJob"); SendToDeviceJob::SendToDeviceJob( - const QString& eventType, const QString& txnId, - const QHash<QString, QHash<QString, QJsonObject>>& messages) + const QString& eventType, const QString& txnId, + const QHash<QString, QHash<QString, QJsonObject>>& messages) : BaseJob(HttpVerb::Put, SendToDeviceJobName, basePath % "/sendToDevice/" % eventType % "/" % txnId) { diff --git a/lib/csapi/to_device.h b/lib/csapi/to_device.h index 83df13b7..e0bbbe28 100644 --- a/lib/csapi/to_device.h +++ b/lib/csapi/to_device.h @@ -9,31 +9,34 @@ #include <QtCore/QHash> #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Send an event to a given set of devices. +/*! + * This endpoint is used to send send-to-device events to a set of + * client devices. + */ +class SendToDeviceJob : public BaseJob +{ +public: + /*! Send an event to a given set of devices. + * \param eventType + * The type of event to send. + * \param txnId + * The transaction ID for this event. Clients should generate an + * ID unique across requests with the same access token; it will be + * used by the server to ensure idempotency of requests. + * \param messages + * The messages to send. A map from user ID, to a map from + * device ID to message body. The device ID may also be `*`, + * meaning all known devices for the user. + */ + explicit SendToDeviceJob( + const QString& eventType, const QString& txnId, + const QHash<QString, QHash<QString, QJsonObject>>& messages = {}); +}; - /// Send an event to a given set of devices. - /// - /// This endpoint is used to send send-to-device events to a set of - /// client devices. - class SendToDeviceJob : public BaseJob - { - public: - /*! Send an event to a given set of devices. - * \param eventType - * The type of event to send. - * \param txnId - * The transaction ID for this event. Clients should generate an - * ID unique across requests with the same access token; it will be - * used by the server to ensure idempotency of requests. - * \param messages - * The messages to send. A map from user ID, to a map from - * device ID to message body. The device ID may also be `*`, - * meaning all known devices for the user. - */ - explicit SendToDeviceJob( - const QString& eventType, const QString& txnId, - const QHash<QString, QHash<QString, QJsonObject>>& - messages = {}); - }; } // namespace QMatrixClient diff --git a/lib/csapi/typing.h b/lib/csapi/typing.h index 1f32a8e5..0c3f75a8 100644 --- a/lib/csapi/typing.h +++ b/lib/csapi/typing.h @@ -4,34 +4,38 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" +namespace QMatrixClient +{ -namespace QMatrixClient { - // Operations +// Operations + +/// Informs the server that the user has started or stopped typing. +/*! + * This tells the server that the user is typing for the next N + * milliseconds where N is the value specified in the ``timeout`` key. + * Alternatively, if ``typing`` is ``false``, it tells the server that the + * user has stopped typing. + */ +class SetTypingJob : public BaseJob +{ +public: + /*! Informs the server that the user has started or stopped typing. + * \param userId + * The user who has started to type. + * \param roomId + * The room in which the user is typing. + * \param typing + * Whether the user is typing or not. If ``false``, the ``timeout`` + * key can be omitted. + * \param timeout + * The length of time in milliseconds to mark this user as typing. + */ + explicit SetTypingJob(const QString& userId, const QString& roomId, + bool typing, Omittable<int> timeout = none); +}; - /// Informs the server that the user has started or stopped typing. - /// - /// This tells the server that the user is typing for the next N - /// milliseconds where N is the value specified in the ``timeout`` key. - /// Alternatively, if ``typing`` is ``false``, it tells the server that the - /// user has stopped typing. - class SetTypingJob : public BaseJob - { - public: - /*! Informs the server that the user has started or stopped typing. - * \param userId - * The user who has started to type. - * \param roomId - * The room in which the user is typing. - * \param typing - * Whether the user is typing or not. If ``false``, the ``timeout`` - * key can be omitted. - * \param timeout - * The length of time in milliseconds to mark this user as typing. - */ - explicit SetTypingJob(const QString& userId, const QString& roomId, - bool typing, Omittable<int> timeout = none); - }; } // namespace QMatrixClient diff --git a/lib/csapi/users.cpp b/lib/csapi/users.cpp index 6d005915..39b05a77 100644 --- a/lib/csapi/users.cpp +++ b/lib/csapi/users.cpp @@ -12,35 +12,39 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -namespace QMatrixClient { - // Converters - - template <> struct JsonObjectConverter<SearchUserDirectoryJob::User> { - static void fillFrom(const QJsonObject& jo, - SearchUserDirectoryJob::User& result) - { - fromJson(jo.value("user_id"_ls), result.userId); - fromJson(jo.value("display_name"_ls), result.displayName); - fromJson(jo.value("avatar_url"_ls), result.avatarUrl); - } - }; +// Converters +namespace QMatrixClient +{ + +template <> +struct JsonObjectConverter<SearchUserDirectoryJob::User> +{ + static void fillFrom(const QJsonObject& jo, + SearchUserDirectoryJob::User& result) + { + fromJson(jo.value("user_id"_ls), result.userId); + fromJson(jo.value("display_name"_ls), result.displayName); + fromJson(jo.value("avatar_url"_ls), result.avatarUrl); + } +}; + } // namespace QMatrixClient class SearchUserDirectoryJob::Private { - public: +public: QVector<User> results; bool limited; }; static const auto SearchUserDirectoryJobName = - QStringLiteral("SearchUserDirectoryJob"); + QStringLiteral("SearchUserDirectoryJob"); SearchUserDirectoryJob::SearchUserDirectoryJob(const QString& searchTerm, Omittable<int> limit) : BaseJob(HttpVerb::Post, SearchUserDirectoryJobName, - basePath % "/user_directory/search"), - d(new Private) + basePath % "/user_directory/search") + , d(new Private) { QJsonObject _data; addParam<>(_data, QStringLiteral("search_term"), searchTerm); @@ -50,8 +54,7 @@ SearchUserDirectoryJob::SearchUserDirectoryJob(const QString& searchTerm, SearchUserDirectoryJob::~SearchUserDirectoryJob() = default; -const QVector<SearchUserDirectoryJob::User>& -SearchUserDirectoryJob::results() const +const QVector<SearchUserDirectoryJob::User>& SearchUserDirectoryJob::results() const { return d->results; } @@ -62,12 +65,13 @@ BaseJob::Status SearchUserDirectoryJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("results"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'results' not found in the response" }; fromJson(json.value("results"_ls), d->results); if (!json.contains("limited"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'limited' not found in the response" }; fromJson(json.value("limited"_ls), d->limited); + return Success; } diff --git a/lib/csapi/users.h b/lib/csapi/users.h index 7754b82a..2e86c009 100644 --- a/lib/csapi/users.h +++ b/lib/csapi/users.h @@ -4,74 +4,80 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QVector> -namespace QMatrixClient { - // Operations - - /// Searches the user directory. - /// - /// Performs a search for users on the homeserver. The homeserver may - /// determine which subset of users are searched, however the homeserver - /// MUST at a minimum consider the users the requesting user shares a - /// room with and those who reside in public rooms (known to the - /// homeserver). The search MUST consider local users to the homeserver, and - /// SHOULD query remote users as part of the search. - /// - /// The search is performed case-insensitively on user IDs and display - /// names preferably using a collation determined based upon the - /// ``Accept-Language`` header provided in the request, if present. - class SearchUserDirectoryJob : public BaseJob +namespace QMatrixClient +{ + +// Operations + +/// Searches the user directory. +/*! + * Performs a search for users on the homeserver. The homeserver may + * determine which subset of users are searched, however the homeserver + * MUST at a minimum consider the users the requesting user shares a + * room with and those who reside in public rooms (known to the homeserver). + * The search MUST consider local users to the homeserver, and SHOULD + * query remote users as part of the search. + * + * The search is performed case-insensitively on user IDs and display + * names preferably using a collation determined based upon the + * ``Accept-Language`` header provided in the request, if present. + */ +class SearchUserDirectoryJob : public BaseJob +{ +public: + // Inner data structures + + /// Performs a search for users on the homeserver. The homeserver + /// maydetermine which subset of users are searched, however the + /// homeserverMUST at a minimum consider the users the requesting user + /// shares aroom with and those who reside in public rooms (known to the + /// homeserver).The search MUST consider local users to the homeserver, and + /// SHOULDquery remote users as part of the search.The search is performed + /// case-insensitively on user IDs and displaynames preferably using a + /// collation determined based upon the ``Accept-Language`` header provided + /// in the request, if present. + struct User { - public: - // Inner data structures - - /// Performs a search for users on the homeserver. The homeserver may - /// determine which subset of users are searched, however the homeserver - /// MUST at a minimum consider the users the requesting user shares a - /// room with and those who reside in public rooms (known to the - /// homeserver). The search MUST consider local users to the homeserver, - /// and SHOULD query remote users as part of the search. - /// - /// The search is performed case-insensitively on user IDs and display - /// names preferably using a collation determined based upon the - /// ``Accept-Language`` header provided in the request, if present. - struct User { - /// The user's matrix user ID. - QString userId; - /// The display name of the user, if one exists. - QString displayName; - /// The avatar url, as an MXC, if one exists. - QString avatarUrl; - }; - - // Construction/destruction - - /*! Searches the user directory. - * \param searchTerm - * The term to search for - * \param limit - * The maximum number of results to return. Defaults to 10. - */ - explicit SearchUserDirectoryJob(const QString& searchTerm, - Omittable<int> limit = none); - ~SearchUserDirectoryJob() override; - - // Result properties - - /// Ordered by rank and then whether or not profile info is available. - const QVector<User>& results() const; - /// Indicates if the result list has been truncated by the limit. - bool limited() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; + /// The user's matrix user ID. + QString userId; + /// The display name of the user, if one exists. + QString displayName; + /// The avatar url, as an MXC, if one exists. + QString avatarUrl; }; + + // Construction/destruction + + /*! Searches the user directory. + * \param searchTerm + * The term to search for + * \param limit + * The maximum number of results to return. Defaults to 10. + */ + explicit SearchUserDirectoryJob(const QString& searchTerm, + Omittable<int> limit = none); + + ~SearchUserDirectoryJob() override; + + // Result properties + + /// Ordered by rank and then whether or not profile info is available. + const QVector<User>& results() const; + /// Indicates if the result list has been truncated by the limit. + bool limited() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/versions.cpp b/lib/csapi/versions.cpp index 13e3be15..1d66b94f 100644 --- a/lib/csapi/versions.cpp +++ b/lib/csapi/versions.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client"); class GetVersionsJob::Private { - public: +public: QStringList versions; QHash<QString, bool> unstableFeatures; }; @@ -27,10 +27,9 @@ QUrl GetVersionsJob::makeRequestUrl(QUrl baseUrl) static const auto GetVersionsJobName = QStringLiteral("GetVersionsJob"); GetVersionsJob::GetVersionsJob() - : BaseJob(HttpVerb::Get, GetVersionsJobName, basePath % "/versions", false), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetVersionsJobName, basePath % "/versions", false) + , d(new Private) +{} GetVersionsJob::~GetVersionsJob() = default; @@ -45,9 +44,10 @@ BaseJob::Status GetVersionsJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("versions"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'versions' not found in the response" }; fromJson(json.value("versions"_ls), d->versions); fromJson(json.value("unstable_features"_ls), d->unstableFeatures); + return Success; } diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h index d017708a..513e7f27 100644 --- a/lib/csapi/versions.h +++ b/lib/csapi/versions.h @@ -4,65 +4,70 @@ #pragma once +#include "converters.h" + #include "jobs/basejob.h" -#include "converters.h" #include <QtCore/QHash> -namespace QMatrixClient { - // Operations +namespace QMatrixClient +{ + +// Operations + +/// Gets the versions of the specification supported by the server. +/*! + * Gets the versions of the specification supported by the server. + * + * Values will take the form ``rX.Y.Z``. + * + * Only the latest ``Z`` value will be reported for each supported ``X.Y`` + * value. i.e. if the server implements ``r0.0.0``, ``r0.0.1``, and ``r1.2.0``, + * it will report ``r0.0.1`` and ``r1.2.0``. + * + * The server may additionally advertise experimental features it supports + * through ``unstable_features``. These features should be namespaced and + * may optionally include version information within their name if desired. + * Features listed here are not for optionally toggling parts of the Matrix + * specification and should only be used to advertise support for a feature + * which has not yet landed in the spec. For example, a feature currently + * undergoing the proposal process may appear here and eventually be taken + * off this list once the feature lands in the spec and the server deems it + * reasonable to do so. Servers may wish to keep advertising features here + * after they've been released into the spec to give clients a chance to + * upgrade appropriately. Additionally, clients should avoid using unstable + * features in their stable releases. + */ +class GetVersionsJob : public BaseJob +{ +public: + explicit GetVersionsJob(); - /// Gets the versions of the specification supported by the server. - /// - /// Gets the versions of the specification supported by the server. - /// - /// Values will take the form ``rX.Y.Z``. - /// - /// Only the latest ``Z`` value will be reported for each supported ``X.Y`` - /// value. i.e. if the server implements ``r0.0.0``, ``r0.0.1``, and - /// ``r1.2.0``, it will report ``r0.0.1`` and ``r1.2.0``. - /// - /// The server may additionally advertise experimental features it supports - /// through ``unstable_features``. These features should be namespaced and - /// may optionally include version information within their name if desired. - /// Features listed here are not for optionally toggling parts of the Matrix - /// specification and should only be used to advertise support for a feature - /// which has not yet landed in the spec. For example, a feature currently - /// undergoing the proposal process may appear here and eventually be taken - /// off this list once the feature lands in the spec and the server deems it - /// reasonable to do so. Servers may wish to keep advertising features here - /// after they've been released into the spec to give clients a chance to - /// upgrade appropriately. Additionally, clients should avoid using unstable - /// features in their stable releases. - class GetVersionsJob : public BaseJob - { - public: - explicit GetVersionsJob(); + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetVersionsJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetVersionsJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); + ~GetVersionsJob() override; - ~GetVersionsJob() override; + // Result properties - // Result properties + /// The supported versions. + const QStringList& versions() const; + /// Experimental features the server supports. Features not listed here, + /// or the lack of this property all together, indicate that a feature is + /// not supported. + const QHash<QString, bool>& unstableFeatures() const; - /// The supported versions. - const QStringList& versions() const; - /// Experimental features the server supports. Features not listed here, - /// or the lack of this property all together, indicate that a feature - /// is not supported. - const QHash<QString, bool>& unstableFeatures() const; +protected: + Status parseJson(const QJsonDocument& data) override; - protected: - Status parseJson(const QJsonDocument& data) override; +private: + class Private; + QScopedPointer<Private> d; +}; - private: - class Private; - QScopedPointer<Private> d; - }; } // namespace QMatrixClient diff --git a/lib/csapi/voip.cpp b/lib/csapi/voip.cpp index ee511906..0e83c915 100644 --- a/lib/csapi/voip.cpp +++ b/lib/csapi/voip.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetTurnServerJob::Private { - public: +public: QJsonObject data; }; @@ -27,11 +27,9 @@ QUrl GetTurnServerJob::makeRequestUrl(QUrl baseUrl) static const auto GetTurnServerJobName = QStringLiteral("GetTurnServerJob"); GetTurnServerJob::GetTurnServerJob() - : BaseJob(HttpVerb::Get, GetTurnServerJobName, - basePath % "/voip/turnServer"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetTurnServerJobName, basePath % "/voip/turnServer") + , d(new Private) +{} GetTurnServerJob::~GetTurnServerJob() = default; diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h index 3b011651..ab34dcad 100644 --- a/lib/csapi/voip.h +++ b/lib/csapi/voip.h @@ -8,38 +8,42 @@ #include <QtCore/QJsonObject> -namespace QMatrixClient { - // Operations - - /// Obtain TURN server credentials. - /// - /// This API provides credentials for the client to use when initiating - /// calls. - class GetTurnServerJob : public BaseJob - { - public: - explicit GetTurnServerJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetTurnServerJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetTurnServerJob() override; - - // Result properties - - /// The TURN server credentials. - const QJsonObject& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Obtain TURN server credentials. +/*! + * This API provides credentials for the client to use when initiating + * calls. + */ +class GetTurnServerJob : public BaseJob +{ +public: + explicit GetTurnServerJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetTurnServerJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetTurnServerJob() override; + + // Result properties + + /// The TURN server credentials. + const QJsonObject& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/wellknown.cpp b/lib/csapi/wellknown.cpp index 8543c519..c2bd7822 100644 --- a/lib/csapi/wellknown.cpp +++ b/lib/csapi/wellknown.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/.well-known"); class GetWellknownJob::Private { - public: +public: DiscoveryInformation data; }; @@ -28,10 +28,9 @@ static const auto GetWellknownJobName = QStringLiteral("GetWellknownJob"); GetWellknownJob::GetWellknownJob() : BaseJob(HttpVerb::Get, GetWellknownJobName, basePath % "/matrix/client", - false), - d(new Private) -{ -} + false) + , d(new Private) +{} GetWellknownJob::~GetWellknownJob() = default; diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h index 1df524ce..66917806 100644 --- a/lib/csapi/wellknown.h +++ b/lib/csapi/wellknown.h @@ -4,49 +4,54 @@ #pragma once -#include "jobs/basejob.h" - #include "converters.h" + #include "csapi/definitions/wellknown/full.h" -namespace QMatrixClient { - // Operations - - /// Gets Matrix server discovery information about the domain. - /// - /// Gets discovery information about the domain. The file may include - /// additional keys, which MUST follow the Java package naming convention, - /// e.g. ``com.example.myapp.property``. This ensures property names are - /// suitably namespaced for each application and reduces the risk of - /// clashes. - /// - /// Note that this endpoint is not necessarily handled by the homeserver, - /// but by another webserver, to be used for discovering the homeserver URL. - class GetWellknownJob : public BaseJob - { - public: - explicit GetWellknownJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetWellknownJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetWellknownJob() override; - - // Result properties - - /// Server discovery information. - const DiscoveryInformation& data() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +#include "jobs/basejob.h" + +namespace QMatrixClient +{ + +// Operations + +/// Gets Matrix server discovery information about the domain. +/*! + * Gets discovery information about the domain. The file may include + * additional keys, which MUST follow the Java package naming convention, + * e.g. ``com.example.myapp.property``. This ensures property names are + * suitably namespaced for each application and reduces the risk of + * clashes. + * + * Note that this endpoint is not necessarily handled by the homeserver, + * but by another webserver, to be used for discovering the homeserver URL. + */ +class GetWellknownJob : public BaseJob +{ +public: + explicit GetWellknownJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetWellknownJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetWellknownJob() override; + + // Result properties + + /// Server discovery information. + const DiscoveryInformation& data() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/whoami.cpp b/lib/csapi/whoami.cpp index f0a77aee..2ca9c435 100644 --- a/lib/csapi/whoami.cpp +++ b/lib/csapi/whoami.cpp @@ -14,7 +14,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class GetTokenOwnerJob::Private { - public: +public: QString userId; }; @@ -27,11 +27,9 @@ QUrl GetTokenOwnerJob::makeRequestUrl(QUrl baseUrl) static const auto GetTokenOwnerJobName = QStringLiteral("GetTokenOwnerJob"); GetTokenOwnerJob::GetTokenOwnerJob() - : BaseJob(HttpVerb::Get, GetTokenOwnerJobName, - basePath % "/account/whoami"), - d(new Private) -{ -} + : BaseJob(HttpVerb::Get, GetTokenOwnerJobName, basePath % "/account/whoami") + , d(new Private) +{} GetTokenOwnerJob::~GetTokenOwnerJob() = default; @@ -41,8 +39,9 @@ BaseJob::Status GetTokenOwnerJob::parseJson(const QJsonDocument& data) { auto json = data.object(); if (!json.contains("user_id"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key 'user_id' not found in the response" }; fromJson(json.value("user_id"_ls), d->userId); + return Success; } diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h index 8f191351..e62b7dad 100644 --- a/lib/csapi/whoami.h +++ b/lib/csapi/whoami.h @@ -6,44 +6,48 @@ #include "jobs/basejob.h" -namespace QMatrixClient { - // Operations - - /// Gets information about the owner of an access token. - /// - /// Gets information about the owner of a given access token. - /// - /// Note that, as with the rest of the Client-Server API, - /// Application Services may masquerade as users within their - /// namespace by giving a ``user_id`` query parameter. In this - /// situation, the server should verify that the given ``user_id`` - /// is registered by the appservice, and return it in the response - /// body. - class GetTokenOwnerJob : public BaseJob - { - public: - explicit GetTokenOwnerJob(); - - /*! Construct a URL without creating a full-fledged job object - * - * This function can be used when a URL for - * GetTokenOwnerJob is necessary but the job - * itself isn't. - */ - static QUrl makeRequestUrl(QUrl baseUrl); - - ~GetTokenOwnerJob() override; - - // Result properties - - /// The user id that owns the access token. - const QString& userId() const; - - protected: - Status parseJson(const QJsonDocument& data) override; - - private: - class Private; - QScopedPointer<Private> d; - }; +namespace QMatrixClient +{ + +// Operations + +/// Gets information about the owner of an access token. +/*! + * Gets information about the owner of a given access token. + * + * Note that, as with the rest of the Client-Server API, + * Application Services may masquerade as users within their + * namespace by giving a ``user_id`` query parameter. In this + * situation, the server should verify that the given ``user_id`` + * is registered by the appservice, and return it in the response + * body. + */ +class GetTokenOwnerJob : public BaseJob +{ +public: + explicit GetTokenOwnerJob(); + + /*! Construct a URL without creating a full-fledged job object + * + * This function can be used when a URL for + * GetTokenOwnerJob is necessary but the job + * itself isn't. + */ + static QUrl makeRequestUrl(QUrl baseUrl); + + ~GetTokenOwnerJob() override; + + // Result properties + + /// The user id that owns the access token. + const QString& userId() const; + +protected: + Status parseJson(const QJsonDocument& data) override; + +private: + class Private; + QScopedPointer<Private> d; +}; + } // namespace QMatrixClient diff --git a/lib/csapi/{{base}}.cpp.mustache b/lib/csapi/{{base}}.cpp.mustache index f9a63412..8ebac6ef 100644 --- a/lib/csapi/{{base}}.cpp.mustache +++ b/lib/csapi/{{base}}.cpp.mustache @@ -141,7 +141,7 @@ BaseJob::Status {{camelCaseOperationId}}Job::parseJson(const QJsonDocument& data }} auto json = data.object(); {{# properties}}{{#required? }} if (!json.contains("{{baseName}}"_ls)) - return { JsonParseError, + return { IncorrectResponse, "The key '{{baseName}}' not found in the response" }; {{/required? }} fromJson(json.value("{{baseName}}"_ls), d->{{paramName}}); diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index f3f18854..de0a5c9f 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -35,9 +35,8 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl) } if (auto* rae = getAs<RoomAvatarEvent>()) { Q_ASSERT(rae->content().fileInfo()); - rae->editContent([remoteUrl](EventContent::FileInfo& fi) { - fi.url = remoteUrl; - }); + rae->editContent( + [remoteUrl](EventContent::FileInfo& fi) { fi.url = remoteUrl; }); } setStatus(EventStatus::FileUploaded); } diff --git a/lib/eventitem.h b/lib/eventitem.h index 8b863d67..58f5479c 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -22,140 +22,144 @@ #include <utility> -namespace QMatrixClient { - class StateEventBase; - - class EventStatus +namespace QMatrixClient +{ +class StateEventBase; + +class EventStatus +{ + Q_GADGET +public: + /** Special marks an event can assume + * + * This is used to hint at a special status of some events in UI. + * All values except Redacted and Hidden are mutually exclusive. + */ + enum Code { - Q_GADGET - public: - /** Special marks an event can assume - * - * This is used to hint at a special status of some events in UI. - * All values except Redacted and Hidden are mutually exclusive. - */ - enum Code { - Normal = 0x0, //< No special designation - Submitted = 0x01, //< The event has just been submitted for sending - FileUploaded = 0x02, //< The file attached to the event has been - //uploaded to the server - Departed = 0x03, //< The event has left the client - ReachedServer = 0x04, //< The server has received the event - SendingFailed = 0x05, //< The server could not receive the event - Redacted = 0x08, //< The event has been redacted - Hidden = 0x10, //< The event should not be shown in the timeline - }; - Q_DECLARE_FLAGS(Status, Code) - Q_FLAG(Status) + Normal = 0x0, //< No special designation + Submitted = 0x01, //< The event has just been submitted for sending + FileUploaded = 0x02, //< The file attached to the event has been + // uploaded to the server + Departed = 0x03, //< The event has left the client + ReachedServer = 0x04, //< The server has received the event + SendingFailed = 0x05, //< The server could not receive the event + Redacted = 0x08, //< The event has been redacted + Hidden = 0x10, //< The event should not be shown in the timeline }; + Q_DECLARE_FLAGS(Status, Code) + Q_FLAG(Status) +}; + +class EventItemBase +{ +public: + explicit EventItemBase(RoomEventPtr&& e) + : evt(std::move(e)) + { + Q_ASSERT(evt); + } - class EventItemBase + const RoomEvent* event() const { return rawPtr(evt); } + const RoomEvent* get() const { return event(); } + template <typename EventT> + const EventT* viewAs() const { - public: - explicit EventItemBase(RoomEventPtr&& e) : evt(std::move(e)) - { - Q_ASSERT(evt); - } - - const RoomEvent* event() const { return rawPtr(evt); } - const RoomEvent* get() const { return event(); } - template <typename EventT> const EventT* viewAs() const - { - return eventCast<const EventT>(evt); - } - const RoomEventPtr& operator->() const { return evt; } - const RoomEvent& operator*() const { return *evt; } - - // Used for event redaction - RoomEventPtr replaceEvent(RoomEventPtr&& other) - { - return std::exchange(evt, move(other)); - } - - protected: - template <typename EventT> EventT* getAs() - { - return eventCast<EventT>(evt); - } - - private: - RoomEventPtr evt; - }; + return eventCast<const EventT>(evt); + } + const RoomEventPtr& operator->() const { return evt; } + const RoomEvent& operator*() const { return *evt; } - class TimelineItem : public EventItemBase + // Used for event redaction + RoomEventPtr replaceEvent(RoomEventPtr&& other) { - public: - // For compatibility with Qt containers, even though we use - // a std:: container now for the room timeline - using index_t = int; + return std::exchange(evt, move(other)); + } - TimelineItem(RoomEventPtr&& e, index_t number) - : EventItemBase(std::move(e)), idx(number) - { - } +protected: + template <typename EventT> + EventT* getAs() + { + return eventCast<EventT>(evt); + } - index_t index() const { return idx; } +private: + RoomEventPtr evt; +}; + +class TimelineItem : public EventItemBase +{ +public: + // For compatibility with Qt containers, even though we use + // a std:: container now for the room timeline + using index_t = int; + + TimelineItem(RoomEventPtr&& e, index_t number) + : EventItemBase(std::move(e)) + , idx(number) + {} + + index_t index() const { return idx; } + +private: + index_t idx; +}; + +template <> +inline const StateEventBase* EventItemBase::viewAs<StateEventBase>() const +{ + return evt->isStateEvent() ? weakPtrCast<const StateEventBase>(evt) + : nullptr; +} - private: - index_t idx; - }; +template <> +inline const CallEventBase* EventItemBase::viewAs<CallEventBase>() const +{ + return evt->isCallEvent() ? weakPtrCast<const CallEventBase>(evt) : nullptr; +} + +class PendingEventItem : public EventItemBase +{ + Q_GADGET +public: + using EventItemBase::EventItemBase; + + EventStatus::Code deliveryStatus() const { return _status; } + QDateTime lastUpdated() const { return _lastUpdated; } + QString annotation() const { return _annotation; } - template <> - inline const StateEventBase* EventItemBase::viewAs<StateEventBase>() const + void setDeparted() { setStatus(EventStatus::Departed); } + void setFileUploaded(const QUrl& remoteUrl); + void setReachedServer(const QString& eventId) { - return evt->isStateEvent() ? weakPtrCast<const StateEventBase>(evt) - : nullptr; + setStatus(EventStatus::ReachedServer); + (*this)->addId(eventId); } - - template <> - inline const CallEventBase* EventItemBase::viewAs<CallEventBase>() const + void setSendingFailed(QString errorText) { - return evt->isCallEvent() ? weakPtrCast<const CallEventBase>(evt) - : nullptr; + setStatus(EventStatus::SendingFailed); + _annotation = std::move(errorText); } + void resetStatus() { setStatus(EventStatus::Submitted); } - class PendingEventItem : public EventItemBase - { - Q_GADGET - public: - using EventItemBase::EventItemBase; - - EventStatus::Code deliveryStatus() const { return _status; } - QDateTime lastUpdated() const { return _lastUpdated; } - QString annotation() const { return _annotation; } - - void setDeparted() { setStatus(EventStatus::Departed); } - void setFileUploaded(const QUrl& remoteUrl); - void setReachedServer(const QString& eventId) - { - setStatus(EventStatus::ReachedServer); - (*this)->addId(eventId); - } - void setSendingFailed(QString errorText) - { - setStatus(EventStatus::SendingFailed); - _annotation = std::move(errorText); - } - void resetStatus() { setStatus(EventStatus::Submitted); } - - private: - EventStatus::Code _status = EventStatus::Submitted; - QDateTime _lastUpdated = QDateTime::currentDateTimeUtc(); - QString _annotation; - - void setStatus(EventStatus::Code status) - { - _status = status; - _lastUpdated = QDateTime::currentDateTimeUtc(); - _annotation.clear(); - } - }; +private: + EventStatus::Code _status = EventStatus::Submitted; + QDateTime _lastUpdated = QDateTime::currentDateTimeUtc(); + QString _annotation; - inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) + void setStatus(EventStatus::Code status) { - QDebugStateSaver dss(d); - d.nospace() << "(" << ti.index() << "|" << ti->id() << ")"; - return d; + _status = status; + _lastUpdated = QDateTime::currentDateTimeUtc(); + _annotation.clear(); } +}; + +inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) +{ + QDebugStateSaver dss(d); + d.nospace() << "(" << ti.index() << "|" << ti->id() << ")"; + return d; } +} // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::EventStatus) diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 0cf2dc60..abab9867 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -24,76 +24,81 @@ #include "event.h" #include "eventcontent.h" -namespace QMatrixClient { - constexpr const char* FavouriteTag = "m.favourite"; - constexpr const char* LowPriorityTag = "m.lowpriority"; +namespace QMatrixClient +{ +constexpr const char* FavouriteTag = "m.favourite"; +constexpr const char* LowPriorityTag = "m.lowpriority"; - struct TagRecord { - using order_type = Omittable<float>; +struct TagRecord +{ + using order_type = Omittable<float>; - order_type order; + order_type order; - TagRecord(order_type order = none) : order(order) {} + TagRecord(order_type order = none) + : order(order) + {} - bool operator<(const TagRecord& other) const - { - // Per The Spec, rooms with no order should be after those with - // order - return !order.omitted() - && (other.order.omitted() - || order.value() < other.order.value()); - } - }; + bool operator<(const TagRecord& other) const + { + // Per The Spec, rooms with no order should be after those with order + return !order.omitted() + && (other.order.omitted() || order.value() < other.order.value()); + } +}; - template <> struct JsonObjectConverter<TagRecord> { - static void fillFrom(const QJsonObject& jo, TagRecord& rec) - { - // Parse a float both from JSON double and JSON string because - // libqmatrixclient previously used to use strings to store order. - const auto orderJv = jo.value("order"_ls); - if (orderJv.isDouble()) - rec.order = fromJson<float>(orderJv); - if (orderJv.isString()) { - bool ok; - rec.order = orderJv.toString().toFloat(&ok); - if (!ok) - rec.order = none; - } - } - static void dumpTo(QJsonObject& jo, const TagRecord& rec) - { - addParam<IfNotEmpty>(jo, QStringLiteral("order"), rec.order); +template <> +struct JsonObjectConverter<TagRecord> +{ + static void fillFrom(const QJsonObject& jo, TagRecord& rec) + { + // Parse a float both from JSON double and JSON string because + // libqmatrixclient previously used to use strings to store order. + const auto orderJv = jo.value("order"_ls); + if (orderJv.isDouble()) + rec.order = fromJson<float>(orderJv); + if (orderJv.isString()) { + bool ok; + rec.order = orderJv.toString().toFloat(&ok); + if (!ok) + rec.order = none; } - }; + } + static void dumpTo(QJsonObject& jo, const TagRecord& rec) + { + addParam<IfNotEmpty>(jo, QStringLiteral("order"), rec.order); + } +}; - using TagsMap = QHash<QString, TagRecord>; +using TagsMap = QHash<QString, TagRecord>; -#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ - class _Name : public Event \ - { \ - public: \ - using content_type = _ContentType; \ - DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(QJsonObject obj) : Event(typeId(), std::move(obj)) {} \ - explicit _Name(_ContentType content) \ - : Event(typeId(), matrixTypeId(), \ - QJsonObject { { QStringLiteral(#_ContentKey), \ - toJson(std::move(content)) } }) \ - { \ - } \ - auto _ContentKey() const \ - { \ - return fromJson<content_type>(contentJson()[#_ContentKey##_ls]); \ - } \ - }; \ - REGISTER_EVENT_TYPE(_Name) \ +#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \ + class _Name : public Event \ + { \ + public: \ + using content_type = _ContentType; \ + DEFINE_EVENT_TYPEID(_TypeId, _Name) \ + explicit _Name(QJsonObject obj) \ + : Event(typeId(), std::move(obj)) \ + {} \ + explicit _Name(_ContentType content) \ + : Event(typeId(), matrixTypeId(), \ + QJsonObject { { QStringLiteral(#_ContentKey), \ + toJson(std::move(content)) } }) \ + {} \ + auto _ContentKey() const \ + { \ + return content<content_type>(#_ContentKey##_ls); \ + } \ + }; \ + REGISTER_EVENT_TYPE(_Name) \ // End of macro - DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", TagsMap, tags) - DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", QString, event_id) - DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, "m.ignored_user_list", QSet<QString>, - ignored_users) +DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", TagsMap, tags) +DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", QString, event_id) +DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, "m.ignored_user_list", QSet<QString>, + ignored_users) - DEFINE_EVENTTYPE_ALIAS(Tag, TagEvent) - DEFINE_EVENTTYPE_ALIAS(ReadMarker, ReadMarkerEvent) -} +DEFINE_EVENTTYPE_ALIAS(Tag, TagEvent) +DEFINE_EVENTTYPE_ALIAS(ReadMarker, ReadMarkerEvent) +} // namespace QMatrixClient diff --git a/lib/events/callanswerevent.cpp b/lib/events/callanswerevent.cpp index 91e164ad..7ab4a6fb 100644 --- a/lib/events/callanswerevent.cpp +++ b/lib/events/callanswerevent.cpp @@ -19,7 +19,6 @@ #include "callanswerevent.h" #include "event.h" - #include "logging.h" #include <QtCore/QJsonDocument> @@ -55,20 +54,18 @@ CallAnswerEvent::CallAnswerEvent(const QJsonObject& obj) CallAnswerEvent::CallAnswerEvent(const QString& callId, const int lifetime, const QString& sdp) - : CallEventBase(typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("lifetime"), lifetime }, - { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), - QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) -{ -} + : CallEventBase( + typeId(), matrixTypeId(), callId, 0, + { { QStringLiteral("lifetime"), lifetime }, + { QStringLiteral("answer"), + QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} CallAnswerEvent::CallAnswerEvent(const QString& callId, const QString& sdp) - : CallEventBase(typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("answer"), - QJsonObject { { QStringLiteral("type"), - QStringLiteral("answer") }, - { QStringLiteral("sdp"), sdp } } } }) -{ -} + : CallEventBase( + typeId(), matrixTypeId(), callId, 0, + { { QStringLiteral("answer"), + QJsonObject { { QStringLiteral("type"), QStringLiteral("answer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index f222803b..69662eb9 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -20,31 +20,29 @@ #include "roomevent.h" -namespace QMatrixClient { - class CallAnswerEvent : public CallEventBase - { - public: - DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) +namespace QMatrixClient +{ +class CallAnswerEvent : public CallEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent) - explicit CallAnswerEvent(const QJsonObject& obj); + explicit CallAnswerEvent(const QJsonObject& obj); - explicit CallAnswerEvent(const QString& callId, const int lifetime, - const QString& sdp); - explicit CallAnswerEvent(const QString& callId, const QString& sdp); + explicit CallAnswerEvent(const QString& callId, const int lifetime, + const QString& sdp); + explicit CallAnswerEvent(const QString& callId, const QString& sdp); - int lifetime() const - { - return content<int>("lifetime"_ls); - } // FIXME: Omittable<>? - QString sdp() const - { - return contentJson()["answer"_ls] - .toObject() - .value("sdp"_ls) - .toString(); - } - }; + int lifetime() const + { + return content<int>("lifetime"_ls); + } // FIXME: Omittable<>? + QString sdp() const + { + return contentJson()["answer"_ls].toObject().value("sdp"_ls).toString(); + } +}; - REGISTER_EVENT_TYPE(CallAnswerEvent) - DEFINE_EVENTTYPE_ALIAS(CallAnswer, CallAnswerEvent) +REGISTER_EVENT_TYPE(CallAnswerEvent) +DEFINE_EVENTTYPE_ALIAS(CallAnswer, CallAnswerEvent) } // namespace QMatrixClient diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index e66e0c09..1c12b800 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -20,30 +20,29 @@ #include "roomevent.h" -namespace QMatrixClient { - class CallCandidatesEvent : public CallEventBase - { - public: - DEFINE_EVENT_TYPEID("m.call.candidates", CallCandidatesEvent) +namespace QMatrixClient +{ +class CallCandidatesEvent : public CallEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.call.candidates", CallCandidatesEvent) - explicit CallCandidatesEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) - { - } + explicit CallCandidatesEvent(const QJsonObject& obj) + : CallEventBase(typeId(), obj) + {} - explicit CallCandidatesEvent(const QString& callId, - const QJsonArray& candidates) - : CallEventBase(typeId(), matrixTypeId(), callId, 0, - { { QStringLiteral("candidates"), candidates } }) - { - } + explicit CallCandidatesEvent(const QString& callId, + const QJsonArray& candidates) + : CallEventBase(typeId(), matrixTypeId(), callId, 0, + { { QStringLiteral("candidates"), candidates } }) + {} - QJsonArray candidates() const - { - return content<QJsonArray>("candidates"_ls); - } - }; + QJsonArray candidates() const + { + return content<QJsonArray>("candidates"_ls); + } +}; - REGISTER_EVENT_TYPE(CallCandidatesEvent) - DEFINE_EVENTTYPE_ALIAS(CallCandidates, CallCandidatesEvent) -} +REGISTER_EVENT_TYPE(CallCandidatesEvent) +DEFINE_EVENTTYPE_ALIAS(CallCandidates, CallCandidatesEvent) +} // namespace QMatrixClient diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp index 80844f2d..2a4fd3da 100644 --- a/lib/events/callhangupevent.cpp +++ b/lib/events/callhangupevent.cpp @@ -19,7 +19,6 @@ #include "callhangupevent.h" #include "event.h" - #include "logging.h" #include <QtCore/QJsonDocument> @@ -50,5 +49,4 @@ CallHangupEvent::CallHangupEvent(const QJsonObject& obj) CallHangupEvent::CallHangupEvent(const QString& callId) : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{ -} +{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 3c3910be..0a5a3283 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -20,16 +20,17 @@ #include "roomevent.h" -namespace QMatrixClient { - class CallHangupEvent : public CallEventBase - { - public: - DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) +namespace QMatrixClient +{ +class CallHangupEvent : public CallEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); - }; + explicit CallHangupEvent(const QJsonObject& obj); + explicit CallHangupEvent(const QString& callId); +}; - REGISTER_EVENT_TYPE(CallHangupEvent) - DEFINE_EVENTTYPE_ALIAS(CallHangup, CallHangupEvent) -} +REGISTER_EVENT_TYPE(CallHangupEvent) +DEFINE_EVENTTYPE_ALIAS(CallHangup, CallHangupEvent) +} // namespace QMatrixClient diff --git a/lib/events/callinviteevent.cpp b/lib/events/callinviteevent.cpp index 2459c093..f565fc3e 100644 --- a/lib/events/callinviteevent.cpp +++ b/lib/events/callinviteevent.cpp @@ -19,7 +19,6 @@ #include "callinviteevent.h" #include "event.h" - #include "logging.h" #include <QtCore/QJsonDocument> @@ -55,11 +54,10 @@ CallInviteEvent::CallInviteEvent(const QJsonObject& obj) CallInviteEvent::CallInviteEvent(const QString& callId, const int lifetime, const QString& sdp) - : CallEventBase(typeId(), matrixTypeId(), callId, lifetime, - { { QStringLiteral("lifetime"), lifetime }, - { QStringLiteral("offer"), - QJsonObject { { QStringLiteral("type"), - QStringLiteral("offer") }, - { QStringLiteral("sdp"), sdp } } } }) -{ -} + : CallEventBase( + typeId(), matrixTypeId(), callId, lifetime, + { { QStringLiteral("lifetime"), lifetime }, + { QStringLiteral("offer"), + QJsonObject { { QStringLiteral("type"), QStringLiteral("offer") }, + { QStringLiteral("sdp"), sdp } } } }) +{} diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index 911ccf96..4334ca5b 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -20,30 +20,28 @@ #include "roomevent.h" -namespace QMatrixClient { - class CallInviteEvent : public CallEventBase - { - public: - DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) +namespace QMatrixClient +{ +class CallInviteEvent : public CallEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent) - explicit CallInviteEvent(const QJsonObject& obj); + explicit CallInviteEvent(const QJsonObject& obj); - explicit CallInviteEvent(const QString& callId, const int lifetime, - const QString& sdp); + explicit CallInviteEvent(const QString& callId, const int lifetime, + const QString& sdp); - int lifetime() const - { - return content<int>("lifetime"_ls); - } // FIXME: Omittable<>? - QString sdp() const - { - return contentJson()["offer"_ls] - .toObject() - .value("sdp"_ls) - .toString(); - } - }; + int lifetime() const + { + return content<int>("lifetime"_ls); + } // FIXME: Omittable<>? + QString sdp() const + { + return contentJson()["offer"_ls].toObject().value("sdp"_ls).toString(); + } +}; - REGISTER_EVENT_TYPE(CallInviteEvent) - DEFINE_EVENTTYPE_ALIAS(CallInvite, CallInviteEvent) -} +REGISTER_EVENT_TYPE(CallInviteEvent) +DEFINE_EVENTTYPE_ALIAS(CallInvite, CallInviteEvent) +} // namespace QMatrixClient diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h index 0d8b8f74..6b4a08ee 100644 --- a/lib/events/directchatevent.h +++ b/lib/events/directchatevent.h @@ -20,18 +20,19 @@ #include "event.h" -namespace QMatrixClient { - class DirectChatEvent : public Event - { - public: - DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) +namespace QMatrixClient +{ +class DirectChatEvent : public Event +{ +public: + DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent) - explicit DirectChatEvent(const QJsonObject& obj) : Event(typeId(), obj) - { - } + explicit DirectChatEvent(const QJsonObject& obj) + : Event(typeId(), obj) + {} - QMultiHash<QString, QString> usersToDirectChats() const; - }; - REGISTER_EVENT_TYPE(DirectChatEvent) - DEFINE_EVENTTYPE_ALIAS(DirectChat, DirectChatEvent) -} + QMultiHash<QString, QString> usersToDirectChats() const; +}; +REGISTER_EVENT_TYPE(DirectChatEvent) +DEFINE_EVENTTYPE_ALIAS(DirectChat, DirectChatEvent) +} // namespace QMatrixClient diff --git a/lib/events/event.cpp b/lib/events/event.cpp index f44b1e5d..718a6602 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -38,10 +38,13 @@ event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId) QString EventTypeRegistry::getMatrixType(event_type_t typeId) { - return typeId < get().eventTypes.size() ? get().eventTypes[typeId] : ""; + return typeId < get().eventTypes.size() ? get().eventTypes[typeId] + : QString(); } -Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) +Event::Event(Type type, const QJsonObject& json) + : _type(type) + , _json(json) { if (!json.contains(ContentKeyL) && !json.value(UnsignedKeyL).toObject().contains(RedactedCauseKeyL)) { @@ -50,11 +53,9 @@ Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json) } } -Event::Event(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson) +Event::Event(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, basicEventJson(matrixType, contentJson)) -{ -} +{} Event::~Event() = default; diff --git a/lib/events/event.h b/lib/events/event.h index a0f12b75..9dcec1ae 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -22,375 +22,396 @@ #include "logging.h" #ifdef ENABLE_EVENTTYPE_ALIAS -#define USE_EVENTTYPE_ALIAS 1 +# define USE_EVENTTYPE_ALIAS 1 #endif -namespace QMatrixClient { - // === event_ptr_tt<> and type casting facilities === - - template <typename EventT> using event_ptr_tt = std::unique_ptr<EventT>; +namespace QMatrixClient +{ +// === event_ptr_tt<> and type casting facilities === + +template <typename EventT> +using event_ptr_tt = std::unique_ptr<EventT>; + +/// Unwrap a plain pointer from a smart pointer +template <typename EventT> +inline EventT* rawPtr(const event_ptr_tt<EventT>& ptr) +{ + return ptr.get(); +} + +/// Unwrap a plain pointer and downcast it to the specified type +template <typename TargetEventT, typename EventT> +inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr) +{ + return static_cast<TargetEventT*>(rawPtr(ptr)); +} + +/// Re-wrap a smart pointer to base into a smart pointer to derived +template <typename TargetT, typename SourceT> +[[deprecated("Consider using eventCast() or visit() " + "instead")]] inline event_ptr_tt<TargetT> +ptrCast(event_ptr_tt<SourceT>&& ptr) +{ + return unique_ptr_cast<TargetT>(ptr); +} + +// === Standard Matrix key names and basicEventJson() === + +static const auto TypeKey = QStringLiteral("type"); +static const auto ContentKey = QStringLiteral("content"); +static const auto EventIdKey = QStringLiteral("event_id"); +static const auto UnsignedKey = QStringLiteral("unsigned"); +static const auto TypeKeyL = "type"_ls; +static const auto ContentKeyL = "content"_ls; +static const auto EventIdKeyL = "event_id"_ls; +static const auto UnsignedKeyL = "unsigned"_ls; +static const auto RedactedCauseKeyL = "redacted_because"_ls; +static const auto PrevContentKeyL = "prev_content"_ls; + +// Minimal correct Matrix event JSON +template <typename StrT> +inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) +{ + return { { TypeKey, std::forward<StrT>(matrixType) }, + { ContentKey, content } }; +} + +// === Event types and event types registry === + +using event_type_t = size_t; +using event_mtype_t = const char*; + +class EventTypeRegistry +{ +public: + ~EventTypeRegistry() = default; + + static event_type_t initializeTypeId(event_mtype_t matrixTypeId); template <typename EventT> - inline EventT* rawPtr(const event_ptr_tt<EventT>& ptr) // unwrap + static inline event_type_t initializeTypeId() { - return ptr.get(); + return initializeTypeId(EventT::matrixTypeId()); } - template <typename TargetEventT, typename EventT> - inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr) - { - return static_cast<TargetEventT*>(rawPtr(ptr)); - } + static QString getMatrixType(event_type_t typeId); - template <typename TargetT, typename SourceT> - inline event_ptr_tt<TargetT> ptrCast(event_ptr_tt<SourceT>&& ptr) - { - return unique_ptr_cast<TargetT>(ptr); - } +private: + EventTypeRegistry() = default; + Q_DISABLE_COPY(EventTypeRegistry) + DISABLE_MOVE(EventTypeRegistry) - // === Standard Matrix key names and basicEventJson() === - - static const auto TypeKey = QStringLiteral("type"); - static const auto ContentKey = QStringLiteral("content"); - static const auto EventIdKey = QStringLiteral("event_id"); - static const auto UnsignedKey = QStringLiteral("unsigned"); - static const auto TypeKeyL = "type"_ls; - static const auto ContentKeyL = "content"_ls; - static const auto EventIdKeyL = "event_id"_ls; - static const auto UnsignedKeyL = "unsigned"_ls; - static const auto RedactedCauseKeyL = "redacted_because"_ls; - static const auto PrevContentKeyL = "prev_content"_ls; - - // Minimal correct Matrix event JSON - template <typename StrT> - inline QJsonObject basicEventJson(StrT matrixType, - const QJsonObject& content) + static EventTypeRegistry& get() { - return { { TypeKey, std::forward<StrT>(matrixType) }, - { ContentKey, content } }; + static EventTypeRegistry etr; + return etr; } - // === Event types and event types registry === + std::vector<event_mtype_t> eventTypes; +}; - using event_type_t = size_t; - using event_mtype_t = const char*; +template <> +inline event_type_t EventTypeRegistry::initializeTypeId<void>() +{ + return initializeTypeId(""); +} - class EventTypeRegistry +template <typename EventT> +struct EventTypeTraits +{ + static event_type_t id() { - public: - ~EventTypeRegistry() = default; - - static event_type_t initializeTypeId(event_mtype_t matrixTypeId); - - template <typename EventT> static inline event_type_t initializeTypeId() - { - return initializeTypeId(EventT::matrixTypeId()); - } - - static QString getMatrixType(event_type_t typeId); - - private: - EventTypeRegistry() = default; - Q_DISABLE_COPY(EventTypeRegistry) - DISABLE_MOVE(EventTypeRegistry) - - static EventTypeRegistry& get() - { - static EventTypeRegistry etr; - return etr; - } - - std::vector<event_mtype_t> eventTypes; - }; - - template <> inline event_type_t EventTypeRegistry::initializeTypeId<void>() - { - return initializeTypeId(""); + static const auto id = EventTypeRegistry::initializeTypeId<EventT>(); + return id; } - - template <typename EventT> struct EventTypeTraits { - static event_type_t id() - { - static const auto id = - EventTypeRegistry::initializeTypeId<EventT>(); - return id; - } - }; - - template <typename EventT> inline event_type_t typeId() +}; + +template <typename EventT> +inline event_type_t typeId() +{ + return EventTypeTraits<std::decay_t<EventT>>::id(); +} + +inline event_type_t unknownEventTypeId() { return typeId<void>(); } + +// === EventFactory === + +/** Create an event of arbitrary type from its arguments */ +template <typename EventT, typename... ArgTs> +inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args) +{ + return std::make_unique<EventT>(std::forward<ArgTs>(args)...); +} + +template <typename BaseEventT> +class EventFactory +{ +public: + template <typename FnT> + static auto addMethod(FnT&& method) { - return EventTypeTraits<std::decay_t<EventT>>::id(); + factories().emplace_back(std::forward<FnT>(method)); + return 0; } - inline event_type_t unknownEventTypeId() { return typeId<void>(); } - - // === EventFactory === - - /** Create an event of arbitrary type from its arguments */ - template <typename EventT, typename... ArgTs> - inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args) - { - return std::make_unique<EventT>(std::forward<ArgTs>(args)...); - } - - template <typename BaseEventT> class EventFactory - { - public: - template <typename FnT> static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward<FnT>(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template <typename EventT> static auto chainFactory() - { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt<BaseEventT> make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; - } - - private: - static auto& factories() - { - using inner_factory_tt = std::function<event_ptr_tt<BaseEventT>( - const QJsonObject&, const QString&)>; - static std::vector<inner_factory_tt> _factories {}; - return _factories; - } - }; - - /** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type + /** Chain two type factories + * Adds the factory class of EventT2 (EventT2::factory_t) to + * the list in factory class of EventT1 (EventT1::factory_t) so + * that when EventT1::factory_t::make() is invoked, types of + * EventT2 factory are looked through as well. This is used + * to include RoomEvent types into the more general Event factory, + * and state event types into the RoomEvent factory. */ - template <typename EventT> inline auto setupFactory() + template <typename EventT> + static auto chainFactory() { - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod( - [](const QJsonObject& json, const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType - ? makeEvent<EventT>(json) - : nullptr; - }); + return addMethod(&EventT::factory_t::make); } - template <typename EventT> inline auto registerEventType() + static event_ptr_tt<BaseEventT> make(const QJsonObject& json, + const QString& matrixType) { - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory<EventT>(); - return _; // Only to facilitate usage in static initialisation + for (const auto& f : factories()) + if (auto e = f(json, matrixType)) + return e; + return nullptr; } - // === Event === - - class Event +private: + static auto& factories() { - Q_GADGET - Q_PROPERTY(Type type READ type CONSTANT) - Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) - public: - using Type = event_type_t; - using factory_t = EventFactory<Event>; - - explicit Event(Type type, const QJsonObject& json); - explicit Event(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); - Q_DISABLE_COPY(Event) - Event(Event&&) = default; - Event& operator=(Event&&) = delete; - virtual ~Event(); - - Type type() const { return _type; } - QString matrixType() const; - QByteArray originalJson() const; - QJsonObject originalJsonObject() const { return fullJson(); } - - const QJsonObject& fullJson() const { return _json; } - - // According to the CS API spec, every event also has - // a "content" object; but since its structure is different for - // different types, we're implementing it per-event type. - - const QJsonObject contentJson() const; - const QJsonObject unsignedJson() const; - - template <typename T> T content(const QString& key) const - { - return fromJson<T>(contentJson()[key]); - } - - template <typename T> T content(const QLatin1String& key) const - { - return fromJson<T>(contentJson()[key]); - } - - friend QDebug operator<<(QDebug dbg, const Event& e) - { - QDebugStateSaver _dss { dbg }; - dbg.noquote().nospace() - << e.matrixType() << '(' << e.type() << "): "; - e.dumpTo(dbg); - return dbg; - } - - virtual bool isStateEvent() const { return false; } - virtual bool isCallEvent() const { return false; } - virtual void dumpTo(QDebug dbg) const; - - protected: - QJsonObject& editJson() { return _json; } - - private: - Type _type; - QJsonObject _json; - }; - using EventPtr = event_ptr_tt<Event>; - - template <typename EventT> - using EventsArray = std::vector<event_ptr_tt<EventT>>; - using Events = EventsArray<Event>; - - // === Macros used with event class definitions === - - // This macro should be used in a public section of an event class to - // provide matrixTypeId() and typeId(). -#define DEFINE_EVENT_TYPEID(_Id, _Type) \ - static constexpr event_mtype_t matrixTypeId() { return _Id; } \ - static auto typeId() { return QMatrixClient::typeId<_Type>(); } \ - // End of macro - - // This macro should be put after an event class definition (in .h or .cpp) - // to enable its deserialisation from a /sync and other - // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[gnu::unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ - // End of macro - -#ifdef USE_EVENTTYPE_ALIAS - namespace EventType { - inline event_type_t logEventType(event_type_t id, const char* idName) - { - qDebug(EVENTS) << "Using id" << id << "for" << idName; - return id; - } + using inner_factory_tt = std::function<event_ptr_tt<BaseEventT>( + const QJsonObject&, const QString&)>; + static std::vector<inner_factory_tt> _factories {}; + return _factories; } +}; - // This macro provides constants in EventType:: namespace for - // back-compatibility with libQMatrixClient 0.3 event type system. -#define DEFINE_EVENTTYPE_ALIAS(_Id, _Type) \ - namespace EventType { \ - [[deprecated( \ - "Use is<>(), eventCast<>() or visit<>()")]] static const auto \ - _Id = logEventType(typeId<_Type>(), #_Id); \ - } \ - // End of macro -#else -#define DEFINE_EVENTTYPE_ALIAS(_Id, _Type) // Nothing -#endif - - // === is<>(), eventCast<>() and visit<>() === - - template <typename EventT> inline bool is(const Event& e) +/** Add a type to its default factory + * Adds a standard factory method (via makeEvent<>) for a given + * type to EventT::factory_t factory class so that it can be + * created dynamically from loadEvent<>(). + * + * \tparam EventT the type to enable dynamic creation of + * \return the registered type id + * \sa loadEvent, Event::type + */ +template <typename EventT> +inline auto setupFactory() +{ + qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); + return EventT::factory_t::addMethod([](const QJsonObject& json, + const QString& jsonMatrixType) { + return EventT::matrixTypeId() == jsonMatrixType ? makeEvent<EventT>(json) + : nullptr; + }); +} + +template <typename EventT> +inline auto registerEventType() +{ + // Initialise exactly once, even if this function is called twice for + // the same type (for whatever reason - you never know the ways of + // static initialisation is done). + static const auto _ = setupFactory<EventT>(); + return _; // Only to facilitate usage in static initialisation +} + +// === Event === + +class Event +{ + Q_GADGET + Q_PROPERTY(Type type READ type CONSTANT) + Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) +public: + using Type = event_type_t; + using factory_t = EventFactory<Event>; + + explicit Event(Type type, const QJsonObject& json); + explicit Event(Type type, event_mtype_t matrixType, + const QJsonObject& contentJson = {}); + Q_DISABLE_COPY(Event) + Event(Event&&) = default; + Event& operator=(Event&&) = delete; + virtual ~Event(); + + Type type() const { return _type; } + QString matrixType() const; + QByteArray originalJson() const; + QJsonObject originalJsonObject() const { return fullJson(); } + + const QJsonObject& fullJson() const { return _json; } + + // According to the CS API spec, every event also has + // a "content" object; but since its structure is different for + // different types, we're implementing it per-event type. + + const QJsonObject contentJson() const; + const QJsonObject unsignedJson() const; + + template <typename T> + T content(const QString& key) const { - return e.type() == typeId<EventT>(); + return fromJson<T>(contentJson()[key]); } - inline bool isUnknown(const Event& e) + template <typename T> + T content(const QLatin1String& key) const { - return e.type() == unknownEventTypeId(); + return fromJson<T>(contentJson()[key]); } - template <typename EventT, typename BasePtrT> - inline auto eventCast(const BasePtrT& eptr) - -> decltype(static_cast<EventT*>(&*eptr)) + friend QDebug operator<<(QDebug dbg, const Event& e) { - Q_ASSERT(eptr); - return is<std::decay_t<EventT>>(*eptr) ? static_cast<EventT*>(&*eptr) - : nullptr; + QDebugStateSaver _dss { dbg }; + dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): "; + e.dumpTo(dbg); + return dbg; } - // A single generic catch-all visitor - template <typename BaseEventT, typename FnT> - inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(event)) - { - return visitor(event); - } + virtual bool isStateEvent() const { return false; } + virtual bool isCallEvent() const { return false; } + virtual void dumpTo(QDebug dbg) const; - template <typename T> constexpr auto is_event() - { - return std::is_base_of<Event, std::decay_t<T>>::value; - } +protected: + QJsonObject& editJson() { return _json; } - template <typename T, typename FnT> constexpr auto needs_cast() - { - return !std::is_convertible<T, fn_arg_t<FnT>>::value; - } +private: + Type _type; + QJsonObject _json; +}; +using EventPtr = event_ptr_tt<Event>; - // A single type-specific void visitor - template <typename BaseEventT, typename FnT> - inline std::enable_if_t<is_event<BaseEventT>() - && needs_cast<BaseEventT, FnT>() - && std::is_void<fn_return_t<FnT>>::value> - visit(const BaseEventT& event, FnT&& visitor) - { - using event_type = fn_arg_t<FnT>; - if (is<std::decay_t<event_type>>(event)) - visitor(static_cast<event_type>(event)); - } +template <typename EventT> +using EventsArray = std::vector<event_ptr_tt<EventT>>; +using Events = EventsArray<Event>; - // A single type-specific non-void visitor with an optional default value - template <typename BaseEventT, typename FnT> - inline std::enable_if_t< - is_event<BaseEventT>() && needs_cast<BaseEventT, FnT>(), - fn_return_t<FnT>> // non-voidness is guarded by defaultValue type - visit(const BaseEventT& event, FnT&& visitor, - fn_return_t<FnT>&& defaultValue = {}) - { - using event_type = fn_arg_t<FnT>; - if (is<std::decay_t<event_type>>(event)) - return visitor(static_cast<event_type>(event)); - return std::forward<fn_return_t<FnT>>(defaultValue); - } +// === Macros used with event class definitions === + +// This macro should be used in a public section of an event class to +// provide matrixTypeId() and typeId(). +#define DEFINE_EVENT_TYPEID(_Id, _Type) \ + static constexpr event_mtype_t matrixTypeId() { return _Id; } \ + static auto typeId() { return QMatrixClient::typeId<_Type>(); } \ + // End of macro + +// This macro should be put after an event class definition (in .h or .cpp) +// to enable its deserialisation from a /sync and other +// polymorphic event arrays +#define REGISTER_EVENT_TYPE(_Type) \ + namespace \ + { \ + [[gnu::unused]] static const auto _factoryAdded##_Type = \ + registerEventType<_Type>(); \ + } \ + // End of macro - // A chain of 2 or more visitors - template <typename BaseEventT, typename FnT1, typename FnT2, - typename... FnTs> - inline std::enable_if_t<is_event<BaseEventT>(), fn_return_t<FnT1>> - visit(const BaseEventT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +#ifdef USE_EVENTTYPE_ALIAS +namespace EventType +{ + inline event_type_t logEventType(event_type_t id, const char* idName) { - using event_type1 = fn_arg_t<FnT1>; - if (is<std::decay_t<event_type1>>(event)) - return visitor1(static_cast<event_type1&>(event)); - return visit(event, std::forward<FnT2>(visitor2), - std::forward<FnTs>(visitors)...); + qDebug(EVENTS) << "Using id" << id << "for" << idName; + return id; } +} // namespace EventType + +// This macro provides constants in EventType:: namespace for +// back-compatibility with libQMatrixClient 0.3 event type system. +# define DEFINE_EVENTTYPE_ALIAS(_Id, _Type) \ + namespace EventType \ + { \ + [[deprecated("Use is<>(), eventCast<>() or " \ + "visit<>()")]] static const auto _Id = \ + logEventType(typeId<_Type>(), #_Id); \ + } \ + // End of macro +#else +# define DEFINE_EVENTTYPE_ALIAS(_Id, _Type) // Nothing +#endif + +// === is<>(), eventCast<>() and visit<>() === + +template <typename EventT> +inline bool is(const Event& e) +{ + return e.type() == typeId<EventT>(); +} + +inline bool isUnknown(const Event& e) +{ + return e.type() == unknownEventTypeId(); +} + +template <typename EventT, typename BasePtrT> +inline auto eventCast(const BasePtrT& eptr) + -> decltype(static_cast<EventT*>(&*eptr)) +{ + Q_ASSERT(eptr); + return is<std::decay_t<EventT>>(*eptr) ? static_cast<EventT*>(&*eptr) + : nullptr; +} + +// A single generic catch-all visitor +template <typename BaseEventT, typename FnT> +inline auto visit(const BaseEventT& event, FnT&& visitor) + -> decltype(visitor(event)) +{ + return visitor(event); +} + +template <typename T> +constexpr auto is_event() +{ + return std::is_base_of<Event, std::decay_t<T>>::value; +} + +template <typename T, typename FnT> +constexpr auto needs_cast() +{ + return !std::is_convertible<T, fn_arg_t<FnT>>::value; +} + +// A single type-specific void visitor +template <typename BaseEventT, typename FnT> +inline std::enable_if_t<is_event<BaseEventT>() && needs_cast<BaseEventT, FnT>() + && std::is_void<fn_return_t<FnT>>::value> +visit(const BaseEventT& event, FnT&& visitor) +{ + using event_type = fn_arg_t<FnT>; + if (is<std::decay_t<event_type>>(event)) + visitor(static_cast<event_type>(event)); +} + +// A single type-specific non-void visitor with an optional default value +template <typename BaseEventT, typename FnT> +inline std::enable_if_t<is_event<BaseEventT>() && needs_cast<BaseEventT, FnT>(), + fn_return_t<FnT>> // non-voidness is guarded by + // defaultValue type + visit(const BaseEventT& event, + FnT&& visitor, + fn_return_t<FnT>&& + defaultValue = {}) +{ + using event_type = fn_arg_t<FnT>; + if (is<std::decay_t<event_type>>(event)) + return visitor(static_cast<event_type>(event)); + return std::forward<fn_return_t<FnT>>(defaultValue); +} + +// A chain of 2 or more visitors +template <typename BaseEventT, typename FnT1, typename FnT2, typename... FnTs> +inline std::enable_if_t<is_event<BaseEventT>(), fn_return_t<FnT1>> +visit(const BaseEventT& event, FnT1&& visitor1, FnT2&& visitor2, + FnTs&&... visitors) +{ + using event_type1 = fn_arg_t<FnT1>; + if (is<std::decay_t<event_type1>>(event)) + return visitor1(static_cast<event_type1&>(event)); + return visit(event, std::forward<FnT2>(visitor2), + std::forward<FnTs>(visitors)...); +} } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::Event*) Q_DECLARE_METATYPE(const QMatrixClient::Event*) diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index cc31fea5..2b84c2b7 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -34,26 +34,31 @@ QJsonObject Base::toJson() const FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType, const QString& originalFilename) - : mimeType(mimeType), - url(u), - payloadSize(payloadSize), - originalName(originalFilename) -{ -} + : mimeType(mimeType) + , url(u) + , payloadSize(payloadSize) + , originalName(originalFilename) +{} FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename) - : originalInfoJson(infoJson), - mimeType(QMimeDatabase().mimeTypeForName( - infoJson["mimetype"_ls].toString())), - url(u), - payloadSize(fromJson<qint64>(infoJson["size"_ls])), - originalName(originalFilename) + : originalInfoJson(infoJson) + , mimeType( + QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) + , url(u) + , payloadSize(fromJson<qint64>(infoJson["size"_ls])) + , originalName(originalFilename) { if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } +bool FileInfo::isValid() const +{ + return url.scheme() == "mxc" + && (url.authority() + url.path()).count('/') == 1; +} + void FileInfo::fillInfoJson(QJsonObject* infoJson) const { Q_ASSERT(infoJson); @@ -65,16 +70,15 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const ImageInfo::ImageInfo(const QUrl& u, qint64 fileSize, QMimeType mimeType, const QSize& imageSize, const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize) -{ -} + : FileInfo(u, fileSize, mimeType, originalFilename) + , imageSize(imageSize) +{} ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename), - imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) -{ -} + : FileInfo(u, infoJson, originalFilename) + , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) +{} void ImageInfo::fillInfoJson(QJsonObject* infoJson) const { @@ -88,8 +92,7 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const Thumbnail::Thumbnail(const QJsonObject& infoJson) : ImageInfo(infoJson["thumbnail_url"_ls].toString(), infoJson["thumbnail_info"_ls].toObject()) -{ -} +{} void Thumbnail::fillInfoJson(QJsonObject* infoJson) const { diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 857e7369..d2b5e477 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -26,257 +26,265 @@ #include <QtCore/QSize> #include <QtCore/QUrl> -namespace QMatrixClient { - namespace EventContent { - /** - * A base class for all content types that can be stored - * in a RoomMessageEvent - * - * Each content type class should have a constructor taking - * a QJsonObject and override fillJson() with an implementation - * that will fill the target QJsonObject with stored values. It is - * assumed but not required that a content object can also be created - * from plain data. - */ - class Base - { - public: - explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {} - virtual ~Base() = default; +namespace QMatrixClient +{ +namespace EventContent +{ + /** + * A base class for all content types that can be stored + * in a RoomMessageEvent + * + * Each content type class should have a constructor taking + * a QJsonObject and override fillJson() with an implementation + * that will fill the target QJsonObject with stored values. It is + * assumed but not required that a content object can also be created + * from plain data. + */ + class Base + { + public: + explicit Base(QJsonObject o = {}) + : originalJson(std::move(o)) + {} + virtual ~Base() = default; + + // FIXME: make toJson() from converters.* work on base classes + QJsonObject toJson() const; - // FIXME: make toJson() from converters.* work on base classes - QJsonObject toJson() const; + public: + QJsonObject originalJson; - public: - QJsonObject originalJson; + protected: + virtual void fillJson(QJsonObject* o) const = 0; + }; - protected: - virtual void fillJson(QJsonObject* o) const = 0; - }; + // The below structures fairly follow CS spec 11.2.1.6. The overall + // set of attributes for each content types is a superset of the spec + // but specific aggregation structure is altered. See doc comments to + // each type for the list of available attributes. - // The below structures fairly follow CS spec 11.2.1.6. The overall - // set of attributes for each content types is a superset of the spec - // but specific aggregation structure is altered. See doc comments to - // each type for the list of available attributes. + // A quick classes inheritance structure follows: + // FileInfo + // FileContent : UrlBasedContent<FileInfo, Thumbnail> + // AudioContent : UrlBasedContent<FileInfo, Duration> + // ImageInfo : FileInfo + imageSize attribute + // ImageContent : UrlBasedContent<ImageInfo, Thumbnail> + // VideoContent : UrlBasedContent<ImageInfo, Thumbnail, Duration> - // A quick classes inheritance structure follows: - // FileInfo - // FileContent : UrlBasedContent<FileInfo, Thumbnail> - // AudioContent : UrlBasedContent<FileInfo, Duration> - // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlBasedContent<ImageInfo, Thumbnail> - // VideoContent : UrlBasedContent<ImageInfo, Thumbnail, Duration> + /** + * A base/mixin class for structures representing an "info" object for + * some content types. These include most attachment types currently in + * the CS API spec. + * + * In order to use it in a content class, derive both from TypedBase + * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo) + * and call fillInfoJson() to fill the "info" subobject. Make sure + * to pass an "info" part of JSON to FileInfo constructor, not the whole + * JSON content, as well as contents of "url" (or a similar key) and + * optionally "filename" node from the main JSON content. Assuming you + * don't do unusual things, you should use \p UrlBasedContent<> instead + * of doing multiple inheritance and overriding Base::fillJson() by hand. + * + * This class is not polymorphic. + */ + class FileInfo + { + public: + explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, + const QMimeType& mimeType = {}, + const QString& originalFilename = {}); + FileInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename = {}); + + bool isValid() const; + + void fillInfoJson(QJsonObject* infoJson) const; /** - * A base/mixin class for structures representing an "info" object for - * some content types. These include most attachment types currently in - * the CS API spec. + * \brief Extract media id from the URL * - * In order to use it in a content class, derive both from TypedBase - * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo) - * and call fillInfoJson() to fill the "info" subobject. Make sure - * to pass an "info" part of JSON to FileInfo constructor, not the whole - * JSON content, as well as contents of "url" (or a similar key) and - * optionally "filename" node from the main JSON content. Assuming you - * don't do unusual things, you should use \p UrlBasedContent<> instead - * of doing multiple inheritance and overriding Base::fillJson() by - * hand. - * - * This class is not polymorphic. + * This can be used, e.g., to construct a QML-facing image:// + * URI as follows: + * \code "image://provider/" + info.mediaId() \endcode */ - class FileInfo - { - public: - explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, - const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + QString mediaId() const { return url.authority() + url.path(); } - void fillInfoJson(QJsonObject* infoJson) const; + public: + QJsonObject originalInfoJson; + QMimeType mimeType; + QUrl url; + qint64 payloadSize; + QString originalName; + }; - /** - * \brief Extract media id from the URL - * - * This can be used, e.g., to construct a QML-facing image:// - * URI as follows: - * \code "image://provider/" + info.mediaId() \endcode - */ - QString mediaId() const { return url.authority() + url.path(); } + template <typename InfoT> + QJsonObject toInfoJson(const InfoT& info) + { + QJsonObject infoJson; + info.fillInfoJson(&infoJson); + return infoJson; + } - public: - QJsonObject originalInfoJson; - QMimeType mimeType; - QUrl url; - qint64 payloadSize; - QString originalName; - }; + /** + * A content info class for image content types: image, thumbnail, video + */ + class ImageInfo : public FileInfo + { + public: + explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, + QMimeType mimeType = {}, const QSize& imageSize = {}, + const QString& originalFilename = {}); + ImageInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename = {}); - template <typename InfoT> QJsonObject toInfoJson(const InfoT& info) - { - QJsonObject infoJson; - info.fillInfoJson(&infoJson); - return infoJson; - } + void fillInfoJson(QJsonObject* infoJson) const; - /** - * A content info class for image content types: image, thumbnail, video - */ - class ImageInfo : public FileInfo - { - public: - explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, - const QSize& imageSize = {}, - const QString& originalFilename = {}); - ImageInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + public: + QSize imageSize; + }; - void fillInfoJson(QJsonObject* infoJson) const; - - public: - QSize imageSize; - }; + /** + * An auxiliary class for an info type that carries a thumbnail + * + * This class saves/loads a thumbnail to/from "info" subobject of + * the JSON representation of event content; namely, + * "info/thumbnail_url" and "info/thumbnail_info" fields are used. + */ + class Thumbnail : public ImageInfo + { + public: + Thumbnail() + : ImageInfo(QUrl()) + {} // To allow empty thumbnails + Thumbnail(const QJsonObject& infoJson); + Thumbnail(const ImageInfo& info) + : ImageInfo(info) + {} + using ImageInfo::ImageInfo; /** - * An auxiliary class for an info type that carries a thumbnail - * - * This class saves/loads a thumbnail to/from "info" subobject of - * the JSON representation of event content; namely, - * "info/thumbnail_url" and "info/thumbnail_info" fields are used. + * Writes thumbnail information to "thumbnail_info" subobject + * and thumbnail URL to "thumbnail_url" node inside "info". */ - class Thumbnail : public ImageInfo - { - public: - Thumbnail() : ImageInfo(QUrl()) {} // To allow empty thumbnails - Thumbnail(const QJsonObject& infoJson); - Thumbnail(const ImageInfo& info) : ImageInfo(info) {} - using ImageInfo::ImageInfo; + void fillInfoJson(QJsonObject* infoJson) const; + }; - /** - * Writes thumbnail information to "thumbnail_info" subobject - * and thumbnail URL to "thumbnail_url" node inside "info". - */ - void fillInfoJson(QJsonObject* infoJson) const; - }; + class TypedBase : public Base + { + public: + explicit TypedBase(const QJsonObject& o = {}) + : Base(o) + {} + virtual QMimeType type() const = 0; + virtual const FileInfo* fileInfo() const { return nullptr; } + virtual FileInfo* fileInfo() { return nullptr; } + virtual const Thumbnail* thumbnailInfo() const { return nullptr; } + }; - class TypedBase : public Base + /** + * A base class for content types that have a URL and additional info + * + * Types that derive from this class template take "url" and, + * optionally, "filename" values from the top-level JSON object and + * the rest of information from the "info" subobject, as defined by + * the parameter type. + * + * \tparam InfoT base info class + */ + template <class InfoT> + class UrlBasedContent : public TypedBase, public InfoT + { + public: + using InfoT::InfoT; + explicit UrlBasedContent(const QJsonObject& json) + : TypedBase(json) + , InfoT(json["url"].toString(), json["info"].toObject(), + json["filename"].toString()) { - public: - explicit TypedBase(const QJsonObject& o = {}) : Base(o) {} - virtual QMimeType type() const = 0; - virtual const FileInfo* fileInfo() const { return nullptr; } - virtual FileInfo* fileInfo() { return nullptr; } - virtual const Thumbnail* thumbnailInfo() const { return nullptr; } - }; - - /** - * A base class for content types that have a URL and additional info - * - * Types that derive from this class template take "url" and, - * optionally, "filename" values from the top-level JSON object and - * the rest of information from the "info" subobject, as defined by - * the parameter type. - * - * \tparam InfoT base info class - */ - template <class InfoT> - class UrlBasedContent : public TypedBase, public InfoT - { - public: - using InfoT::InfoT; - explicit UrlBasedContent(const QJsonObject& json) - : TypedBase(json), - InfoT(json["url"].toString(), json["info"].toObject(), - json["filename"].toString()) - { - // A small hack to facilitate links creation in QML. - originalJson.insert("mediaId", InfoT::mediaId()); - } + // A small hack to facilitate links creation in QML. + originalJson.insert("mediaId", InfoT::mediaId()); + } - QMimeType type() const override { return InfoT::mimeType; } - const FileInfo* fileInfo() const override { return this; } - FileInfo* fileInfo() override { return this; } + QMimeType type() const override { return InfoT::mimeType; } + const FileInfo* fileInfo() const override { return this; } + FileInfo* fileInfo() override { return this; } - protected: - void fillJson(QJsonObject* json) const override - { - Q_ASSERT(json); - json->insert("url", InfoT::url.toString()); - if (!InfoT::originalName.isEmpty()) - json->insert("filename", InfoT::originalName); - json->insert("info", toInfoJson<InfoT>(*this)); - } - }; + protected: + void fillJson(QJsonObject* json) const override + { + Q_ASSERT(json); + json->insert("url", InfoT::url.toString()); + if (!InfoT::originalName.isEmpty()) + json->insert("filename", InfoT::originalName); + json->insert("info", toInfoJson<InfoT>(*this)); + } + }; - template <typename InfoT> - class UrlWithThumbnailContent : public UrlBasedContent<InfoT> + template <typename InfoT> + class UrlWithThumbnailContent : public UrlBasedContent<InfoT> + { + public: + using UrlBasedContent<InfoT>::UrlBasedContent; + explicit UrlWithThumbnailContent(const QJsonObject& json) + : UrlBasedContent<InfoT>(json) + , thumbnail(InfoT::originalInfoJson) { - public: - using UrlBasedContent<InfoT>::UrlBasedContent; - explicit UrlWithThumbnailContent(const QJsonObject& json) - : UrlBasedContent<InfoT>(json), - thumbnail(InfoT::originalInfoJson) - { - // Another small hack, to simplify making a thumbnail link - UrlBasedContent<InfoT>::originalJson.insert( - "thumbnailMediaId", thumbnail.mediaId()); - } + // Another small hack, to simplify making a thumbnail link + UrlBasedContent<InfoT>::originalJson.insert("thumbnailMediaId", + thumbnail.mediaId()); + } - const Thumbnail* thumbnailInfo() const override - { - return &thumbnail; - } + const Thumbnail* thumbnailInfo() const override { return &thumbnail; } - public: - Thumbnail thumbnail; + public: + Thumbnail thumbnail; - protected: - void fillJson(QJsonObject* json) const override - { - UrlBasedContent<InfoT>::fillJson(json); - auto infoJson = json->take("info").toObject(); - thumbnail.fillInfoJson(&infoJson); - json->insert("info", infoJson); - } - }; + protected: + void fillJson(QJsonObject* json) const override + { + UrlBasedContent<InfoT>::fillJson(json); + auto infoJson = json->take("info").toObject(); + thumbnail.fillInfoJson(&infoJson); + json->insert("info", infoJson); + } + }; - /** - * Content class for m.image - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - imageSize (QSize for a combination of "h" and "w" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field, in the same vein as for the main image: - * - payloadSize - * - mimeType - * - imageSize - */ - using ImageContent = UrlWithThumbnailContent<ImageInfo>; + /** + * Content class for m.image + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename (extension to the spec) + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - imageSize (QSize for a combination of "h" and "w" in JSON) + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: contents of + * thumbnail field, in the same vein as for the main image: + * - payloadSize + * - mimeType + * - imageSize + */ + using ImageContent = UrlWithThumbnailContent<ImageInfo>; - /** - * Content class for m.file - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: - * - thumbnail.payloadSize - * - thumbnail.mimeType - * - thumbnail.imageSize (QSize for "h" and "w" in JSON) - */ - using FileContent = UrlWithThumbnailContent<FileInfo>; - } // namespace EventContent + /** + * Content class for m.file + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: + * - thumbnail.payloadSize + * - thumbnail.mimeType + * - thumbnail.imageSize (QSize for "h" and "w" in JSON) + */ + using FileContent = UrlWithThumbnailContent<FileInfo>; +} // namespace EventContent } // namespace QMatrixClient diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index a19a83b6..9c797701 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -20,51 +20,54 @@ #include "stateevent.h" -namespace QMatrixClient { - namespace _impl { - template <typename BaseEventT> - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory<BaseEventT>::make(json, matrixType)) - return e; - return makeEvent<BaseEventT>(unknownEventTypeId(), json); - } - } - - /** Create an event with proper type from a JSON object - * Use this factory template to detect the type from the JSON object - * contents (the detected event type should derive from the template - * parameter type) and create an event object of that type. - */ +namespace QMatrixClient +{ +namespace _impl +{ template <typename BaseEventT> - inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson) + static inline auto loadEvent(const QJsonObject& json, + const QString& matrixType) { - return _impl::loadEvent<BaseEventT>(fullJson, - fullJson[TypeKeyL].toString()); + if (auto e = EventFactory<BaseEventT>::make(json, matrixType)) + return e; + return makeEvent<BaseEventT>(unknownEventTypeId(), json); } +} // namespace _impl - /** Create an event from a type string and content JSON - * Use this factory template to resolve the C++ type from the Matrix - * type string in \p matrixType and create an event of that type that has - * its content part set to \p content. - */ - template <typename BaseEventT> - inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType, - const QJsonObject& content) +/** Create an event with proper type from a JSON object + * Use this factory template to detect the type from the JSON object + * contents (the detected event type should derive from the template + * parameter type) and create an event object of that type. + */ +template <typename BaseEventT> +inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson) +{ + return _impl::loadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString()); +} + +/** Create an event from a type string and content JSON + * Use this factory template to resolve the C++ type from the Matrix + * type string in \p matrixType and create an event of that type that has + * its content part set to \p content. + */ +template <typename BaseEventT> +inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType, + const QJsonObject& content) +{ + return _impl::loadEvent<BaseEventT>(basicEventJson(matrixType, content), + matrixType); +} + +template <typename EventT> +struct JsonConverter<event_ptr_tt<EventT>> +{ + static auto load(const QJsonValue& jv) { - return _impl::loadEvent<BaseEventT>(basicEventJson(matrixType, content), - matrixType); + return loadEvent<EventT>(jv.toObject()); } - - template <typename EventT> struct JsonConverter<event_ptr_tt<EventT>> { - static auto load(const QJsonValue& jv) - { - return loadEvent<EventT>(jv.toObject()); - } - static auto load(const QJsonDocument& jd) - { - return loadEvent<EventT>(jd.object()); - } - }; + static auto load(const QJsonDocument& jd) + { + return loadEvent<EventT>(jd.object()); + } +}; } // namespace QMatrixClient diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 76c7b6e4..fcb8431b 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -40,26 +40,26 @@ Example of a Receipt Event: using namespace QMatrixClient; -ReceiptEvent::ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) +ReceiptEvent::ReceiptEvent(const QJsonObject& obj) + : Event(typeId(), obj) { const auto& contents = contentJson(); _eventsWithReceipts.reserve(contents.size()); - for (auto eventIt = contents.begin(); eventIt != contents.end(); - ++eventIt) { + for (auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt) { if (eventIt.key().isEmpty()) { qCWarning(EPHEMERAL) - << "ReceiptEvent has an empty event id, skipping"; + << "ReceiptEvent has an empty event id, skipping"; qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << contents; continue; } const QJsonObject reads = - eventIt.value().toObject().value("m.read"_ls).toObject(); + eventIt.value().toObject().value("m.read"_ls).toObject(); QVector<Receipt> receipts; receipts.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { const QJsonObject user = userIt.value().toObject(); receipts.push_back( - { userIt.key(), fromJson<QDateTime>(user["ts"_ls]) }); + { userIt.key(), fromJson<QDateTime>(user["ts"_ls]) }); } _eventsWithReceipts.push_back({ eventIt.key(), std::move(receipts) }); } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index fca38bba..e8396670 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -23,31 +23,34 @@ #include <QtCore/QDateTime> #include <QtCore/QVector> -namespace QMatrixClient { - struct Receipt { - QString userId; - QDateTime timestamp; - }; - struct ReceiptsForEvent { - QString evtId; - QVector<Receipt> receipts; - }; - using EventsWithReceipts = QVector<ReceiptsForEvent>; +namespace QMatrixClient +{ +struct Receipt +{ + QString userId; + QDateTime timestamp; +}; +struct ReceiptsForEvent +{ + QString evtId; + QVector<Receipt> receipts; +}; +using EventsWithReceipts = QVector<ReceiptsForEvent>; - class ReceiptEvent : public Event - { - public: - DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) - explicit ReceiptEvent(const QJsonObject& obj); +class ReceiptEvent : public Event +{ +public: + DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + explicit ReceiptEvent(const QJsonObject& obj); - const EventsWithReceipts& eventsWithReceipts() const - { - return _eventsWithReceipts; - } + const EventsWithReceipts& eventsWithReceipts() const + { + return _eventsWithReceipts; + } - private: - EventsWithReceipts _eventsWithReceipts; - }; - REGISTER_EVENT_TYPE(ReceiptEvent) - DEFINE_EVENTTYPE_ALIAS(Receipt, ReceiptEvent) +private: + EventsWithReceipts _eventsWithReceipts; +}; +REGISTER_EVENT_TYPE(ReceiptEvent) +DEFINE_EVENTTYPE_ALIAS(Receipt, ReceiptEvent) } // namespace QMatrixClient diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index 4187291c..a7dd9705 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -20,23 +20,23 @@ #include "roomevent.h" -namespace QMatrixClient { - class RedactionEvent : public RoomEvent - { - public: - DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) +namespace QMatrixClient +{ +class RedactionEvent : public RoomEvent +{ +public: + DEFINE_EVENT_TYPEID("m.room.redaction", RedactionEvent) - explicit RedactionEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj) - { - } + explicit RedactionEvent(const QJsonObject& obj) + : RoomEvent(typeId(), obj) + {} - QString redactedEvent() const - { - return fullJson()["redacts"_ls].toString(); - } - QString reason() const { return contentJson()["reason"_ls].toString(); } - }; - REGISTER_EVENT_TYPE(RedactionEvent) - DEFINE_EVENTTYPE_ALIAS(Redaction, RedactionEvent) + QString redactedEvent() const + { + return fullJson()["redacts"_ls].toString(); + } + QString reason() const { return contentJson()["reason"_ls].toString(); } +}; +REGISTER_EVENT_TYPE(RedactionEvent) +DEFINE_EVENTTYPE_ALIAS(Redaction, RedactionEvent) } // namespace QMatrixClient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index 69cd1f9d..ee460339 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -21,21 +21,21 @@ #include "eventcontent.h" #include "stateevent.h" -namespace QMatrixClient { - class RoomAvatarEvent : public StateEvent<EventContent::ImageContent> - { - // It's a bit of an overkill to use a full-fledged ImageContent - // because in reality m.room.avatar usually only has a single URL, - // without a thumbnail. But The Spec says there be thumbnails, and - // we follow The Spec. - public: - DEFINE_EVENT_TYPEID("m.room.avatar", RoomAvatarEvent) - explicit RoomAvatarEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - { - } - QUrl url() const { return content().url; } - }; - REGISTER_EVENT_TYPE(RoomAvatarEvent) - DEFINE_EVENTTYPE_ALIAS(RoomAvatar, RoomAvatarEvent) +namespace QMatrixClient +{ +class RoomAvatarEvent : public StateEvent<EventContent::ImageContent> +{ + // It's a bit of an overkill to use a full-fledged ImageContent + // because in reality m.room.avatar usually only has a single URL, + // without a thumbnail. But The Spec says there be thumbnails, and + // we follow The Spec. +public: + DEFINE_EVENT_TYPEID("m.room.avatar", RoomAvatarEvent) + explicit RoomAvatarEvent(const QJsonObject& obj) + : StateEvent(typeId(), obj) + {} + QUrl url() const { return content().url; } +}; +REGISTER_EVENT_TYPE(RoomAvatarEvent) +DEFINE_EVENTTYPE_ALIAS(RoomAvatar, RoomAvatarEvent) } // namespace QMatrixClient diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index fbfb33ca..17b86388 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -20,27 +20,30 @@ #include "stateevent.h" -namespace QMatrixClient { - class RoomCreateEvent : public StateEventBase - { - public: - DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) - - explicit RoomCreateEvent() : StateEventBase(typeId(), matrixTypeId()) {} - explicit RoomCreateEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - { - } +namespace QMatrixClient +{ +class RoomCreateEvent : public StateEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent) - struct Predecessor { - QString roomId; - QString eventId; - }; + explicit RoomCreateEvent() + : StateEventBase(typeId(), matrixTypeId()) + {} + explicit RoomCreateEvent(const QJsonObject& obj) + : StateEventBase(typeId(), obj) + {} - bool isFederated() const; - QString version() const; - Predecessor predecessor() const; - bool isUpgrade() const; + struct Predecessor + { + QString roomId; + QString eventId; }; - REGISTER_EVENT_TYPE(RoomCreateEvent) -} + + bool isFederated() const; + QString version() const; + Predecessor predecessor() const; + bool isUpgrade() const; +}; +REGISTER_EVENT_TYPE(RoomCreateEvent) +} // namespace QMatrixClient diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 221a3a61..c28de559 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -25,15 +25,15 @@ using namespace QMatrixClient; [[gnu::unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory<RoomEvent>(); + Event::factory_t::chainFactory<RoomEvent>(); RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, matrixType, contentJson) -{ -} +{} -RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) +RoomEvent::RoomEvent(Type type, const QJsonObject& json) + : Event(type, json) { const auto unsignedData = json[UnsignedKeyL].toObject(); const auto redaction = unsignedData[RedactedCauseKeyL]; @@ -49,8 +49,7 @@ QString RoomEvent::id() const { return fullJson()[EventIdKeyL].toString(); } QDateTime RoomEvent::timestamp() const { - return QMatrixClient::fromJson<QDateTime>( - fullJson()["origin_server_ts"_ls]); + return QMatrixClient::fromJson<QDateTime>(fullJson()["origin_server_ts"_ls]); } QString RoomEvent::roomId() const @@ -108,8 +107,7 @@ CallEventBase::CallEventBase(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : RoomEvent(type, matrixType, makeCallContentJson(callId, version, contentJson)) -{ -} +{} CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) : RoomEvent(type, json) diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index 746e9173..42cd8fe4 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -22,85 +22,84 @@ #include <QtCore/QDateTime> -namespace QMatrixClient { - class RedactionEvent; +namespace QMatrixClient +{ +class RedactionEvent; - /** This class corresponds to m.room.* events */ - class RoomEvent : public Event - { - Q_GADGET - Q_PROPERTY(QString id READ id) - Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString redactionReason READ redactionReason) - Q_PROPERTY(bool isRedacted READ isRedacted) - Q_PROPERTY( - QString transactionId READ transactionId WRITE setTransactionId) - public: - using factory_t = EventFactory<RoomEvent>; +/** This class corresponds to m.room.* events */ +class RoomEvent : public Event +{ + Q_GADGET + Q_PROPERTY(QString id READ id) + Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + Q_PROPERTY(QString senderId READ senderId CONSTANT) + Q_PROPERTY(QString redactionReason READ redactionReason) + Q_PROPERTY(bool isRedacted READ isRedacted) + Q_PROPERTY(QString transactionId READ transactionId WRITE setTransactionId) +public: + using factory_t = EventFactory<RoomEvent>; - // RedactionEvent is an incomplete type here so we cannot inline - // constructors and destructors and we cannot use 'using'. - RoomEvent(Type type, event_mtype_t matrixType, - const QJsonObject& contentJson = {}); - RoomEvent(Type type, const QJsonObject& json); - ~RoomEvent() override; + // RedactionEvent is an incomplete type here so we cannot inline + // constructors and destructors and we cannot use 'using'. + RoomEvent(Type type, event_mtype_t matrixType, + const QJsonObject& contentJson = {}); + RoomEvent(Type type, const QJsonObject& json); + ~RoomEvent() override; - QString id() const; - QDateTime timestamp() const; - QString roomId() const; - QString senderId() const; - bool isRedacted() const { return bool(_redactedBecause); } - const event_ptr_tt<RedactionEvent>& redactedBecause() const - { - return _redactedBecause; - } - QString redactionReason() const; - QString transactionId() const; - QString stateKey() const; + QString id() const; + QDateTime timestamp() const; + QString roomId() const; + QString senderId() const; + bool isRedacted() const { return bool(_redactedBecause); } + const event_ptr_tt<RedactionEvent>& redactedBecause() const + { + return _redactedBecause; + } + QString redactionReason() const; + QString transactionId() const; + QString stateKey() const; - /** - * Sets the transaction id for locally created events. This should be - * done before the event is exposed to any code using the respective - * Q_PROPERTY. - * - * \param txnId - transaction id, normally obtained from - * Connection::generateTxnId() - */ - void setTransactionId(const QString& txnId); + /** + * Sets the transaction id for locally created events. This should be + * done before the event is exposed to any code using the respective + * Q_PROPERTY. + * + * \param txnId - transaction id, normally obtained from + * Connection::generateTxnId() + */ + void setTransactionId(const QString& txnId); - /** - * Sets event id for locally created events - * - * When a new event is created locally, it has no server id yet. - * This function allows to add the id once the confirmation from - * the server is received. There should be no id set previously - * in the event. It's the responsibility of the code calling addId() - * to notify clients that use Q_PROPERTY(id) about its change - */ - void addId(const QString& newId); + /** + * Sets event id for locally created events + * + * When a new event is created locally, it has no server id yet. + * This function allows to add the id once the confirmation from + * the server is received. There should be no id set previously + * in the event. It's the responsibility of the code calling addId() + * to notify clients that use Q_PROPERTY(id) about its change + */ + void addId(const QString& newId); - private: - event_ptr_tt<RedactionEvent> _redactedBecause; - }; - using RoomEventPtr = event_ptr_tt<RoomEvent>; - using RoomEvents = EventsArray<RoomEvent>; - using RoomEventsRange = Range<RoomEvents>; +private: + event_ptr_tt<RedactionEvent> _redactedBecause; +}; +using RoomEventPtr = event_ptr_tt<RoomEvent>; +using RoomEvents = EventsArray<RoomEvent>; +using RoomEventsRange = Range<RoomEvents>; - class CallEventBase : public RoomEvent - { - public: - CallEventBase(Type type, event_mtype_t matrixType, - const QString& callId, int version, - const QJsonObject& contentJson = {}); - CallEventBase(Type type, const QJsonObject& json); - ~CallEventBase() override = default; - bool isCallEvent() const override { return true; } +class CallEventBase : public RoomEvent +{ +public: + CallEventBase(Type type, event_mtype_t matrixType, const QString& callId, + int version, const QJsonObject& contentJson = {}); + CallEventBase(Type type, const QJsonObject& json); + ~CallEventBase() override = default; + bool isCallEvent() const override { return true; } - QString callId() const { return content<QString>("call_id"_ls); } - int version() const { return content<int>("version"_ls); } - }; + QString callId() const { return content<QString>("call_id"_ls); } + int version() const { return content<int>("version"_ls); } +}; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::RoomEvent*) Q_DECLARE_METATYPE(const QMatrixClient::RoomEvent*) diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 53203873..e6292b73 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -28,31 +28,33 @@ static const std::array<QString, 5> membershipStrings = { QStringLiteral("leave"), QStringLiteral("ban") } }; -namespace QMatrixClient { - template <> struct JsonConverter<MembershipType> { - static MembershipType load(const QJsonValue& jv) - { - const auto& membershipString = jv.toString(); - for (auto it = membershipStrings.begin(); - it != membershipStrings.end(); ++it) - if (membershipString == *it) - return MembershipType(it - membershipStrings.begin()); +namespace QMatrixClient +{ +template <> +struct JsonConverter<MembershipType> +{ + static MembershipType load(const QJsonValue& jv) + { + const auto& membershipString = jv.toString(); + for (auto it = membershipStrings.begin(); it != membershipStrings.end(); + ++it) + if (membershipString == *it) + return MembershipType(it - membershipStrings.begin()); - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; - return MembershipType::Undefined; - } - }; -} + qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; + return MembershipType::Undefined; + } +}; +} // namespace QMatrixClient using namespace QMatrixClient; MemberEventContent::MemberEventContent(const QJsonObject& json) - : membership(fromJson<MembershipType>(json["membership"_ls])), - isDirect(json["is_direct"_ls].toBool()), - displayName(json["displayname"_ls].toString()), - avatarUrl(json["avatar_url"_ls].toString()) -{ -} + : membership(fromJson<MembershipType>(json["membership"_ls])) + , isDirect(json["is_direct"_ls].toBool()) + , displayName(sanitized(json["displayname"_ls].toString())) + , avatarUrl(json["avatar_url"_ls].toString()) +{} void MemberEventContent::fillJson(QJsonObject* o) const { @@ -69,20 +71,20 @@ void MemberEventContent::fillJson(QJsonObject* o) const bool RoomMemberEvent::isInvite() const { return membership() == MembershipType::Invite - && (!prevContent() || prevContent()->membership != membership()); + && (!prevContent() || prevContent()->membership != membership()); } bool RoomMemberEvent::isJoin() const { return membership() == MembershipType::Join - && (!prevContent() || prevContent()->membership != membership()); + && (!prevContent() || prevContent()->membership != membership()); } bool RoomMemberEvent::isLeave() const { return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership != membership() - && prevContent()->membership != MembershipType::Ban; + && prevContent()->membership != membership() + && prevContent()->membership != MembershipType::Ban; } bool RoomMemberEvent::isRename() const diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index 55f419d8..a837b026 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -21,92 +21,92 @@ #include "eventcontent.h" #include "stateevent.h" -namespace QMatrixClient { - class MemberEventContent : public EventContent::Base +namespace QMatrixClient +{ +class MemberEventContent : public EventContent::Base +{ +public: + enum MembershipType : size_t { - public: - enum MembershipType : size_t { - Invite = 0, - Join, - Knock, - Leave, - Ban, - Undefined - }; + Invite = 0, + Join, + Knock, + Leave, + Ban, + Undefined + }; - explicit MemberEventContent(MembershipType mt = Join) : membership(mt) - { - } - explicit MemberEventContent(const QJsonObject& json); + explicit MemberEventContent(MembershipType mt = Join) + : membership(mt) + {} + explicit MemberEventContent(const QJsonObject& json); - MembershipType membership; - bool isDirect = false; - QString displayName; - QUrl avatarUrl; + MembershipType membership; + bool isDirect = false; + QString displayName; + QUrl avatarUrl; - protected: - void fillJson(QJsonObject* o) const override; - }; +protected: + void fillJson(QJsonObject* o) const override; +}; - using MembershipType = MemberEventContent::MembershipType; +using MembershipType = MemberEventContent::MembershipType; - class RoomMemberEvent : public StateEvent<MemberEventContent> - { - Q_GADGET - public: - DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) +class RoomMemberEvent : public StateEvent<MemberEventContent> +{ + Q_GADGET +public: + DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) - using MembershipType = MemberEventContent::MembershipType; + using MembershipType = MemberEventContent::MembershipType; - explicit RoomMemberEvent(const QJsonObject& obj) - : StateEvent(typeId(), obj) - { - } - RoomMemberEvent(MemberEventContent&& c) - : StateEvent(typeId(), matrixTypeId(), c) - { - } + explicit RoomMemberEvent(const QJsonObject& obj) + : StateEvent(typeId(), obj) + {} + RoomMemberEvent(MemberEventContent&& c) + : StateEvent(typeId(), matrixTypeId(), c) + {} - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ - RoomMemberEvent(Type type, const QJsonObject& fullJson) - : StateEvent(type, fullJson) - { - } + /// A special constructor to create unknown RoomMemberEvents + /** + * This is needed in order to use RoomMemberEvent as a "base event + * class" in cases like GetMembersByRoomJob when RoomMemberEvents + * (rather than RoomEvents or StateEvents) are resolved from JSON. + * For such cases loadEvent<> requires an underlying class to be + * constructible with unknownTypeId() instead of its genuine id. + * Don't use it directly. + * \sa GetMembersByRoomJob, loadEvent, unknownTypeId + */ + RoomMemberEvent(Type type, const QJsonObject& fullJson) + : StateEvent(type, fullJson) + {} - MembershipType membership() const { return content().membership; } - QString userId() const { return fullJson()["state_key"_ls].toString(); } - bool isDirect() const { return content().isDirect; } - QString displayName() const { return content().displayName; } - QUrl avatarUrl() const { return content().avatarUrl; } - bool isInvite() const; - bool isJoin() const; - bool isLeave() const; - bool isRename() const; - bool isAvatarUpdate() const; + MembershipType membership() const { return content().membership; } + QString userId() const { return fullJson()["state_key"_ls].toString(); } + bool isDirect() const { return content().isDirect; } + QString displayName() const { return content().displayName; } + QUrl avatarUrl() const { return content().avatarUrl; } + bool isInvite() const; + bool isJoin() const; + bool isLeave() const; + bool isRename() const; + bool isAvatarUpdate() const; - private: - REGISTER_ENUM(MembershipType) - }; +private: + REGISTER_ENUM(MembershipType) +}; - template <> class EventFactory<RoomMemberEvent> +template <> +class EventFactory<RoomMemberEvent> +{ +public: + static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json, + const QString&) { - public: - static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json, - const QString&) - { - return makeEvent<RoomMemberEvent>(json); - } - }; + return makeEvent<RoomMemberEvent>(json); + } +}; - REGISTER_EVENT_TYPE(RoomMemberEvent) - DEFINE_EVENTTYPE_ALIAS(RoomMember, RoomMemberEvent) +REGISTER_EVENT_TYPE(RoomMemberEvent) +DEFINE_EVENTTYPE_ALIAS(RoomMember, RoomMemberEvent) } // namespace QMatrixClient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 0af02eb0..c7f17303 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -40,19 +40,22 @@ static const auto NoticeTypeKey = "m.notice"; static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html"); -template <typename ContentT> TypedBase* make(const QJsonObject& json) +template <typename ContentT> +TypedBase* make(const QJsonObject& json) { return new ContentT(json); } -template <> TypedBase* make<TextContent>(const QJsonObject& json) +template <> +TypedBase* make<TextContent>(const QJsonObject& json) { return json.contains(FormattedBodyKey) || json.contains(RelatesToKey) - ? new TextContent(json) - : nullptr; + ? new TextContent(json) + : nullptr; } -struct MsgTypeDesc { +struct MsgTypeDesc +{ QString matrixType; MsgType enumType; TypedBase* (*maker)(const QJsonObject&); @@ -71,9 +74,10 @@ const std::vector<MsgTypeDesc> msgTypes = { QString msgTypeToJson(MsgType enumType) { - auto it = std::find_if( - msgTypes.begin(), msgTypes.end(), - [=](const MsgTypeDesc& mtd) { return mtd.enumType == enumType; }); + auto it = std::find_if(msgTypes.begin(), msgTypes.end(), + [=](const MsgTypeDesc& mtd) { + return mtd.enumType == enumType; + }); if (it != msgTypes.end()) return it->matrixType; @@ -112,16 +116,14 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, TypedBase* content) : RoomEvent(typeId(), matrixTypeId(), - assembleContentJson(plainBody, jsonMsgType, content)), - _content(content) -{ -} + assembleContentJson(plainBody, jsonMsgType, content)) + , _content(content) +{} RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, TypedBase* content) : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) -{ -} +{} TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) { @@ -155,11 +157,11 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, asGenericFile ? QStringLiteral("m.file") : rawMsgTypeForFile(file), contentFromFile(file, asGenericFile)) -{ -} +{} RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(typeId(), obj), _content(nullptr) + : RoomEvent(typeId(), obj) + , _content(nullptr) { if (isRedacted()) return; @@ -202,15 +204,15 @@ QString RoomMessageEvent::plainBody() const QMimeType RoomMessageEvent::mimeType() const { static const auto PlainTextMimeType = - QMimeDatabase().mimeTypeForName("text/plain"); + QMimeDatabase().mimeTypeForName("text/plain"); return _content ? _content->type() : PlainTextMimeType; } bool RoomMessageEvent::hasTextContent() const { return !content() - || (msgtype() == MsgType::Text || msgtype() == MsgType::Emote - || msgtype() == MsgType::Notice); + || (msgtype() == MsgType::Text || msgtype() == MsgType::Emote + || msgtype() == MsgType::Notice); } bool RoomMessageEvent::hasFileContent() const @@ -226,11 +228,12 @@ bool RoomMessageEvent::hasThumbnail() const QString rawMsgTypeForMimeType(const QMimeType& mimeType) { auto name = mimeType.name(); - return name.startsWith("image/") ? QStringLiteral("m.image") - : name.startsWith("video/") - ? QStringLiteral("m.video") - : name.startsWith("audio/") ? QStringLiteral("m.audio") - : QStringLiteral("m.file"); + return name.startsWith("image/") + ? QStringLiteral("m.image") + : name.startsWith("video/") + ? QStringLiteral("m.video") + : name.startsWith("audio/") ? QStringLiteral("m.audio") + : QStringLiteral("m.file"); } QString RoomMessageEvent::rawMsgTypeForUrl(const QUrl& url) @@ -245,9 +248,9 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi) TextContent::TextContent(const QString& text, const QString& contentType, Omittable<RelatesTo> relatesTo) - : mimeType(QMimeDatabase().mimeTypeForName(contentType)), - body(text), - relatesTo(std::move(relatesTo)) + : mimeType(QMimeDatabase().mimeTypeForName(contentType)) + , body(text) + , relatesTo(std::move(relatesTo)) { if (contentType == HtmlContentTypeId) mimeType = QMimeDatabase().mimeTypeForName("text/html"); @@ -270,10 +273,8 @@ TextContent::TextContent(const QJsonObject& json) mimeType = PlainTextMimeType; body = json[BodyKey].toString(); } - const auto replyJson = json[RelatesToKey] - .toObject() - .value(RelatesTo::ReplyTypeId()) - .toObject(); + const auto replyJson = + json[RelatesToKey].toObject().value(RelatesTo::ReplyTypeId()).toObject(); if (!replyJson.isEmpty()) relatesTo = replyTo(fromJson<QString>(replyJson[EventIdKeyL])); } @@ -292,16 +293,15 @@ void TextContent::fillJson(QJsonObject* json) const LocationContent::LocationContent(const QString& geoUri, const Thumbnail& thumbnail) - : geoUri(geoUri), thumbnail(thumbnail) -{ -} + : geoUri(geoUri) + , thumbnail(thumbnail) +{} LocationContent::LocationContent(const QJsonObject& json) - : TypedBase(json), - geoUri(json["geo_uri"_ls].toString()), - thumbnail(json["info"_ls].toObject()) -{ -} + : TypedBase(json) + , geoUri(json["geo_uri"_ls].toString()) + , thumbnail(json["info"_ls].toObject()) +{} QMimeType LocationContent::type() const { diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index bd1b7c83..eabb21e3 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -23,205 +23,203 @@ class QFileInfo; -namespace QMatrixClient { - namespace MessageEventContent = EventContent; // Back-compatibility +namespace QMatrixClient +{ +namespace MessageEventContent = EventContent; // Back-compatibility + +/** + * The event class corresponding to m.room.message events + */ +class RoomMessageEvent : public RoomEvent +{ + Q_GADGET + Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) + Q_PROPERTY(QString plainBody READ plainBody CONSTANT) + Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) + Q_PROPERTY(EventContent::TypedBase* content READ content CONSTANT) +public: + DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) + + enum class MsgType + { + Text, + Emote, + Notice, + Image, + File, + Location, + Video, + Audio, + Unknown + }; + + RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, + EventContent::TypedBase* content = nullptr); + explicit RoomMessageEvent(const QString& plainBody, + MsgType msgType = MsgType::Text, + EventContent::TypedBase* content = nullptr); + explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, + bool asGenericFile = false); + explicit RoomMessageEvent(const QJsonObject& obj); + + MsgType msgtype() const; + QString rawMsgtype() const; + QString plainBody() const; + EventContent::TypedBase* content() const { return _content.data(); } + template <typename VisitorT> + void editContent(VisitorT visitor) + { + visitor(*_content); + editJson()[ContentKeyL] = assembleContentJson(plainBody(), rawMsgtype(), + content()); + } + QMimeType mimeType() const; + bool hasTextContent() const; + bool hasFileContent() const; + bool hasThumbnail() const; + + static QString rawMsgTypeForUrl(const QUrl& url); + static QString rawMsgTypeForFile(const QFileInfo& fi); + +private: + QScopedPointer<EventContent::TypedBase> _content; + + static QJsonObject assembleContentJson(const QString& plainBody, + const QString& jsonMsgType, + EventContent::TypedBase* content); + + REGISTER_ENUM(MsgType) +}; +REGISTER_EVENT_TYPE(RoomMessageEvent) +DEFINE_EVENTTYPE_ALIAS(RoomMessage, RoomMessageEvent) +using MessageEventType = RoomMessageEvent::MsgType; + +namespace EventContent +{ + // Additional event content types + + struct RelatesTo + { + static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; } + QString type; // The only supported relation so far + QString eventId; + }; + inline RelatesTo replyTo(QString eventId) + { + return { RelatesTo::ReplyTypeId(), std::move(eventId) }; + } /** - * The event class corresponding to m.room.message events + * Rich text content for m.text, m.emote, m.notice + * + * Available fields: mimeType, body. The body can be either rich text + * or plain text, depending on what mimeType specifies. */ - class RoomMessageEvent : public RoomEvent + class TextContent : public TypedBase { - Q_GADGET - Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) - Q_PROPERTY(QString plainBody READ plainBody CONSTANT) - Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) - Q_PROPERTY(EventContent::TypedBase* content READ content CONSTANT) - public: - DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) - - enum class MsgType { - Text, - Emote, - Notice, - Image, - File, - Location, - Video, - Audio, - Unknown - }; - - RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, - EventContent::TypedBase* content = nullptr); - explicit RoomMessageEvent(const QString& plainBody, - MsgType msgType = MsgType::Text, - EventContent::TypedBase* content = nullptr); - explicit RoomMessageEvent(const QString& plainBody, - const QFileInfo& file, - bool asGenericFile = false); - explicit RoomMessageEvent(const QJsonObject& obj); - - MsgType msgtype() const; - QString rawMsgtype() const; - QString plainBody() const; - EventContent::TypedBase* content() const { return _content.data(); } - template <typename VisitorT> void editContent(VisitorT visitor) - { - visitor(*_content); - editJson()[ContentKeyL] = - assembleContentJson(plainBody(), rawMsgtype(), content()); - } - QMimeType mimeType() const; - bool hasTextContent() const; - bool hasFileContent() const; - bool hasThumbnail() const; - - static QString rawMsgTypeForUrl(const QUrl& url); - static QString rawMsgTypeForFile(const QFileInfo& fi); + public: + TextContent(const QString& text, const QString& contentType, + Omittable<RelatesTo> relatesTo = none); + explicit TextContent(const QJsonObject& json); - private: - QScopedPointer<EventContent::TypedBase> _content; + QMimeType type() const override { return mimeType; } - static QJsonObject - assembleContentJson(const QString& plainBody, - const QString& jsonMsgType, - EventContent::TypedBase* content); + QMimeType mimeType; + QString body; + Omittable<RelatesTo> relatesTo; - REGISTER_ENUM(MsgType) + protected: + void fillJson(QJsonObject* json) const override; }; - REGISTER_EVENT_TYPE(RoomMessageEvent) - DEFINE_EVENTTYPE_ALIAS(RoomMessage, RoomMessageEvent) - using MessageEventType = RoomMessageEvent::MsgType; - - namespace EventContent { - // Additional event content types - - struct RelatesTo { - static constexpr const char* ReplyTypeId() - { - return "m.in_reply_to"; - } - QString type; // The only supported relation so far - QString eventId; - }; - inline RelatesTo replyTo(QString eventId) - { - return { RelatesTo::ReplyTypeId(), std::move(eventId) }; - } - /** - * Rich text content for m.text, m.emote, m.notice - * - * Available fields: mimeType, body. The body can be either rich text - * or plain text, depending on what mimeType specifies. - */ - class TextContent : public TypedBase - { - public: - TextContent(const QString& text, const QString& contentType, - Omittable<RelatesTo> relatesTo = none); - explicit TextContent(const QJsonObject& json); - - QMimeType type() const override { return mimeType; } - - QMimeType mimeType; - QString body; - Omittable<RelatesTo> relatesTo; - - protected: - void fillJson(QJsonObject* json) const override; - }; - - /** - * Content class for m.location - * - * Available fields: - * - corresponding to the top-level JSON: - * - geoUri ("geo_uri" in JSON) - * - corresponding to the "info" subobject: - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: - * - thumbnail.payloadSize - * - thumbnail.mimeType - * - thumbnail.imageSize - */ - class LocationContent : public TypedBase - { - public: - LocationContent(const QString& geoUri, - const Thumbnail& thumbnail = {}); - explicit LocationContent(const QJsonObject& json); + /** + * Content class for m.location + * + * Available fields: + * - corresponding to the top-level JSON: + * - geoUri ("geo_uri" in JSON) + * - corresponding to the "info" subobject: + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: + * - thumbnail.payloadSize + * - thumbnail.mimeType + * - thumbnail.imageSize + */ + class LocationContent : public TypedBase + { + public: + LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); + explicit LocationContent(const QJsonObject& json); - QMimeType type() const override; + QMimeType type() const override; - public: - QString geoUri; - Thumbnail thumbnail; + public: + QString geoUri; + Thumbnail thumbnail; - protected: - void fillJson(QJsonObject* o) const override; - }; + protected: + void fillJson(QJsonObject* o) const override; + }; - /** - * A base class for info types that include duration: audio and video - */ - template <typename ContentT> class PlayableContent : public ContentT + /** + * A base class for info types that include duration: audio and video + */ + template <typename ContentT> + class PlayableContent : public ContentT + { + public: + using ContentT::ContentT; + PlayableContent(const QJsonObject& json) + : ContentT(json) + , duration(ContentT::originalInfoJson["duration"_ls].toInt()) + {} + + protected: + void fillJson(QJsonObject* json) const override { - public: - using ContentT::ContentT; - PlayableContent(const QJsonObject& json) - : ContentT(json), - duration(ContentT::originalInfoJson["duration"_ls].toInt()) - { - } - - protected: - void fillJson(QJsonObject* json) const override - { - ContentT::fillJson(json); - auto infoJson = json->take("info"_ls).toObject(); - infoJson.insert(QStringLiteral("duration"), duration); - json->insert(QStringLiteral("info"), infoJson); - } - - public: - int duration; - }; - - /** - * Content class for m.video - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the CS API spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - duration - * - imageSize (QSize for a combination of "h" and "w" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field, in the same vein as for "info": - * - payloadSize - * - mimeType - * - imageSize - */ - using VideoContent = - PlayableContent<UrlWithThumbnailContent<ImageInfo>>; - - /** - * Content class for m.audio - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the CS API spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - duration - */ - using AudioContent = PlayableContent<UrlBasedContent<FileInfo>>; - } // namespace EventContent + ContentT::fillJson(json); + auto infoJson = json->take("info"_ls).toObject(); + infoJson.insert(QStringLiteral("duration"), duration); + json->insert(QStringLiteral("info"), infoJson); + } + + public: + int duration; + }; + + /** + * Content class for m.video + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename (extension to the CS API spec) + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - duration + * - imageSize (QSize for a combination of "h" and "w" in JSON) + * - thumbnail.url ("thumbnail_url" in JSON) + * - corresponding to the "info/thumbnail_info" subobject: contents of + * thumbnail field, in the same vein as for "info": + * - payloadSize + * - mimeType + * - imageSize + */ + using VideoContent = PlayableContent<UrlWithThumbnailContent<ImageInfo>>; + + /** + * Content class for m.audio + * + * Available fields: + * - corresponding to the top-level JSON: + * - url + * - filename (extension to the CS API spec) + * - corresponding to the "info" subobject: + * - payloadSize ("size" in JSON) + * - mimeType ("mimetype" in JSON) + * - duration + */ + using AudioContent = PlayableContent<UrlBasedContent<FileInfo>>; +} // namespace EventContent } // namespace QMatrixClient diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h index 5b7ade76..aa9cb766 100644 --- a/lib/events/roomtombstoneevent.h +++ b/lib/events/roomtombstoneevent.h @@ -20,22 +20,22 @@ #include "stateevent.h" -namespace QMatrixClient { - class RoomTombstoneEvent : public StateEventBase - { - public: - DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) +namespace QMatrixClient +{ +class RoomTombstoneEvent : public StateEventBase +{ +public: + DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent) - explicit RoomTombstoneEvent() : StateEventBase(typeId(), matrixTypeId()) - { - } - explicit RoomTombstoneEvent(const QJsonObject& obj) - : StateEventBase(typeId(), obj) - { - } + explicit RoomTombstoneEvent() + : StateEventBase(typeId(), matrixTypeId()) + {} + explicit RoomTombstoneEvent(const QJsonObject& obj) + : StateEventBase(typeId(), obj) + {} - QString serverMessage() const; - QString successorRoomId() const; - }; - REGISTER_EVENT_TYPE(RoomTombstoneEvent) -} + QString serverMessage() const; + QString successorRoomId() const; +}; +REGISTER_EVENT_TYPE(RoomTombstoneEvent) +} // namespace QMatrixClient diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index 430eacb0..7ad2efa6 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -18,74 +18,76 @@ #pragma once +#include "converters.h" #include "stateevent.h" -#include "converters.h" +namespace QMatrixClient +{ +namespace EventContent +{ + template <typename T> + class SimpleContent + { + public: + using value_type = T; -namespace QMatrixClient { - namespace EventContent { - template <typename T> class SimpleContent + // The constructor is templated to enable perfect forwarding + template <typename TT> + SimpleContent(QString keyName, TT&& value) + : value(std::forward<TT>(value)) + , key(std::move(keyName)) + {} + SimpleContent(const QJsonObject& json, QString keyName) + : value(fromJson<T>(json[keyName])) + , key(std::move(keyName)) + {} + QJsonObject toJson() const { - public: - using value_type = T; - - // The constructor is templated to enable perfect forwarding - template <typename TT> - SimpleContent(QString keyName, TT&& value) - : value(std::forward<TT>(value)), key(std::move(keyName)) - { - } - SimpleContent(const QJsonObject& json, QString keyName) - : value(fromJson<T>(json[keyName])), key(std::move(keyName)) - { - } - QJsonObject toJson() const - { - return { { key, QMatrixClient::toJson(value) } }; - } + return { { key, QMatrixClient::toJson(value) } }; + } - public: - T value; + public: + T value; - protected: - QString key; - }; - } // namespace EventContent + protected: + QString key; + }; +} // namespace EventContent #define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \ class _Name : public StateEvent<EventContent::SimpleContent<_ValueType>> \ { \ - public: \ + public: \ using value_type = content_type::value_type; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name() : _Name(value_type()) {} \ + explicit _Name() \ + : _Name(value_type()) \ + {} \ template <typename T> \ explicit _Name(T&& value) \ : StateEvent(typeId(), matrixTypeId(), \ QStringLiteral(#_ContentKey), std::forward<T>(value)) \ - { \ - } \ + {} \ explicit _Name(QJsonObject obj) \ : StateEvent(typeId(), std::move(obj), \ QStringLiteral(#_ContentKey)) \ - { \ - } \ + {} \ auto _ContentKey() const { return content().value; } \ }; \ REGISTER_EVENT_TYPE(_Name) \ // End of macro - DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) - DEFINE_EVENTTYPE_ALIAS(RoomName, RoomNameEvent) - DEFINE_SIMPLE_STATE_EVENT(RoomAliasesEvent, "m.room.aliases", QStringList, - aliases) - DEFINE_EVENTTYPE_ALIAS(RoomAliases, RoomAliasesEvent) - DEFINE_SIMPLE_STATE_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias", - QString, alias) - DEFINE_EVENTTYPE_ALIAS(RoomCanonicalAlias, RoomCanonicalAliasEvent) - DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) - DEFINE_EVENTTYPE_ALIAS(RoomTopic, RoomTopicEvent) - DEFINE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption", QString, - algorithm) - DEFINE_EVENTTYPE_ALIAS(RoomEncryption, EncryptionEvent) +DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) +DEFINE_EVENTTYPE_ALIAS(RoomName, RoomNameEvent) +DEFINE_SIMPLE_STATE_EVENT(RoomAliasesEvent, "m.room.aliases", QStringList, + aliases) +DEFINE_EVENTTYPE_ALIAS(RoomAliases, RoomAliasesEvent) +DEFINE_SIMPLE_STATE_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias", + QString, alias) +DEFINE_EVENTTYPE_ALIAS(RoomCanonicalAlias, RoomCanonicalAliasEvent) +DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) +DEFINE_EVENTTYPE_ALIAS(RoomTopic, RoomTopicEvent) +DEFINE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption", QString, + algorithm) +DEFINE_EVENTTYPE_ALIAS(RoomEncryption, EncryptionEvent) } // namespace QMatrixClient diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index f172edb6..7fea59a1 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -24,10 +24,9 @@ using namespace QMatrixClient; // StateEventBase itself can be instantiated if there's a state_key JSON key // but the event type is unknown. [[gnu::unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod([](const QJsonObject& json, - const QString& matrixType) - -> StateEventPtr { - if (!json.contains("state_key")) + RoomEvent::factory_t::addMethod( + [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { + if (!json.contains("state_key"_ls)) return nullptr; if (auto e = StateEventBase::factory_t::make(json, matrixType)) @@ -53,7 +52,7 @@ void StateEventBase::dumpTo(QDebug dbg) const dbg << '<' << stateKey() << "> "; if (unsignedJson().contains(PrevContentKeyL)) dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject()) - .toJson(QJsonDocument::Compact) + .toJson(QJsonDocument::Compact) << " -> "; RoomEvent::dumpTo(dbg); } diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 1a02646b..8a89c86c 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -20,110 +20,111 @@ #include "roomevent.h" -namespace QMatrixClient { - class StateEventBase : public RoomEvent - { - public: - using factory_t = EventFactory<StateEventBase>; +namespace QMatrixClient +{ +class StateEventBase : public RoomEvent +{ +public: + using factory_t = EventFactory<StateEventBase>; - using RoomEvent::RoomEvent; - ~StateEventBase() override = default; + using RoomEvent::RoomEvent; + ~StateEventBase() override = default; - bool isStateEvent() const override { return true; } - QString replacedState() const; - void dumpTo(QDebug dbg) const override; + bool isStateEvent() const override { return true; } + QString replacedState() const; + void dumpTo(QDebug dbg) const override; - virtual bool repeatsState() const; - }; - using StateEventPtr = event_ptr_tt<StateEventBase>; - using StateEvents = EventsArray<StateEventBase>; + virtual bool repeatsState() const; +}; +using StateEventPtr = event_ptr_tt<StateEventBase>; +using StateEvents = EventsArray<StateEventBase>; - template <> inline bool is<StateEventBase>(const Event& e) - { - return e.isStateEvent(); - } +template <> +inline bool is<StateEventBase>(const Event& e) +{ + return e.isStateEvent(); +} + +/** + * A combination of event type and state key uniquely identifies a piece + * of state in Matrix. + * \sa + * https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events + */ +using StateEventKey = QPair<QString, QString>; - /** - * A combination of event type and state key uniquely identifies a piece - * of state in Matrix. - * \sa - * https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events - */ - using StateEventKey = QPair<QString, QString>; +template <typename ContentT> +struct Prev +{ + template <typename... ContentParamTs> + explicit Prev(const QJsonObject& unsignedJson, + ContentParamTs&&... contentParams) + : senderId(unsignedJson.value("prev_sender"_ls).toString()) + , content(unsignedJson.value(PrevContentKeyL).toObject(), + std::forward<ContentParamTs>(contentParams)...) + {} - template <typename ContentT> struct Prev { - template <typename... ContentParamTs> - explicit Prev(const QJsonObject& unsignedJson, - ContentParamTs&&... contentParams) - : senderId(unsignedJson.value("prev_sender"_ls).toString()), - content(unsignedJson.value(PrevContentKeyL).toObject(), - std::forward<ContentParamTs>(contentParams)...) - { - } + QString senderId; + ContentT content; +}; - QString senderId; - ContentT content; - }; +template <typename ContentT> +class StateEvent : public StateEventBase +{ +public: + using content_type = ContentT; - template <typename ContentT> class StateEvent : public StateEventBase + template <typename... ContentParamTs> + explicit StateEvent(Type type, const QJsonObject& fullJson, + ContentParamTs&&... contentParams) + : StateEventBase(type, fullJson) + , _content(contentJson(), std::forward<ContentParamTs>(contentParams)...) { - public: - using content_type = ContentT; - - template <typename... ContentParamTs> - explicit StateEvent(Type type, const QJsonObject& fullJson, - ContentParamTs&&... contentParams) - : StateEventBase(type, fullJson), - _content(contentJson(), - std::forward<ContentParamTs>(contentParams)...) - { - const auto& unsignedData = unsignedJson(); - if (unsignedData.contains(PrevContentKeyL)) - _prev = std::make_unique<Prev<ContentT>>( - unsignedData, - std::forward<ContentParamTs>(contentParams)...); - } - template <typename... ContentParamTs> - explicit StateEvent(Type type, event_mtype_t matrixType, - ContentParamTs&&... contentParams) - : StateEventBase(type, matrixType), - _content(std::forward<ContentParamTs>(contentParams)...) - { - editJson().insert(ContentKey, _content.toJson()); - } + const auto& unsignedData = unsignedJson(); + if (unsignedData.contains(PrevContentKeyL)) + _prev = std::make_unique<Prev<ContentT>>( + unsignedData, std::forward<ContentParamTs>(contentParams)...); + } + template <typename... ContentParamTs> + explicit StateEvent(Type type, event_mtype_t matrixType, + ContentParamTs&&... contentParams) + : StateEventBase(type, matrixType) + , _content(std::forward<ContentParamTs>(contentParams)...) + { + editJson().insert(ContentKey, _content.toJson()); + } - const ContentT& content() const { return _content; } - template <typename VisitorT> void editContent(VisitorT&& visitor) - { - visitor(_content); - editJson()[ContentKeyL] = _content.toJson(); - } - [[deprecated("Use prevContent instead")]] const ContentT* - prev_content() const - { - return prevContent(); - } - const ContentT* prevContent() const - { - return _prev ? &_prev->content : nullptr; - } - QString prevSenderId() const - { - return _prev ? _prev->senderId : QString(); - } + const ContentT& content() const { return _content; } + template <typename VisitorT> + void editContent(VisitorT&& visitor) + { + visitor(_content); + editJson()[ContentKeyL] = _content.toJson(); + } + [[deprecated("Use prevContent instead")]] const ContentT* prev_content() const + { + return prevContent(); + } + const ContentT* prevContent() const + { + return _prev ? &_prev->content : nullptr; + } + QString prevSenderId() const { return _prev ? _prev->senderId : QString(); } - private: - ContentT _content; - std::unique_ptr<Prev<ContentT>> _prev; - }; +private: + ContentT _content; + std::unique_ptr<Prev<ContentT>> _prev; +}; } // namespace QMatrixClient -namespace std { - template <> struct hash<QMatrixClient::StateEventKey> { - size_t - operator()(const QMatrixClient::StateEventKey& k) const Q_DECL_NOEXCEPT - { - return qHash(k); - } - }; -} +namespace std +{ +template <> +struct hash<QMatrixClient::StateEventKey> +{ + size_t operator()(const QMatrixClient::StateEventKey& k) const Q_DECL_NOEXCEPT + { + return qHash(k); + } +}; +} // namespace std diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index ee3d6b67..128a206a 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -22,7 +22,8 @@ using namespace QMatrixClient; -TypingEvent::TypingEvent(const QJsonObject& obj) : Event(typeId(), obj) +TypingEvent::TypingEvent(const QJsonObject& obj) + : Event(typeId(), obj) { const auto& array = contentJson()["user_ids"_ls].toArray(); for (const auto& user : array) diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h index f66a1fbc..241359b4 100644 --- a/lib/events/typingevent.h +++ b/lib/events/typingevent.h @@ -20,19 +20,20 @@ #include "event.h" -namespace QMatrixClient { - class TypingEvent : public Event - { - public: - DEFINE_EVENT_TYPEID("m.typing", TypingEvent) +namespace QMatrixClient +{ +class TypingEvent : public Event +{ +public: + DEFINE_EVENT_TYPEID("m.typing", TypingEvent) - TypingEvent(const QJsonObject& obj); + TypingEvent(const QJsonObject& obj); - const QStringList& users() const { return _users; } + const QStringList& users() const { return _users; } - private: - QStringList _users; - }; - REGISTER_EVENT_TYPE(TypingEvent) - DEFINE_EVENTTYPE_ALIAS(Typing, TypingEvent) +private: + QStringList _users; +}; +REGISTER_EVENT_TYPE(TypingEvent) +DEFINE_EVENTTYPE_ALIAS(Typing, TypingEvent) } // namespace QMatrixClient diff --git a/lib/identity/definitions/request_email_validation.cpp b/lib/identity/definitions/request_email_validation.cpp index a2d0a707..131b9488 100644 --- a/lib/identity/definitions/request_email_validation.cpp +++ b/lib/identity/definitions/request_email_validation.cpp @@ -7,7 +7,7 @@ using namespace QMatrixClient; void JsonObjectConverter<RequestEmailValidation>::dumpTo( - QJsonObject& jo, const RequestEmailValidation& pod) + QJsonObject& jo, const RequestEmailValidation& pod) { addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); addParam<>(jo, QStringLiteral("email"), pod.email); @@ -16,7 +16,7 @@ void JsonObjectConverter<RequestEmailValidation>::dumpTo( } void JsonObjectConverter<RequestEmailValidation>::fillFrom( - const QJsonObject& jo, RequestEmailValidation& result) + const QJsonObject& jo, RequestEmailValidation& result) { fromJson(jo.value("client_secret"_ls), result.clientSecret); fromJson(jo.value("email"_ls), result.email); diff --git a/lib/identity/definitions/request_email_validation.h b/lib/identity/definitions/request_email_validation.h index 51369039..2496d7f5 100644 --- a/lib/identity/definitions/request_email_validation.h +++ b/lib/identity/definitions/request_email_validation.h @@ -6,35 +6,37 @@ #include "converters.h" -#include "converters.h" +namespace QMatrixClient +{ + +// Data structures -namespace QMatrixClient { - // Data structures +struct RequestEmailValidation +{ + /// A unique string generated by the client, and used to identify + /// thevalidation attempt. It must be a string consisting of the + /// characters``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters + /// and itmust not be empty. + QString clientSecret; + /// The email address to validate. + QString email; + /// The server will only send an email if the ``send_attempt``is a number + /// greater than the most recent one which it has seen,scoped to that + /// ``email`` + ``client_secret`` pair. This is toavoid repeatedly sending + /// the same email in the case of requestretries between the POSTing user + /// and the identity server.The client should increment this value if they + /// desire a newemail (e.g. a reminder) to be sent. + int sendAttempt; + /// Optional. When the validation is completed, the identityserver will + /// redirect the user to this URL. + QString nextLink; +}; - struct RequestEmailValidation { - /// A unique string generated by the client, and used to identify the - /// validation attempt. It must be a string consisting of the characters - /// ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - /// it must not be empty. - QString clientSecret; - /// The email address to validate. - QString email; - /// The server will only send an email if the ``send_attempt`` - /// is a number greater than the most recent one which it has seen, - /// scoped to that ``email`` + ``client_secret`` pair. This is to - /// avoid repeatedly sending the same email in the case of request - /// retries between the POSTing user and the identity server. - /// The client should increment this value if they desire a new - /// email (e.g. a reminder) to be sent. - int sendAttempt; - /// Optional. When the validation is completed, the identity - /// server will redirect the user to this URL. - QString nextLink; - }; - template <> struct JsonObjectConverter<RequestEmailValidation> { - static void dumpTo(QJsonObject& jo, const RequestEmailValidation& pod); - static void fillFrom(const QJsonObject& jo, - RequestEmailValidation& pod); - }; +template <> +struct JsonObjectConverter<RequestEmailValidation> +{ + static void dumpTo(QJsonObject& jo, const RequestEmailValidation& pod); + static void fillFrom(const QJsonObject& jo, RequestEmailValidation& pod); +}; } // namespace QMatrixClient diff --git a/lib/identity/definitions/request_msisdn_validation.cpp b/lib/identity/definitions/request_msisdn_validation.cpp index 9facbf7e..0087d202 100644 --- a/lib/identity/definitions/request_msisdn_validation.cpp +++ b/lib/identity/definitions/request_msisdn_validation.cpp @@ -7,7 +7,7 @@ using namespace QMatrixClient; void JsonObjectConverter<RequestMsisdnValidation>::dumpTo( - QJsonObject& jo, const RequestMsisdnValidation& pod) + QJsonObject& jo, const RequestMsisdnValidation& pod) { addParam<>(jo, QStringLiteral("client_secret"), pod.clientSecret); addParam<>(jo, QStringLiteral("country"), pod.country); @@ -17,7 +17,7 @@ void JsonObjectConverter<RequestMsisdnValidation>::dumpTo( } void JsonObjectConverter<RequestMsisdnValidation>::fillFrom( - const QJsonObject& jo, RequestMsisdnValidation& result) + const QJsonObject& jo, RequestMsisdnValidation& result) { fromJson(jo.value("client_secret"_ls), result.clientSecret); fromJson(jo.value("country"_ls), result.country); diff --git a/lib/identity/definitions/request_msisdn_validation.h b/lib/identity/definitions/request_msisdn_validation.h index c4fe479e..f8060cfc 100644 --- a/lib/identity/definitions/request_msisdn_validation.h +++ b/lib/identity/definitions/request_msisdn_validation.h @@ -6,38 +6,41 @@ #include "converters.h" -#include "converters.h" +namespace QMatrixClient +{ + +// Data structures -namespace QMatrixClient { - // Data structures +struct RequestMsisdnValidation +{ + /// A unique string generated by the client, and used to identify + /// thevalidation attempt. It must be a string consisting of the + /// characters``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters + /// and itmust not be empty. + QString clientSecret; + /// The two-letter uppercase ISO country code that the number + /// in``phone_number`` should be parsed as if it were dialled from. + QString country; + /// The phone number to validate. + QString phoneNumber; + /// The server will only send an SMS if the ``send_attempt`` is anumber + /// greater than the most recent one which it has seen,scoped to that + /// ``country`` + ``phone_number`` + ``client_secret``triple. This is to + /// avoid repeatedly sending the same SMS inthe case of request retries + /// between the POSTing user and theidentity server. The client should + /// increment this value ifthey desire a new SMS (e.g. a reminder) to be + /// sent. + int sendAttempt; + /// Optional. When the validation is completed, the identityserver will + /// redirect the user to this URL. + QString nextLink; +}; - struct RequestMsisdnValidation { - /// A unique string generated by the client, and used to identify the - /// validation attempt. It must be a string consisting of the characters - /// ``[0-9a-zA-Z.=_-]``. Its length must not exceed 255 characters and - /// it must not be empty. - QString clientSecret; - /// The two-letter uppercase ISO country code that the number in - /// ``phone_number`` should be parsed as if it were dialled from. - QString country; - /// The phone number to validate. - QString phoneNumber; - /// The server will only send an SMS if the ``send_attempt`` is a - /// number greater than the most recent one which it has seen, - /// scoped to that ``country`` + ``phone_number`` + ``client_secret`` - /// triple. This is to avoid repeatedly sending the same SMS in - /// the case of request retries between the POSTing user and the - /// identity server. The client should increment this value if - /// they desire a new SMS (e.g. a reminder) to be sent. - int sendAttempt; - /// Optional. When the validation is completed, the identity - /// server will redirect the user to this URL. - QString nextLink; - }; - template <> struct JsonObjectConverter<RequestMsisdnValidation> { - static void dumpTo(QJsonObject& jo, const RequestMsisdnValidation& pod); - static void fillFrom(const QJsonObject& jo, - RequestMsisdnValidation& pod); - }; +template <> +struct JsonObjectConverter<RequestMsisdnValidation> +{ + static void dumpTo(QJsonObject& jo, const RequestMsisdnValidation& pod); + static void fillFrom(const QJsonObject& jo, RequestMsisdnValidation& pod); +}; } // namespace QMatrixClient diff --git a/lib/identity/definitions/sid.h b/lib/identity/definitions/sid.h index 85462b46..752d62bb 100644 --- a/lib/identity/definitions/sid.h +++ b/lib/identity/definitions/sid.h @@ -6,19 +6,25 @@ #include "converters.h" -namespace QMatrixClient { - // Data structures +namespace QMatrixClient +{ - struct Sid { - /// The session ID. Session IDs are opaque strings generated by the - /// identity server. They must consist entirely of the characters - /// ``[0-9a-zA-Z.=_-]``. Their length must not exceed 255 characters and - /// they must not be empty. - QString sid; - }; - template <> struct JsonObjectConverter<Sid> { - static void dumpTo(QJsonObject& jo, const Sid& pod); - static void fillFrom(const QJsonObject& jo, Sid& pod); - }; +// Data structures + +struct Sid +{ + /// The session ID. Session IDs are opaque strings generated by the + /// identityserver. They must consist entirely of the + /// characters``[0-9a-zA-Z.=_-]``. Their length must not exceed 255 + /// characters and theymust not be empty. + QString sid; +}; + +template <> +struct JsonObjectConverter<Sid> +{ + static void dumpTo(QJsonObject& jo, const Sid& pod); + static void fillFrom(const QJsonObject& jo, Sid& pod); +}; } // namespace QMatrixClient diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp index a023d4f7..a0a3dc29 100644 --- a/lib/jobs/basejob.cpp +++ b/lib/jobs/basejob.cpp @@ -32,7 +32,8 @@ using namespace QMatrixClient; -struct NetworkReplyDeleter : public QScopedPointerDeleteLater { +struct NetworkReplyDeleter : public QScopedPointerDeleteLater +{ static inline void cleanup(QNetworkReply* reply) { if (reply && reply->isRunning()) @@ -43,18 +44,17 @@ struct NetworkReplyDeleter : public QScopedPointerDeleteLater { class BaseJob::Private { - public: +public: // Using an idiom from clang-tidy: // http://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html Private(HttpVerb v, QString endpoint, const QUrlQuery& q, Data&& data, bool nt) - : verb(v), - apiEndpoint(std::move(endpoint)), - requestQuery(q), - requestData(std::move(data)), - needsToken(nt) - { - } + : verb(v) + , apiEndpoint(std::move(endpoint)) + , requestQuery(q) + , requestData(std::move(data)) + , needsToken(nt) + {} void sendRequest(bool inBackground); const JobTimeoutConfig& getCurrentTimeoutConfig() const; @@ -94,8 +94,7 @@ class BaseJob::Private BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, bool needsToken) : BaseJob(verb, name, endpoint, Query {}, Data {}, needsToken) -{ -} +{} BaseJob::BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, const Query& query, Data&& data, bool needsToken) @@ -121,9 +120,9 @@ QUrl BaseJob::requestUrl() const bool BaseJob::isBackground() const { return d->reply - && d->reply->request() - .attribute(QNetworkRequest::BackgroundRequestAttribute) - .toBool(); + && d->reply->request() + .attribute(QNetworkRequest::BackgroundRequestAttribute) + .toBool(); } const QString& BaseJob::apiEndpoint() const { return d->apiEndpoint; } @@ -182,7 +181,7 @@ QUrl BaseJob::makeRequestUrl(QUrl baseUrl, const QString& path, if (!pathBase.endsWith('/') && !path.startsWith('/')) pathBase.push_back('/'); - baseUrl.setPath(pathBase + path); + baseUrl.setPath(pathBase + path, QUrl::TolerantMode); baseUrl.setQuery(query); return baseUrl; } @@ -253,8 +252,7 @@ void BaseJob::sendRequest(bool inBackground) if (!d->requestQuery.isEmpty()) qCDebug(d->logCat) << " query:" << d->requestQuery.toString(); d->sendRequest(inBackground); - connect(d->reply.data(), &QNetworkReply::finished, this, - &BaseJob::gotReply); + connect(d->reply.data(), &QNetworkReply::finished, this, &BaseJob::gotReply); if (d->reply->isRunning()) { connect(d->reply.data(), &QNetworkReply::metaDataChanged, this, &BaseJob::checkReply); @@ -279,10 +277,10 @@ void BaseJob::gotReply() else { // FIXME: Factor out to smth like BaseJob::handleError() d->rawResponse = d->reply->readAll(); - const auto jsonBody = - d->reply->rawHeader("Content-Type") == "application/json"; - qCDebug(d->logCat).noquote() << "Error body (truncated if long):" - << d->rawResponse.left(500); + const auto jsonBody = d->reply->rawHeader("Content-Type") + == "application/json"; + qCDebug(d->logCat).noquote() + << "Error body (truncated if long):" << d->rawResponse.left(500); if (jsonBody) { auto json = QJsonDocument::fromJson(d->rawResponse).object(); const auto errCode = json.value("errcode"_ls).toString(); @@ -291,8 +289,8 @@ void BaseJob::gotReply() QString msg = tr("Too many requests"); auto retryInterval = json.value("retry_after_ms"_ls).toInt(-1); if (retryInterval != -1) - msg += tr(", next retry advised after %1 ms") - .arg(retryInterval); + msg += + tr(", next retry advised after %1 ms").arg(retryInterval); else // We still have to figure some reasonable interval retryInterval = getNextRetryInterval(); @@ -301,7 +299,7 @@ void BaseJob::gotReply() // Shortcut to retry instead of executing finishJob() stop(); qCWarning(d->logCat) - << this << "will retry in" << retryInterval << "ms"; + << this << "will retry in" << retryInterval << "ms"; d->retryTimer.start(retryInterval); emit retryScheduled(d->retriesTaken, retryInterval); return; @@ -314,11 +312,10 @@ void BaseJob::gotReply() d->status.code = UnsupportedRoomVersionError; if (json.contains("room_version")) d->status.message = - tr("Requested room version: %1") - .arg(json.value("room_version").toString()); + tr("Requested room version: %1") + .arg(json.value("room_version").toString()); } else if (!json.isEmpty()) // Not localisable on the client side - setStatus(IncorrectRequestError, - json.value("error"_ls).toString()); + setStatus(d->status.code, json.value("error"_ls).toString()); } } @@ -341,7 +338,7 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) Q_ASSERT_X(patternParts.size() <= 2, __FUNCTION__, "BaseJob: Expected content type should have up to two" " /-separated parts; violating pattern: " - + pattern); + + pattern); if (ctype.split('/').front() == patternParts.front() && patternParts.back() == "*") @@ -358,25 +355,23 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const // so check genuine HTTP codes. The below processing is based on // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes const auto httpCodeHeader = - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (!httpCodeHeader.isValid()) { qCWarning(d->logCat) << this << "didn't get valid HTTP headers"; return { NetworkError, reply->errorString() }; } const QString replyState = reply->isRunning() - ? QStringLiteral("(tentative)") - : QStringLiteral("(final)"); + ? QStringLiteral("(tentative)") + : QStringLiteral("(final)"); const auto urlString = '|' + d->reply->url().toDisplayString(); const auto httpCode = httpCodeHeader.toInt(); const auto reason = - reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute) - .toString(); + reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); if (httpCode / 100 == 2) // 2xx { qCDebug(d->logCat).noquote().nospace() << this << urlString; - qCDebug(d->logCat).noquote() - << " " << httpCode << reason << replyState; + qCDebug(d->logCat).noquote() << " " << httpCode << reason << replyState; if (!checkContentType(reply->rawHeader("Content-Type"), d->expectedContentTypes)) return { UnexpectedResponseTypeWarning, @@ -423,7 +418,7 @@ BaseJob::Status BaseJob::doCheckReply(QNetworkReply* reply) const BaseJob::Status BaseJob::parseReply(QNetworkReply* reply) { d->rawResponse = reply->readAll(); - QJsonParseError error; + QJsonParseError error { 0, QJsonParseError::MissingObject }; const auto& json = QJsonDocument::fromJson(d->rawResponse, &error); if (error.error == QJsonParseError::NoError) return parseJson(json); @@ -440,7 +435,7 @@ void BaseJob::stop() d->reply->disconnect(this); // Ignore whatever comes from the reply if (d->reply->isRunning()) { qCWarning(d->logCat) - << this << "stopped without ready network reply"; + << this << "stopped without ready network reply"; d->reply->abort(); } } else @@ -455,12 +450,12 @@ void BaseJob::finishJob() // TODO: The whole retrying thing should be put to ConnectionManager // otherwise independently retrying jobs make a bit of notification // storm towards the UI. - const auto retryInterval = - error() == TimeoutError ? 0 : getNextRetryInterval(); + const auto retryInterval = error() == TimeoutError + ? 0 + : getNextRetryInterval(); ++d->retriesTaken; - qCWarning(d->logCat).nospace() - << this << ": retry #" << d->retriesTaken << " in " - << retryInterval / 1000 << " s"; + qCWarning(d->logCat).nospace() << this << ": retry #" << d->retriesTaken + << " in " << retryInterval / 1000 << " s"; d->retryTimer.start(retryInterval); emit retryScheduled(d->retriesTaken, retryInterval); return; @@ -510,19 +505,20 @@ BaseJob::Status BaseJob::status() const { return d->status; } QByteArray BaseJob::rawData(int bytesAtMost) const { return bytesAtMost > 0 && d->rawResponse.size() > bytesAtMost - ? d->rawResponse.left(bytesAtMost) - : d->rawResponse; + ? d->rawResponse.left(bytesAtMost) + : d->rawResponse; } QString BaseJob::rawDataSample(int bytesAtMost) const { auto data = rawData(bytesAtMost); Q_ASSERT(data.size() <= d->rawResponse.size()); - return data.size() == d->rawResponse.size() ? data - : data - + tr("...(truncated, %Ln bytes in total)", - "Comes after trimmed raw network response", - d->rawResponse.size()); + return data.size() == d->rawResponse.size() + ? data + : data + + tr("...(truncated, %Ln bytes in total)", + "Comes after trimmed raw network response", + d->rawResponse.size()); } QString BaseJob::statusCaption() const @@ -538,8 +534,6 @@ QString BaseJob::statusCaption() const return tr("Request was abandoned"); case NetworkError: return tr("Network problems"); - case JsonParseError: - return tr("Response could not be parsed"); case TimeoutError: return tr("Request timed out"); case ContentAccessError: @@ -573,10 +567,25 @@ QUrl BaseJob::errorUrl() const { return d->errorUrl; } void BaseJob::setStatus(Status s) { + // The crash that led to this code has been reported in + // https://github.com/QMatrixClient/Quaternion/issues/566 - basically, + // when cleaning up childrent of a deleted Connection, there's a chance + // of pending jobs being abandoned, calling setStatus(Abandoned). + // There's nothing wrong with this; however, the safety check for + // cleartext access tokens below uses d->connection - which is a dangling + // pointer. + // To alleviate that, a stricter condition is applied, that for Abandoned + // and to-be-Abandoned jobs the status message will be disregarded entirely. + // For 0.6 we might rectify the situation by making d->connection + // a QPointer<> (and derive ConnectionData from QObject, respectively). + if (d->status.code == Abandoned || s.code == Abandoned) + s.message.clear(); + if (d->status == s) return; - if (d->connection && !d->connection->accessToken().isEmpty()) + if (!s.message.isEmpty() && d->connection + && !d->connection->accessToken().isEmpty()) s.message.replace(d->connection->accessToken(), "(REDACTED)"); if (!s.good()) qCWarning(d->logCat) << this << "status" << s; diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h index 8ff25d42..04d79c66 100644 --- a/lib/jobs/basejob.h +++ b/lib/jobs/basejob.h @@ -28,329 +28,350 @@ class QNetworkReply; class QSslError; -namespace QMatrixClient { - class ConnectionData; - - enum class HttpVerb { Get, Put, Post, Delete }; +namespace QMatrixClient +{ +class ConnectionData; + +enum class HttpVerb +{ + Get, + Put, + Post, + Delete +}; + +struct JobTimeoutConfig +{ + int jobTimeout; + int nextRetryInterval; +}; + +class BaseJob : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) + Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) +public: + enum StatusCode + { + NoError = 0 // To be compatible with Qt conventions + , + Success = 0, + Pending = 1, + WarningLevel = 20, + UnexpectedResponseType = 21, + UnexpectedResponseTypeWarning = UnexpectedResponseType, + Abandoned = 50 //< A very brief period between abandoning and object + //deletion + , + ErrorLevel = 100 //< Errors have codes starting from this + , + NetworkError = 100, + Timeout, + TimeoutError = Timeout, + ContentAccessError, + NotFoundError, + IncorrectRequest, + IncorrectRequestError = IncorrectRequest, + IncorrectResponse, + IncorrectResponseError = IncorrectResponse, + JsonParseError //< deprecated; Use IncorrectResponse instead + = IncorrectResponse, + TooManyRequests, + TooManyRequestsError = TooManyRequests, + RequestNotImplemented, + RequestNotImplementedError = RequestNotImplemented, + UnsupportedRoomVersion, + UnsupportedRoomVersionError = UnsupportedRoomVersion, + NetworkAuthRequired, + NetworkAuthRequiredError = NetworkAuthRequired, + UserConsentRequired, + UserConsentRequiredError = UserConsentRequired, + UserDefinedError = 256 + }; - struct JobTimeoutConfig { - int jobTimeout; - int nextRetryInterval; + /** + * A simple wrapper around QUrlQuery that allows its creation from + * a list of string pairs + */ + class Query : public QUrlQuery + { + public: + using QUrlQuery::QUrlQuery; + Query() = default; + Query(const std::initializer_list<QPair<QString, QString>>& l) + { + setQueryItems(l); + } }; - class BaseJob : public QObject + using Data = RequestData; + + /** + * This structure stores the status of a server call job. The status + * consists of a code, that is described (but not delimited) by the + * respective enum, and a freeform message. + * + * To extend the list of error codes, define an (anonymous) enum + * along the lines of StatusCode, with additional values + * starting at UserDefinedError + */ + class Status { - Q_OBJECT - Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT) - Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries) - public: - /* Just in case, the values are compatible with KJob - * (which BaseJob used to inherit from). */ - enum StatusCode { - NoError = 0 // To be compatible with Qt conventions - , - Success = 0, - Pending = 1, - WarningLevel = 20, - UnexpectedResponseTypeWarning = 21, - Abandoned = 50 //< A very brief period between abandoning and object - //deletion - , - ErrorLevel = 100 //< Errors have codes starting from this - , - NetworkError = 100, - JsonParseError // TODO: Merge into IncorrectResponseError - , - TimeoutError, - ContentAccessError, - NotFoundError, - IncorrectRequestError, - IncorrectResponseError, - TooManyRequestsError, - RequestNotImplementedError, - UnsupportedRoomVersionError, - NetworkAuthRequiredError, - UserConsentRequiredError, - UserDefinedError = 200 - }; - - /** - * A simple wrapper around QUrlQuery that allows its creation from - * a list of string pairs - */ - class Query : public QUrlQuery + public: + Status(StatusCode c) + : code(c) + {} + Status(int c, QString m) + : code(c) + , message(std::move(m)) + {} + + bool good() const { return code < ErrorLevel; } + friend QDebug operator<<(QDebug dbg, const Status& s) { - public: - using QUrlQuery::QUrlQuery; - Query() = default; - Query(const std::initializer_list<QPair<QString, QString>>& l) - { - setQueryItems(l); - } - }; - - using Data = RequestData; - - /** - * This structure stores the status of a server call job. The status - * consists of a code, that is described (but not delimited) by the - * respective enum, and a freeform message. - * - * To extend the list of error codes, define an (anonymous) enum - * along the lines of StatusCode, with additional values - * starting at UserDefinedError - */ - class Status + QDebugStateSaver _s(dbg); + return dbg.noquote().nospace() << s.code << ": " << s.message; + } + + bool operator==(const Status& other) const { - public: - Status(StatusCode c) : code(c) {} - Status(int c, QString m) : code(c), message(std::move(m)) {} - - bool good() const { return code < ErrorLevel; } - friend QDebug operator<<(QDebug dbg, const Status& s) - { - QDebugStateSaver _s(dbg); - return dbg.noquote().nospace() << s.code << ": " << s.message; - } - - bool operator==(const Status& other) const - { - return code == other.code && message == other.message; - } - bool operator!=(const Status& other) const - { - return !operator==(other); - } - - int code; - QString message; - }; - - using duration_t = int; // milliseconds - - public: - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - bool needsToken = true); - BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, - const Query& query, Data&& data = {}, bool needsToken = true); - - QUrl requestUrl() const; - bool isBackground() const; - - /** Current status of the job */ - Status status() const; - /** Short human-friendly message on the job status */ - QString statusCaption() const; - /** Get raw response body as received from the server - * \param bytesAtMost return this number of leftmost bytes, or -1 - * to return the entire response - */ - QByteArray rawData(int bytesAtMost = -1) const; - /** Get UI-friendly sample of raw data - * - * This is almost the same as rawData but appends the "truncated" - * suffix if not all data fit in bytesAtMost. This call is - * recommended to present a sample of raw data as "details" next to - * error messages. Note that the default \p bytesAtMost value is - * also tailored to UI cases. - */ - QString rawDataSample(int bytesAtMost = 65535) const; - - /** Error (more generally, status) code - * Equivalent to status().code - * \sa status - */ - int error() const; - /** Error-specific message, as returned by the server */ - virtual QString errorString() const; - /** A URL to help/clarify the error, if provided by the server */ - QUrl errorUrl() const; - - int maxRetries() const; - void setMaxRetries(int newMaxRetries); - - Q_INVOKABLE duration_t getCurrentTimeout() const; - Q_INVOKABLE duration_t getNextRetryInterval() const; - Q_INVOKABLE duration_t millisToRetry() const; - - friend QDebug operator<<(QDebug dbg, const BaseJob* j) + return code == other.code && message == other.message; + } + bool operator!=(const Status& other) const { - return dbg << j->objectName(); + return !operator==(other); } - public slots: - void start(const ConnectionData* connData, bool inBackground = false); - - /** - * Abandons the result of this job, arrived or unarrived. - * - * This aborts waiting for a reply from the server (if there was - * any pending) and deletes the job object. No result signals - * (result, success, failure) are emitted. - */ - void abandon(); - - signals: - /** The job is about to send a network request */ - void aboutToStart(); - - /** The job has sent a network request */ - void started(); - - /** The job has changed its status */ - void statusChanged(Status newStatus); - - /** - * The previous network request has failed; the next attempt will - * be done in the specified time - * @param nextAttempt the 1-based number of attempt (will always be more - * than 1) - * @param inMilliseconds the interval after which the next attempt will - * be taken - */ - void retryScheduled(int nextAttempt, int inMilliseconds); - - /** - * Emitted when the job is finished, in any case. It is used to notify - * observers that the job is terminated and that progress can be hidden. - * - * This should not be emitted directly by subclasses; - * use finishJob() instead. - * - * In general, to be notified of a job's completion, client code - * should connect to result(), success(), or failure() - * rather than finished(). However if you need to track the job's - * lifecycle you should connect to this instead of result(); - * in particular, only this signal will be emitted on abandoning. - * - * @param job the job that emitted this signal - * - * @see result, success, failure - */ - void finished(BaseJob* job); - - /** - * Emitted when the job is finished (except when abandoned). - * - * Use error() to know if the job was finished with error. - * - * @param job the job that emitted this signal - * - * @see success, failure - */ - void result(BaseJob* job); - - /** - * Emitted together with result() in case there's no error. - * - * @see result, failure - */ - void success(BaseJob*); - - /** - * Emitted together with result() if there's an error. - * Similar to result(), this won't be emitted in case of abandon(). - * - * @see result, success - */ - void failure(BaseJob*); - - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void uploadProgress(qint64 bytesSent, qint64 bytesTotal); - - protected: - using headers_t = QHash<QByteArray, QByteArray>; - - const QString& apiEndpoint() const; - void setApiEndpoint(const QString& apiEndpoint); - const headers_t& requestHeaders() const; - void setRequestHeader(const headers_t::key_type& headerName, - const headers_t::mapped_type& headerValue); - void setRequestHeaders(const headers_t& headers); - const QUrlQuery& query() const; - void setRequestQuery(const QUrlQuery& query); - const Data& requestData() const; - void setRequestData(Data&& data); - const QByteArrayList& expectedContentTypes() const; - void addExpectedContentType(const QByteArray& contentType); - void setExpectedContentTypes(const QByteArrayList& contentTypes); - - /** Construct a URL out of baseUrl, path and query - * The function automatically adds '/' between baseUrl's path and - * \p path if necessary. The query component of \p baseUrl - * is ignored. - */ - static QUrl makeRequestUrl(QUrl baseUrl, const QString& path, - const QUrlQuery& query = {}); - - virtual void beforeStart(const ConnectionData* connData); - virtual void afterStart(const ConnectionData* connData, - QNetworkReply* reply); - virtual void beforeAbandon(QNetworkReply*); - - /** - * Used by gotReply() to check the received reply for general - * issues such as network errors or access denial. - * Returning anything except NoError/Success prevents - * further parseReply()/parseJson() invocation. - * - * @param reply the reply received from the server - * @return the result of checking the reply - * - * @see gotReply - */ - virtual Status doCheckReply(QNetworkReply* reply) const; - - /** - * Processes the reply. By default, parses the reply into - * a QJsonDocument and calls parseJson() if it's a valid JSON. - * - * @param reply raw contents of a HTTP reply from the server (without - * headers) - * - * @see gotReply, parseJson - */ - virtual Status parseReply(QNetworkReply* reply); - - /** - * Processes the JSON document received from the Matrix server. - * By default returns succesful status without analysing the JSON. - * - * @param json valid JSON document received from the server - * - * @see parseReply - */ - virtual Status parseJson(const QJsonDocument&); - - void setStatus(Status s); - void setStatus(int code, QString message); - - // Q_DECLARE_LOGGING_CATEGORY return different function types - // in different versions - using LoggingCategory = decltype(JOBS)*; - void setLoggingCategory(LoggingCategory lcf); - - // Job objects should only be deleted via QObject::deleteLater - ~BaseJob() override; - - protected slots: - void timeout(); - - private slots: - void sendRequest(bool inBackground); - void checkReply(); - void gotReply(); - - private: - void stop(); - void finishJob(); - - class Private; - QScopedPointer<Private> d; + int code; + QString message; }; - inline bool isJobRunning(BaseJob* job) + using duration_t = int; // milliseconds + +public: + BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + bool needsToken = true); + BaseJob(HttpVerb verb, const QString& name, const QString& endpoint, + const Query& query, Data&& data = {}, bool needsToken = true); + + QUrl requestUrl() const; + bool isBackground() const; + + /** Current status of the job */ + Status status() const; + /** Short human-friendly message on the job status */ + QString statusCaption() const; + /** Get raw response body as received from the server + * \param bytesAtMost return this number of leftmost bytes, or -1 + * to return the entire response + */ + QByteArray rawData(int bytesAtMost = -1) const; + /** Get UI-friendly sample of raw data + * + * This is almost the same as rawData but appends the "truncated" + * suffix if not all data fit in bytesAtMost. This call is + * recommended to present a sample of raw data as "details" next to + * error messages. Note that the default \p bytesAtMost value is + * also tailored to UI cases. + */ + QString rawDataSample(int bytesAtMost = 65535) const; + + /** Error (more generally, status) code + * Equivalent to status().code + * \sa status + */ + int error() const; + /** Error-specific message, as returned by the server */ + virtual QString errorString() const; + /** A URL to help/clarify the error, if provided by the server */ + QUrl errorUrl() const; + + int maxRetries() const; + void setMaxRetries(int newMaxRetries); + + Q_INVOKABLE duration_t getCurrentTimeout() const; + Q_INVOKABLE duration_t getNextRetryInterval() const; + Q_INVOKABLE duration_t millisToRetry() const; + + friend QDebug operator<<(QDebug dbg, const BaseJob* j) { - return job && job->error() == BaseJob::Pending; + return dbg << j->objectName(); } + +public slots: + void start(const ConnectionData* connData, bool inBackground = false); + + /** + * Abandons the result of this job, arrived or unarrived. + * + * This aborts waiting for a reply from the server (if there was + * any pending) and deletes the job object. No result signals + * (result, success, failure) are emitted. + */ + void abandon(); + +signals: + /** The job is about to send a network request */ + void aboutToStart(); + + /** The job has sent a network request */ + void started(); + + /** The job has changed its status */ + void statusChanged(Status newStatus); + + /** + * The previous network request has failed; the next attempt will + * be done in the specified time + * @param nextAttempt the 1-based number of attempt (will always be more + * than 1) + * @param inMilliseconds the interval after which the next attempt will be + * taken + */ + void retryScheduled(int nextAttempt, int inMilliseconds); + + /** + * Emitted when the job is finished, in any case. It is used to notify + * observers that the job is terminated and that progress can be hidden. + * + * This should not be emitted directly by subclasses; + * use finishJob() instead. + * + * In general, to be notified of a job's completion, client code + * should connect to result(), success(), or failure() + * rather than finished(). However if you need to track the job's + * lifecycle you should connect to this instead of result(); + * in particular, only this signal will be emitted on abandoning. + * + * @param job the job that emitted this signal + * + * @see result, success, failure + */ + void finished(BaseJob* job); + + /** + * Emitted when the job is finished (except when abandoned). + * + * Use error() to know if the job was finished with error. + * + * @param job the job that emitted this signal + * + * @see success, failure + */ + void result(BaseJob* job); + + /** + * Emitted together with result() in case there's no error. + * + * @see result, failure + */ + void success(BaseJob*); + + /** + * Emitted together with result() if there's an error. + * Similar to result(), this won't be emitted in case of abandon(). + * + * @see result, success + */ + void failure(BaseJob*); + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + +protected: + using headers_t = QHash<QByteArray, QByteArray>; + + const QString& apiEndpoint() const; + void setApiEndpoint(const QString& apiEndpoint); + const headers_t& requestHeaders() const; + void setRequestHeader(const headers_t::key_type& headerName, + const headers_t::mapped_type& headerValue); + void setRequestHeaders(const headers_t& headers); + const QUrlQuery& query() const; + void setRequestQuery(const QUrlQuery& query); + const Data& requestData() const; + void setRequestData(Data&& data); + const QByteArrayList& expectedContentTypes() const; + void addExpectedContentType(const QByteArray& contentType); + void setExpectedContentTypes(const QByteArrayList& contentTypes); + + /** Construct a URL out of baseUrl, path and query + * The function automatically adds '/' between baseUrl's path and + * \p path if necessary. The query component of \p baseUrl + * is ignored. + */ + static QUrl makeRequestUrl(QUrl baseUrl, const QString& path, + const QUrlQuery& query = {}); + + virtual void beforeStart(const ConnectionData* connData); + virtual void afterStart(const ConnectionData* connData, + QNetworkReply* reply); + virtual void beforeAbandon(QNetworkReply*); + + /** + * Used by gotReply() to check the received reply for general + * issues such as network errors or access denial. + * Returning anything except NoError/Success prevents + * further parseReply()/parseJson() invocation. + * + * @param reply the reply received from the server + * @return the result of checking the reply + * + * @see gotReply + */ + virtual Status doCheckReply(QNetworkReply* reply) const; + + /** + * Processes the reply. By default, parses the reply into + * a QJsonDocument and calls parseJson() if it's a valid JSON. + * + * @param reply raw contents of a HTTP reply from the server (without + * headers) + * + * @see gotReply, parseJson + */ + virtual Status parseReply(QNetworkReply* reply); + + /** + * Processes the JSON document received from the Matrix server. + * By default returns succesful status without analysing the JSON. + * + * @param json valid JSON document received from the server + * + * @see parseReply + */ + virtual Status parseJson(const QJsonDocument&); + + void setStatus(Status s); + void setStatus(int code, QString message); + + // Q_DECLARE_LOGGING_CATEGORY return different function types + // in different versions + using LoggingCategory = decltype(JOBS)*; + void setLoggingCategory(LoggingCategory lcf); + + // Job objects should only be deleted via QObject::deleteLater + ~BaseJob() override; + +protected slots: + void timeout(); + +private slots: + void sendRequest(bool inBackground); + void checkReply(); + void gotReply(); + +private: + void stop(); + void finishJob(); + + class Private; + QScopedPointer<Private> d; +}; + +inline bool isJobRunning(BaseJob* job) +{ + return job && job->error() == BaseJob::Pending; +} } // namespace QMatrixClient diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp index 12aacb8b..3dff5a68 100644 --- a/lib/jobs/downloadfilejob.cpp +++ b/lib/jobs/downloadfilejob.cpp @@ -8,14 +8,15 @@ using namespace QMatrixClient; class DownloadFileJob::Private { - public: - Private() : tempFile(new QTemporaryFile()) {} +public: + Private() + : tempFile(new QTemporaryFile()) + {} explicit Private(const QString& localFilename) - : targetFile(new QFile(localFilename)), - tempFile(new QFile(targetFile->fileName() + ".qmcdownload")) - { - } + : targetFile(new QFile(localFilename)) + , tempFile(new QFile(targetFile->fileName() + ".qmcdownload")) + {} QScopedPointer<QFile> targetFile; QScopedPointer<QFile> tempFile; @@ -23,16 +24,17 @@ class DownloadFileJob::Private QUrl DownloadFileJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri) { - return makeRequestUrl(baseUrl, mxcUri.authority(), mxcUri.path().mid(1)); + return makeRequestUrl(std::move(baseUrl), mxcUri.authority(), + mxcUri.path().mid(1)); } DownloadFileJob::DownloadFileJob(const QString& serverName, const QString& mediaId, const QString& localFilename) - : GetContentJob(serverName, mediaId), - d(localFilename.isEmpty() ? new Private : new Private(localFilename)) + : GetContentJob(serverName, mediaId) + , d(localFilename.isEmpty() ? new Private : new Private(localFilename)) { - setObjectName("DownloadFileJob"); + setObjectName(QStringLiteral("DownloadFileJob")); } QString DownloadFileJob::targetFileName() const @@ -49,8 +51,7 @@ void DownloadFileJob::beforeStart(const ConnectionData*) setStatus(FileError, "Could not open the target file for writing"); return; } - if (!d->tempFile->isReadable() - && !d->tempFile->open(QIODevice::WriteOnly)) { + if (!d->tempFile->isReadable() && !d->tempFile->open(QIODevice::WriteOnly)) { qCWarning(JOBS) << "Couldn't open the temporary file" << d->tempFile->fileName() << "for writing"; setStatus(FileError, "Could not open the temporary download file"); diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h index fd34ba5a..58858448 100644 --- a/lib/jobs/downloadfilejob.h +++ b/lib/jobs/downloadfilejob.h @@ -2,27 +2,31 @@ #include "csapi/content-repo.h" -namespace QMatrixClient { - class DownloadFileJob : public GetContentJob +namespace QMatrixClient +{ +class DownloadFileJob : public GetContentJob +{ +public: + enum { - public: - enum { FileError = BaseJob::UserDefinedError + 1 }; + FileError = BaseJob::UserDefinedError + 1 + }; - using GetContentJob::makeRequestUrl; - static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri); + using GetContentJob::makeRequestUrl; + static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri); - DownloadFileJob(const QString& serverName, const QString& mediaId, - const QString& localFilename = {}); + DownloadFileJob(const QString& serverName, const QString& mediaId, + const QString& localFilename = {}); - QString targetFileName() const; + QString targetFileName() const; - private: - class Private; - QScopedPointer<Private> d; +private: + class Private; + QScopedPointer<Private> d; - void beforeStart(const ConnectionData*) override; - void afterStart(const ConnectionData*, QNetworkReply* reply) override; - void beforeAbandon(QNetworkReply*) override; - Status parseReply(QNetworkReply*) override; - }; -} + void beforeStart(const ConnectionData*) override; + void afterStart(const ConnectionData*, QNetworkReply* reply) override; + void beforeAbandon(QNetworkReply*) override; + Status parseReply(QNetworkReply*) override; +}; +} // namespace QMatrixClient diff --git a/lib/jobs/mediathumbnailjob.cpp b/lib/jobs/mediathumbnailjob.cpp index d3370f1f..db2bbc13 100644 --- a/lib/jobs/mediathumbnailjob.cpp +++ b/lib/jobs/mediathumbnailjob.cpp @@ -29,19 +29,16 @@ QUrl MediaThumbnailJob::makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri, } MediaThumbnailJob::MediaThumbnailJob(const QString& serverName, - const QString& mediaId, - QSize requestedSize) + const QString& mediaId, QSize requestedSize) : GetContentThumbnailJob(serverName, mediaId, requestedSize.width(), requestedSize.height()) -{ -} +{} MediaThumbnailJob::MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize) : MediaThumbnailJob(mxcUri.authority(), mxcUri.path().mid(1), // sans leading '/' requestedSize) -{ -} +{} QImage MediaThumbnailJob::thumbnail() const { return _thumbnail; } @@ -60,5 +57,6 @@ BaseJob::Status MediaThumbnailJob::parseReply(QNetworkReply* reply) if (_thumbnail.loadFromData(data()->readAll())) return Success; - return { IncorrectResponseError, "Could not read image data" }; + return { IncorrectResponseError, + QStringLiteral("Could not read image data") }; } diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h index 1dcf8ccb..eeabe7a9 100644 --- a/lib/jobs/mediathumbnailjob.h +++ b/lib/jobs/mediathumbnailjob.h @@ -22,25 +22,26 @@ #include <QtGui/QPixmap> -namespace QMatrixClient { - class MediaThumbnailJob : public GetContentThumbnailJob - { - public: - using GetContentThumbnailJob::makeRequestUrl; - static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri, - QSize requestedSize); - - MediaThumbnailJob(const QString& serverName, const QString& mediaId, - QSize requestedSize); - MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize); - - QImage thumbnail() const; - QImage scaledThumbnail(QSize toSize) const; - - protected: - Status parseReply(QNetworkReply* reply) override; - - private: - QImage _thumbnail; - }; +namespace QMatrixClient +{ +class MediaThumbnailJob : public GetContentThumbnailJob +{ +public: + using GetContentThumbnailJob::makeRequestUrl; + static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri, + QSize requestedSize); + + MediaThumbnailJob(const QString& serverName, const QString& mediaId, + QSize requestedSize); + MediaThumbnailJob(const QUrl& mxcUri, QSize requestedSize); + + QImage thumbnail() const; + QImage scaledThumbnail(QSize toSize) const; + +protected: + Status parseReply(QNetworkReply* reply) override; + +private: + QImage _thumbnail; +}; } // namespace QMatrixClient diff --git a/lib/jobs/postreadmarkersjob.h b/lib/jobs/postreadmarkersjob.h index 3c5cac89..d53ae66c 100644 --- a/lib/jobs/postreadmarkersjob.h +++ b/lib/jobs/postreadmarkersjob.h @@ -26,14 +26,14 @@ using namespace QMatrixClient; class PostReadMarkersJob : public BaseJob { - public: +public: explicit PostReadMarkersJob(const QString& roomId, const QString& readUpToEventId) - : BaseJob(HttpVerb::Post, "PostReadMarkersJob", - QStringLiteral("_matrix/client/r0/rooms/%1/read_markers") - .arg(roomId)) + : BaseJob( + HttpVerb::Post, "PostReadMarkersJob", + QStringLiteral("_matrix/client/r0/rooms/%1/read_markers").arg(roomId)) { - setRequestData(QJsonObject { - { QStringLiteral("m.fully_read"), readUpToEventId } }); + setRequestData( + QJsonObject { { QStringLiteral("m.fully_read"), readUpToEventId } }); } }; diff --git a/lib/jobs/requestdata.cpp b/lib/jobs/requestdata.cpp index 477f49e7..8248d6b1 100644 --- a/lib/jobs/requestdata.cpp +++ b/lib/jobs/requestdata.cpp @@ -17,15 +17,22 @@ auto fromData(const QByteArray& data) return source; } -template <typename JsonDataT> inline auto fromJson(const JsonDataT& jdata) +template <typename JsonDataT> +inline auto fromJson(const JsonDataT& jdata) { return fromData(QJsonDocument(jdata).toJson(QJsonDocument::Compact)); } -RequestData::RequestData(const QByteArray& a) : _source(fromData(a)) {} +RequestData::RequestData(const QByteArray& a) + : _source(fromData(a)) +{} -RequestData::RequestData(const QJsonObject& jo) : _source(fromJson(jo)) {} +RequestData::RequestData(const QJsonObject& jo) + : _source(fromJson(jo)) +{} -RequestData::RequestData(const QJsonArray& ja) : _source(fromJson(ja)) {} +RequestData::RequestData(const QJsonArray& ja) + : _source(fromJson(ja)) +{} RequestData::~RequestData() = default; diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h index 207ff731..974a9ddf 100644 --- a/lib/jobs/requestdata.h +++ b/lib/jobs/requestdata.h @@ -26,33 +26,33 @@ class QJsonArray; class QJsonDocument; class QIODevice; -namespace QMatrixClient { - /** - * A simple wrapper that represents the request body. - * Provides a unified interface to dump an unstructured byte stream - * as well as JSON (and possibly other structures in the future) to - * a QByteArray consumed by QNetworkAccessManager request methods. - */ - class RequestData - { - public: - RequestData() = default; - RequestData(const QByteArray& a); - RequestData(const QJsonObject& jo); - RequestData(const QJsonArray& ja); - RequestData(QIODevice* source) - : _source(std::unique_ptr<QIODevice>(source)) - { - } - RequestData(const RequestData&) = delete; - RequestData& operator=(const RequestData&) = delete; - RequestData(RequestData&&) = default; - RequestData& operator=(RequestData&&) = default; - ~RequestData(); +namespace QMatrixClient +{ +/** + * A simple wrapper that represents the request body. + * Provides a unified interface to dump an unstructured byte stream + * as well as JSON (and possibly other structures in the future) to + * a QByteArray consumed by QNetworkAccessManager request methods. + */ +class RequestData +{ +public: + RequestData() = default; + RequestData(const QByteArray& a); + RequestData(const QJsonObject& jo); + RequestData(const QJsonArray& ja); + RequestData(QIODevice* source) + : _source(std::unique_ptr<QIODevice>(source)) + {} + RequestData(const RequestData&) = delete; + RequestData& operator=(const RequestData&) = delete; + RequestData(RequestData&&) = default; + RequestData& operator=(RequestData&&) = default; + ~RequestData(); - QIODevice* source() const { return _source.get(); } + QIODevice* source() const { return _source.get(); } - private: - std::unique_ptr<QIODevice> _source; - }; +private: + std::unique_ptr<QIODevice> _source; +}; } // namespace QMatrixClient diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index db11005a..f660e1b6 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -47,8 +47,7 @@ SyncJob::SyncJob(const QString& since, const Filter& filter, int timeout, : SyncJob(since, QJsonDocument(toJson(filter)).toJson(QJsonDocument::Compact), timeout, presence) -{ -} +{} BaseJob::Status SyncJob::parseJson(const QJsonDocument& data) { diff --git a/lib/jobs/syncjob.h b/lib/jobs/syncjob.h index 2afaf0f7..e2cec8f7 100644 --- a/lib/jobs/syncjob.h +++ b/lib/jobs/syncjob.h @@ -18,26 +18,26 @@ #pragma once -#include "basejob.h" - #include "../csapi/definitions/sync_filter.h" #include "../syncdata.h" +#include "basejob.h" -namespace QMatrixClient { - class SyncJob : public BaseJob - { - public: - explicit SyncJob(const QString& since = {}, const QString& filter = {}, - int timeout = -1, const QString& presence = {}); - explicit SyncJob(const QString& since, const Filter& filter, - int timeout = -1, const QString& presence = {}); +namespace QMatrixClient +{ +class SyncJob : public BaseJob +{ +public: + explicit SyncJob(const QString& since = {}, const QString& filter = {}, + int timeout = -1, const QString& presence = {}); + explicit SyncJob(const QString& since, const Filter& filter, + int timeout = -1, const QString& presence = {}); - SyncData&& takeData() { return std::move(d); } + SyncData&& takeData() { return std::move(d); } - protected: - Status parseJson(const QJsonDocument& data) override; +protected: + Status parseJson(const QJsonDocument& data) override; - private: - SyncData d; - }; +private: + SyncData d; +}; } // namespace QMatrixClient diff --git a/lib/joinstate.h b/lib/joinstate.h index ddaba9a5..f7c0cb2b 100644 --- a/lib/joinstate.h +++ b/lib/joinstate.h @@ -22,27 +22,28 @@ #include <array> -namespace QMatrixClient { - enum class JoinState : unsigned int { - Join = 0x1, - Invite = 0x2, - Leave = 0x4, - }; +namespace QMatrixClient +{ +enum class JoinState : unsigned int +{ + Join = 0x1, + Invite = 0x2, + Leave = 0x4, +}; - Q_DECLARE_FLAGS(JoinStates, JoinState) +Q_DECLARE_FLAGS(JoinStates, JoinState) - // We cannot use REGISTER_ENUM outside of a Q_OBJECT and besides, we want - // to use strings that match respective JSON keys. - static const std::array<const char*, 3> JoinStateStrings { - { "join", "invite", "leave" } - }; +// We cannot use REGISTER_ENUM outside of a Q_OBJECT and besides, we want +// to use strings that match respective JSON keys. +static const std::array<const char*, 3> JoinStateStrings { { "join", "invite", + "leave" } }; - inline const char* toCString(JoinState js) - { - size_t state = size_t(js), index = 0; - while (state >>= 1) - ++index; - return JoinStateStrings[index]; - } +inline const char* toCString(JoinState js) +{ + size_t state = size_t(js), index = 0; + while (state >>= 1) + ++index; + return JoinStateStrings[index]; +} } // namespace QMatrixClient Q_DECLARE_OPERATORS_FOR_FLAGS(QMatrixClient::JoinStates) diff --git a/lib/logging.cpp b/lib/logging.cpp index 5d70ba36..73cc59a1 100644 --- a/lib/logging.cpp +++ b/lib/logging.cpp @@ -19,9 +19,10 @@ #include "logging.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) -#define LOGGING_CATEGORY(Name, Id) Q_LOGGING_CATEGORY((Name), (Id), QtInfoMsg) +# define LOGGING_CATEGORY(Name, Id) \ + Q_LOGGING_CATEGORY((Name), (Id), QtInfoMsg) #else -#define LOGGING_CATEGORY(Name, Id) Q_LOGGING_CATEGORY((Name), (Id)) +# define LOGGING_CATEGORY(Name, Id) Q_LOGGING_CATEGORY((Name), (Id)) #endif // Use LOGGING_CATEGORY instead of Q_LOGGING_CATEGORY in the rest of the code diff --git a/lib/logging.h b/lib/logging.h index 06603322..a50c1795 100644 --- a/lib/logging.h +++ b/lib/logging.h @@ -28,54 +28,55 @@ Q_DECLARE_LOGGING_CATEGORY(EPHEMERAL) Q_DECLARE_LOGGING_CATEGORY(JOBS) Q_DECLARE_LOGGING_CATEGORY(SYNCJOB) -namespace QMatrixClient { - // QDebug manipulators +namespace QMatrixClient +{ +// QDebug manipulators - using QDebugManip = QDebug (*)(QDebug); +using QDebugManip = QDebug (*)(QDebug); - /** - * @brief QDebug manipulator to setup the stream for JSON output - * - * Originally made to encapsulate the change in QDebug behavior in Qt 5.4 - * and the respective addition of QDebug::noquote(). - * Together with the operator<<() helper, the proposed usage is - * (similar to std:: I/O manipulators): - * - * @example qCDebug() << formatJson << json_object; // (QJsonObject, etc.) - */ - inline QDebug formatJson(QDebug debug_object) - { +/** + * @brief QDebug manipulator to setup the stream for JSON output + * + * Originally made to encapsulate the change in QDebug behavior in Qt 5.4 + * and the respective addition of QDebug::noquote(). + * Together with the operator<<() helper, the proposed usage is + * (similar to std:: I/O manipulators): + * + * @example qCDebug() << formatJson << json_object; // (QJsonObject, etc.) + */ +inline QDebug formatJson(QDebug debug_object) +{ #if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) - return debug_object; + return debug_object; #else - return debug_object.noquote(); + return debug_object.noquote(); #endif - } +} - /** - * @brief A helper operator to facilitate usage of formatJson (and possibly - * other manipulators) - * - * @param debug_object to output the json to - * @param qdm a QDebug manipulator - * @return a copy of debug_object that has its mode altered by qdm - */ - inline QDebug operator<<(QDebug debug_object, QDebugManip qdm) - { - return qdm(debug_object); - } +/** + * @brief A helper operator to facilitate usage of formatJson (and possibly + * other manipulators) + * + * @param debug_object to output the json to + * @param qdm a QDebug manipulator + * @return a copy of debug_object that has its mode altered by qdm + */ +inline QDebug operator<<(QDebug debug_object, QDebugManip qdm) +{ + return qdm(debug_object); +} - inline qint64 profilerMinNsecs() - { - return +inline qint64 profilerMinNsecs() +{ + return #ifdef PROFILER_LOG_USECS - PROFILER_LOG_USECS + PROFILER_LOG_USECS #else - 200 + 200 #endif - * 1000; - } + * 1000; } +} // namespace QMatrixClient inline QDebug operator<<(QDebug debug_object, const QElapsedTimer& et) { diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp index 174ac16e..9ac589b8 100644 --- a/lib/networkaccessmanager.cpp +++ b/lib/networkaccessmanager.cpp @@ -25,14 +25,14 @@ using namespace QMatrixClient; class NetworkAccessManager::Private { - public: +public: QList<QSslError> ignoredSslErrors; }; NetworkAccessManager::NetworkAccessManager(QObject* parent) - : d(std::make_unique<Private>()) -{ -} + : QNetworkAccessManager(parent) + , d(std::make_unique<Private>()) +{} QList<QSslError> NetworkAccessManager::ignoredSslErrors() const { @@ -69,10 +69,9 @@ NetworkAccessManager* NetworkAccessManager::instance() NetworkAccessManager::~NetworkAccessManager() = default; QNetworkReply* NetworkAccessManager::createRequest( - Operation op, const QNetworkRequest& request, QIODevice* outgoingData) + Operation op, const QNetworkRequest& request, QIODevice* outgoingData) { - auto reply = - QNetworkAccessManager::createRequest(op, request, outgoingData); + auto reply = QNetworkAccessManager::createRequest(op, request, outgoingData); reply->ignoreSslErrors(d->ignoredSslErrors); return reply; } diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h index ebaaa5b2..bf8f0cbc 100644 --- a/lib/networkaccessmanager.h +++ b/lib/networkaccessmanager.h @@ -22,27 +22,27 @@ #include <memory> -namespace QMatrixClient { - class NetworkAccessManager : public QNetworkAccessManager - { - Q_OBJECT - public: - NetworkAccessManager(QObject* parent = nullptr); - ~NetworkAccessManager() override; - - QList<QSslError> ignoredSslErrors() const; - void addIgnoredSslError(const QSslError& error); - void clearIgnoredSslErrors(); - - /** Get a pointer to the singleton */ - static NetworkAccessManager* instance(); - - private: - QNetworkReply* - createRequest(Operation op, const QNetworkRequest& request, - QIODevice* outgoingData = Q_NULLPTR) override; - - class Private; - std::unique_ptr<Private> d; - }; +namespace QMatrixClient +{ +class NetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT +public: + NetworkAccessManager(QObject* parent = nullptr); + ~NetworkAccessManager() override; + + QList<QSslError> ignoredSslErrors() const; + void addIgnoredSslError(const QSslError& error); + void clearIgnoredSslErrors(); + + /** Get a pointer to the singleton */ + static NetworkAccessManager* instance(); + +private: + QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, + QIODevice* outgoingData = Q_NULLPTR) override; + + class Private; + std::unique_ptr<Private> d; +}; } // namespace QMatrixClient diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp index de333884..f5655975 100644 --- a/lib/networksettings.cpp +++ b/lib/networksettings.cpp @@ -23,12 +23,12 @@ using namespace QMatrixClient; void NetworkSettings::setupApplicationProxy() const { QNetworkProxy::setApplicationProxy( - { proxyType(), proxyHostName(), proxyPort() }); + { proxyType(), proxyHostName(), proxyPort() }); } QMC_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType, "proxy_type", QNetworkProxy::DefaultProxy, setProxyType) QMC_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname", - "", setProxyHostName) + {}, setProxyHostName) QMC_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1, setProxyPort) diff --git a/lib/networksettings.h b/lib/networksettings.h index ca9c7dfc..0c21a9fe 100644 --- a/lib/networksettings.h +++ b/lib/networksettings.h @@ -24,22 +24,22 @@ Q_DECLARE_METATYPE(QNetworkProxy::ProxyType) -namespace QMatrixClient { - class NetworkSettings : public SettingsGroup - { - Q_OBJECT - QMC_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) - QMC_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) - QMC_DECLARE_SETTING(quint16, proxyPort, setProxyPort) - Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) - public: - template <typename... ArgTs> - explicit NetworkSettings(ArgTs... qsettingsArgs) - : SettingsGroup(QStringLiteral("Network"), qsettingsArgs...) - { - } - ~NetworkSettings() override = default; +namespace QMatrixClient +{ +class NetworkSettings : public SettingsGroup +{ + Q_OBJECT + QMC_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType) + QMC_DECLARE_SETTING(QString, proxyHostName, setProxyHostName) + QMC_DECLARE_SETTING(quint16, proxyPort, setProxyPort) + Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName) +public: + template <typename... ArgTs> + explicit NetworkSettings(ArgTs... qsettingsArgs) + : SettingsGroup(QStringLiteral("Network"), qsettingsArgs...) + {} + ~NetworkSettings() override = default; - Q_INVOKABLE void setupApplicationProxy() const; - }; -} + Q_INVOKABLE void setupApplicationProxy() const; +}; +} // namespace QMatrixClient diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 0f36424f..1b3229d4 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -22,91 +22,88 @@ #include <QtCore/QPointer> -namespace QMatrixClient { - namespace _impl { - template <typename SenderT, typename SignalT, typename ContextT, - typename... ArgTs> - inline QMetaObject::Connection - connectUntil(SenderT* sender, SignalT signal, ContextT* context, - std::function<bool(ArgTs...)> slot, - Qt::ConnectionType connType) - { - // See https://bugreports.qt.io/browse/QTBUG-60339 +namespace QMatrixClient +{ +namespace _impl +{ + template <typename SenderT, typename SignalT, typename ContextT, typename... ArgTs> + inline QMetaObject::Connection + connectUntil(SenderT* sender, SignalT signal, ContextT* context, + std::function<bool(ArgTs...)> slot, Qt::ConnectionType connType) + { + // See https://bugreports.qt.io/browse/QTBUG-60339 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - auto pc = std::make_shared<QMetaObject::Connection>(); + auto pc = std::make_shared<QMetaObject::Connection>(); #else - auto pc = std::make_unique<QMetaObject::Connection>(); + auto pc = std::make_unique<QMetaObject::Connection>(); #endif - auto& c = *pc; // Resolve a reference before pc is moved to lambda - c = QObject::connect( - sender, signal, context, - [pc = std::move(pc), slot](ArgTs... args) { - Q_ASSERT( - *pc); // If it's been triggered, it should exist - if (slot(std::forward<ArgTs>(args)...)) - QObject::disconnect(*pc); - }, - connType); - return c; - } + auto& c = *pc; // Resolve a reference before pc is moved to lambda + c = QObject::connect( + sender, signal, context, + [pc = std::move(pc), slot](ArgTs... args) { + Q_ASSERT(*pc); // If it's been triggered, it should exist + if (slot(std::forward<ArgTs>(args)...)) + QObject::disconnect(*pc); + }, + connType); + return c; } +} // namespace _impl - template <typename SenderT, typename SignalT, typename ContextT, - typename FunctorT> - inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, - const FunctorT& slot, - Qt::ConnectionType connType = Qt::AutoConnection) - { - return _impl::connectUntil( - sender, signal, context, - typename function_traits<FunctorT>::function_type(slot), - connType); - } +template <typename SenderT, typename SignalT, typename ContextT, typename FunctorT> +inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, + const FunctorT& slot, + Qt::ConnectionType connType = Qt::AutoConnection) +{ + return _impl::connectUntil( + sender, signal, context, + typename function_traits<FunctorT>::function_type(slot), connType); +} - /** Create a single-shot connection that triggers on the signal and - * then self-disconnects - * - * Only supports DirectConnection type. - */ - template <typename SenderT, typename SignalT, typename ReceiverT, - typename SlotT> - inline auto connectSingleShot(SenderT* sender, SignalT signal, - ReceiverT* receiver, SlotT slot) - { - QMetaObject::Connection connection; - connection = QObject::connect(sender, signal, receiver, slot, - Qt::DirectConnection); - Q_ASSERT(connection); - QObject::connect(sender, signal, receiver, - [connection] { QObject::disconnect(connection); }, - Qt::DirectConnection); - return connection; - } +/** Create a single-shot connection that triggers on the signal and + * then self-disconnects + * + * Only supports DirectConnection type. + */ +template <typename SenderT, typename SignalT, typename ReceiverT, typename SlotT> +inline auto connectSingleShot(SenderT* sender, SignalT signal, + ReceiverT* receiver, SlotT slot) +{ + QMetaObject::Connection connection; + connection = QObject::connect(sender, signal, receiver, slot, + Qt::DirectConnection); + Q_ASSERT(connection); + QObject::connect( + sender, signal, receiver, + [connection] { QObject::disconnect(connection); }, Qt::DirectConnection); + return connection; +} - /** A guard pointer that disconnects an interested object upon destruction - * It's almost QPointer<> except that you have to initialise it with one - * more additional parameter - a pointer to a QObject that will be - * disconnected from signals of the underlying pointer upon the guard's - * destruction. - */ - template <typename T> class ConnectionsGuard : public QPointer<T> +/** A guard pointer that disconnects an interested object upon destruction + * It's almost QPointer<> except that you have to initialise it with one + * more additional parameter - a pointer to a QObject that will be + * disconnected from signals of the underlying pointer upon the guard's + * destruction. + */ +template <typename T> +class ConnectionsGuard : public QPointer<T> +{ +public: + ConnectionsGuard(T* publisher, QObject* subscriber) + : QPointer<T>(publisher) + , subscriber(subscriber) + {} + ~ConnectionsGuard() { - public: - ConnectionsGuard(T* publisher, QObject* subscriber) - : QPointer<T>(publisher), subscriber(subscriber) - { - } - ~ConnectionsGuard() - { - if (*this) - (*this)->disconnect(subscriber); - } - ConnectionsGuard(ConnectionsGuard&&) = default; - ConnectionsGuard& operator=(ConnectionsGuard&&) = default; - Q_DISABLE_COPY(ConnectionsGuard) - using QPointer<T>::operator=; + if (*this) + (*this)->disconnect(subscriber); + } + ConnectionsGuard(ConnectionsGuard&&) = default; + ConnectionsGuard& operator=(ConnectionsGuard&&) = default; + Q_DISABLE_COPY(ConnectionsGuard) + using QPointer<T>::operator=; - private: - QObject* subscriber; - }; -} +private: + QObject* subscriber; +}; +} // namespace QMatrixClient diff --git a/lib/room.cpp b/lib/room.cpp index c7c94fe5..ec2a34ef 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -21,6 +21,9 @@ #include "avatar.h" #include "connection.h" #include "converters.h" +#include "syncdata.h" +#include "user.h" + #include "csapi/account-data.h" #include "csapi/banning.h" #include "csapi/inviting.h" @@ -33,6 +36,7 @@ #include "csapi/room_upgrades.h" #include "csapi/rooms.h" #include "csapi/tags.h" + #include "events/callanswerevent.h" #include "events/callcandidatesevent.h" #include "events/callhangupevent.h" @@ -48,8 +52,6 @@ #include "jobs/downloadfilejob.h" #include "jobs/mediathumbnailjob.h" #include "jobs/postreadmarkersjob.h" -#include "syncdata.h" -#include "user.h" #include <QtCore/QDir> #include <QtCore/QHash> @@ -70,19 +72,25 @@ using std::move; using std::llround; #endif -enum EventsPlacement : int { Older = -1, Newer = 1 }; +enum EventsPlacement : int +{ + Older = -1, + Newer = 1 +}; class Room::Private { - public: - /** Map of user names to users. User names potentially duplicate, hence a - * multi-hashmap. */ +public: + /// Map of user names to users + /** User names potentially duplicate, hence QMultiHash. */ using members_map_t = QMultiHash<QString, User*>; Private(Connection* c, QString id_, JoinState initialJoinState) - : q(nullptr), connection(c), id(move(id_)), joinState(initialJoinState) - { - } + : q(nullptr) + , connection(c) + , id(move(id_)) + , joinState(initialJoinState) + {} Room* q; @@ -106,6 +114,7 @@ class Room::Private members_map_t membersMap; QList<User*> usersTyping; QMultiHash<QString, User*> eventIdReadUsers; + QList<User*> usersInvited; QList<User*> membersLeft; int unreadMessages = 0; bool displayed = false; @@ -119,16 +128,16 @@ class Room::Private QPointer<GetRoomEventsJob> eventsHistoryJob; QPointer<GetMembersByRoomJob> allMembersJob; - struct FileTransferPrivateInfo { + struct FileTransferPrivateInfo + { FileTransferPrivateInfo() = default; FileTransferPrivateInfo(BaseJob* j, const QString& fileName, bool isUploading = false) - : status(FileTransferInfo::Started), - job(j), - localFileInfo(fileName), - isUpload(isUploading) - { - } + : status(FileTransferInfo::Started) + , job(j) + , localFileInfo(fileName) + , isUpload(isUploading) + {} FileTransferInfo::Status status = FileTransferInfo::None; QPointer<BaseJob> job = nullptr; @@ -170,7 +179,7 @@ class Room::Private // void inviteUser(User* u); // We might get it at some point in time. void insertMemberIntoMap(User* u); - void renameMember(User* u, QString oldName); + void renameMember(User* u, const QString& oldName); void removeMemberFromMap(const QString& username, User* u); // This updates the room displayname field (which is the way a room @@ -187,11 +196,11 @@ class Room::Private void getPreviousContent(int limit = 10); template <typename EventT> - const EventT* getCurrentState(QString stateKey = {}) const + const EventT* getCurrentState(const QString& stateKey = {}) const { static const EventT empty; - const auto* evt = currentState.value( - { EventT::matrixTypeId(), stateKey }, &empty); + const auto* evt = + currentState.value({ EventT::matrixTypeId(), stateKey }, &empty); Q_ASSERT(evt->type() == EventT::typeId() && evt->matrixType() == EventT::matrixTypeId()); return static_cast<const EventT*>(evt); @@ -200,7 +209,7 @@ class Room::Private bool isEventNotable(const TimelineItem& ti) const { return !ti->isRedacted() && ti->senderId() != connection->userId() - && is<RoomMessageEvent>(*ti); + && is<RoomMessageEvent>(*ti); } template <typename EventArrayT> @@ -219,8 +228,9 @@ class Room::Private baseState[{ evt.matrixType(), evt.stateKey() }] = move(eptr); } if (events.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::Private::updateStateFrom():" - << events.size() << "event(s)," << et; + qCDebug(PROFILER) + << "*** Room::Private::updateStateFrom():" << events.size() + << "event(s)," << et; } return changes; } @@ -236,8 +246,8 @@ class Room::Private * @param placement - position and direction of insertion: Older for * historical messages, Newer for new ones */ - Timeline::difference_type moveEventsToTimeline(RoomEventsRange events, - EventsPlacement placement); + Timeline::size_type moveEventsToTimeline(RoomEventsRange events, + EventsPlacement placement); /** * Remove events from the passed container that are already in the timeline @@ -246,8 +256,7 @@ class Room::Private Changes setLastReadEvent(User* u, QString eventId); void updateUnreadCount(rev_iter_t from, rev_iter_t to); - Changes promoteReadMarker(User* u, rev_iter_t newMarker, - bool force = false); + Changes promoteReadMarker(User* u, rev_iter_t newMarker, bool force = false); Changes markMessagesAsRead(rev_iter_t upToMarker); @@ -273,13 +282,14 @@ class Room::Private if (q->successorId().isEmpty()) { // TODO: Queue up state events sending (see #133). return connection->callApi<SetRoomStateWithKeyJob>( - id, EvT::matrixTypeId(), stateKey, event.contentJson()); + id, EvT::matrixTypeId(), stateKey, event.contentJson()); } qCWarning(MAIN) << q << "has been upgraded, state won't be set"; return nullptr; } - template <typename EvT> auto requestSetState(const EvT& event) + template <typename EvT> + auto requestSetState(const EvT& event) { return connection->callApi<SetRoomStateJob>(id, EvT::matrixTypeId(), event.contentJson()); @@ -297,7 +307,7 @@ class Room::Private QJsonObject toJson() const; - private: +private: using users_shortlist_t = std::array<User*, 3>; template <typename ContT> users_shortlist_t buildShortlist(const ContT& users) const; @@ -307,19 +317,19 @@ class Room::Private }; Room::Room(Connection* connection, QString id, JoinState initialJoinState) - : QObject(connection), d(new Private(connection, id, initialJoinState)) + : QObject(connection) + , d(new Private(connection, id, initialJoinState)) { setObjectName(id); // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; d->displayname = d->calculateDisplayname(); // Set initial "Empty room" name - connectUntil( - connection, &Connection::loadedRoomState, this, [this](Room* r) { - if (this == r) - emit baseStateLoaded(); - return this == r; // loadedRoomState fires only once per room - }); + connectUntil(connection, &Connection::loadedRoomState, this, [this](Room* r) { + if (this == r) + emit baseStateLoaded(); + return this == r; // loadedRoomState fires only once per room + }); qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } @@ -330,13 +340,13 @@ const QString& Room::id() const { return d->id; } QString Room::version() const { const auto v = d->getCurrentState<RoomCreateEvent>()->version(); - return v.isEmpty() ? "1" : v; + return v.isEmpty() ? QStringLiteral("1") : v; } bool Room::isUnstable() const { return !connection()->loadingCapabilities() - && !connection()->stableRoomVersions().contains(version()); + && !connection()->stableRoomVersions().contains(version()); } QString Room::predecessorId() const @@ -356,6 +366,11 @@ const Room::PendingEvents& Room::pendingEvents() const return d->unsyncedEvents; } +bool Room::allHistoryLoaded() const +{ + return !d->timeline.empty() && is<RoomCreateEvent>(*d->timeline.front()); +} + QString Room::name() const { return d->getCurrentState<RoomNameEvent>()->name(); @@ -373,6 +388,8 @@ QString Room::canonicalAlias() const QString Room::displayName() const { return d->displayname; } +void Room::refreshDisplayName() { d->updateDisplayname(); } + QString Room::topic() const { return d->getCurrentState<RoomTopicEvent>()->topic(); @@ -396,8 +413,7 @@ QImage Room::avatar(int width, int height) const auto dcUsers = directChatUsers(); for (auto* u : dcUsers) if (u != localUser()) - return u->avatar(width, height, this, - [=] { emit avatarChanged(); }); + return u->avatar(width, height, this, [=] { emit avatarChanged(); }); return {}; } @@ -465,14 +481,13 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) QElapsedTimer et; et.start(); - const auto newUnreadMessages = count_if( - from, to, std::bind(&Room::Private::isEventNotable, this, _1)); + const auto newUnreadMessages = + count_if(from, to, std::bind(&Room::Private::isEventNotable, this, _1)); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Counting gained unread messages took" << et; if (newUnreadMessages > 0) { - // See - // https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages < 0) unreadMessages = 0; @@ -480,8 +495,8 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to) qCDebug(MAIN) << "Room" << q->objectName() << "has gained" << newUnreadMessages << "unread message(s)," << (q->readMarker() == timeline.crend() - ? "in total at least" - : "in total") + ? "in total at least" + : "in total") << unreadMessages << "unread message(s)"; emit q->unreadMessagesChanged(q); } @@ -494,17 +509,18 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend()); const auto prevMarker = q->readMarker(u); - if (!force - && prevMarker <= newMarker) // Remember, we deal with reverse iterators + if (!force && prevMarker <= newMarker) // Remember, we deal with reverse + // iterators return Change::NoChange; Q_ASSERT(newMarker < timeline.crend()); // Try to auto-promote the read marker over the user's own messages // (switch to direct iterators for that). - auto eagerMarker = find_if( - newMarker.base(), timeline.cend(), - [=](const TimelineItem& ti) { return ti->senderId() != u->id(); }); + auto eagerMarker = find_if(newMarker.base(), timeline.cend(), + [=](const TimelineItem& ti) { + return ti->senderId() != u->id(); + }); auto changes = setLastReadEvent(u, (*(eagerMarker - 1))->id()); if (isLocalUser(u)) { @@ -512,20 +528,19 @@ Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, QElapsedTimer et; et.start(); unreadMessages = - count_if(eagerMarker, timeline.cend(), - std::bind(&Room::Private::isEventNotable, this, _1)); + int(count_if(eagerMarker, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1))); if (et.nsecsElapsed() > profilerMinNsecs() / 10) qCDebug(PROFILER) << "Recounting unread messages took" << et; - // See - // https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (unreadMessages == 0) unreadMessages = -1; if (force || unreadMessages != oldUnreadCount) { if (unreadMessages == -1) { - qCDebug(MAIN) << "Room" << displayname - << "has no more unread messages"; + qCDebug(MAIN) + << "Room" << displayname << "has no more unread messages"; } else qCDebug(MAIN) << "Room" << displayname << "still has" << unreadMessages << "unread message(s)"; @@ -548,8 +563,9 @@ Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker) // until the previous last-read message, whichever comes first. for (; upToMarker < prevMarker; ++upToMarker) { if ((*upToMarker)->senderId() != q->localUser()->id()) { - connection->callApi<PostReceiptJob>(id, "m.read", - (*upToMarker)->id()); + connection->callApi<PostReceiptJob>(id, QStringLiteral("m.read"), + QUrl::toPercentEncoding( + (*upToMarker)->id())); break; } } @@ -569,22 +585,26 @@ void Room::markAllMessagesAsRead() bool Room::canSwitchVersions() const { + if (!successorId().isEmpty()) + return false; // No one can upgrade a room that's already upgraded + // TODO, #276: m.room.power_levels - const auto* plEvt = d->currentState.value({ "m.room.power_levels", "" }); + const auto* plEvt = + d->currentState.value({ QStringLiteral("m.room.power_levels"), {} }); if (!plEvt) return true; const auto plJson = plEvt->contentJson(); const auto currentUserLevel = - plJson.value("users"_ls) - .toObject() - .value(localUser()->id()) - .toInt(plJson.value("users_default"_ls).toInt()); + plJson.value("users"_ls) + .toObject() + .value(localUser()->id()) + .toInt(plJson.value("users_default"_ls).toInt()); const auto tombstonePowerLevel = - plJson.value("events") - .toObject() - .value("m.room.tombstone"_ls) - .toInt(plJson.value("state_default"_ls).toInt()); + plJson.value("events"_ls) + .toObject() + .value("m.room.tombstone"_ls) + .toInt(plJson.value("state_default"_ls).toInt()); return currentUserLevel >= tombstonePowerLevel; } @@ -614,13 +634,13 @@ TimelineItem::index_t Room::maxTimelineIndex() const bool Room::isValidIndex(TimelineItem::index_t timelineIndex) const { return !d->timeline.empty() && timelineIndex >= minTimelineIndex() - && timelineIndex <= maxTimelineIndex(); + && timelineIndex <= maxTimelineIndex(); } Room::rev_iter_t Room::findInTimeline(TimelineItem::index_t index) const { return timelineEdge() - - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); + - (isValidIndex(index) ? index - minTimelineIndex() + 1 : 0); } Room::rev_iter_t Room::findInTimeline(const QString& evtId) const @@ -657,7 +677,7 @@ void Room::Private::getAllMembers() return; allMembersJob = connection->callApi<GetMembersByRoomJob>( - id, connection->nextBatchToken(), "join"); + id, connection->nextBatchToken(), "join"); auto nextIndex = timeline.empty() ? 0 : timeline.back().index() + 1; connect(allMembersJob, &BaseJob::success, q, [=] { Q_ASSERT(timeline.empty() || nextIndex <= q->maxTimelineIndex() + 1); @@ -760,7 +780,7 @@ void Room::resetNotificationCount() if (d->notificationCount == 0) return; d->notificationCount = 0; - emit notificationCountChanged(this); + emit notificationCountChanged(); } int Room::highlightCount() const { return d->highlightCount; } @@ -770,14 +790,20 @@ void Room::resetHighlightCount() if (d->highlightCount == 0) return; d->highlightCount = 0; - emit highlightCountChanged(this); + emit highlightCountChanged(); } void Room::switchVersion(QString newVersion) { - auto* job = connection()->callApi<UpgradeRoomJob>(id(), newVersion); - connect(job, &BaseJob::failure, this, - [this, job] { emit upgradeFailed(job->errorString()); }); + if (!successorId().isEmpty()) { + Q_ASSERT(!successorId().isEmpty()); + emit upgradeFailed(tr("The room is already upgraded")); + } + if (auto* job = connection()->callApi<UpgradeRoomJob>(id(), newVersion)) + connect(job, &BaseJob::failure, this, + [this, job] { emit upgradeFailed(job->errorString()); }); + else + emit upgradeFailed(tr("Couldn't initiate upgrade")); } bool Room::hasAccountData(const QString& type) const @@ -848,8 +874,8 @@ void Room::setTags(TagsMap newTags) { d->setTags(move(newTags)); connection()->callApi<SetAccountDataPerRoomJob>( - localUser()->id(), id(), TagEvent::matrixTypeId(), - TagEvent(d->tags).contentJson()); + localUser()->id(), id(), TagEvent::matrixTypeId(), + TagEvent(d->tags).contentJson()); } void Room::Private::setTags(TagsMap newTags) @@ -867,7 +893,7 @@ void Room::Private::setTags(TagsMap newTags) } tags = move(newTags); qCDebug(MAIN) << "Room" << q->objectName() << "is tagged with" - << q->tagNames().join(", "); + << q->tagNames().join(QStringLiteral(", ")); emit q->tagsChanged(); } @@ -980,8 +1006,7 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const fti.progress = int(progress); fti.total = int(total); fti.localDir = QUrl::fromLocalFile(infoIt->localFileInfo.absolutePath()); - fti.localPath = - QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); + fti.localPath = QUrl::fromLocalFile(infoIt->localFileInfo.absoluteFilePath()); return fti; #else return { infoIt->status, @@ -1040,8 +1065,8 @@ bool Room::usesEncryption() const int Room::joinedCount() const { return d->summary.joinedMemberCount.omitted() - ? d->membersMap.size() - : d->summary.joinedMemberCount.value(); + ? d->membersMap.size() + : d->summary.joinedMemberCount.value(); } int Room::invitedCount() const @@ -1059,8 +1084,8 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary) { if (!summary.merge(newSummary)) return Change::NoChange; - qCDebug(MAIN).nospace().noquote() << "Updated room summary for " - << q->objectName() << ": " << summary; + qCDebug(MAIN).nospace().noquote() + << "Updated room summary for " << q->objectName() << ": " << summary; emit q->memberListChanged(); return Change::SummaryChange; } @@ -1070,7 +1095,11 @@ void Room::Private::insertMemberIntoMap(User* u) const auto userName = u->name(q); // If there is exactly one namesake of the added user, signal member // renaming for that other one because the two should be disambiguated now. - auto namesakes = membersMap.values(userName); + const auto namesakes = membersMap.values(userName); + + // Callers should check they are not adding an existing user once more. + Q_ASSERT(!namesakes.contains(u)); + if (namesakes.size() == 1) emit q->memberAboutToRename(namesakes.front(), namesakes.front()->fullName(q)); @@ -1079,7 +1108,7 @@ void Room::Private::insertMemberIntoMap(User* u) emit q->memberRenamed(namesakes.front()); } -void Room::Private::renameMember(User* u, QString oldName) +void Room::Private::renameMember(User* u, const QString& oldName) { if (u->name(q) == oldName) { qCWarning(MAIN) << "Room::Private::renameMember(): the user " @@ -1089,7 +1118,6 @@ void Room::Private::renameMember(User* u, QString oldName) removeMemberFromMap(oldName, u); insertMemberIntoMap(u); } - emit q->memberRenamed(u); } void Room::Private::removeMemberFromMap(const QString& username, User* u) @@ -1097,15 +1125,13 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) User* namesake = nullptr; auto namesakes = membersMap.values(username); if (namesakes.size() == 2) { - namesake = - namesakes.front() == u ? namesakes.back() : namesakes.front(); + namesake = namesakes.front() == u ? namesakes.back() : namesakes.front(); Q_ASSERT_X(namesake != u, __FUNCTION__, "Room members list is broken"); emit q->memberAboutToRename(namesake, username); } membersMap.remove(username, u); // If there was one namesake besides the removed user, signal member - // renaming for it because it doesn't need to be disambiguated anymore. - // TODO: Think about left users. + // renaming for it because it doesn't need to be disambiguated any more. if (namesake) emit q->memberRenamed(namesake); } @@ -1115,7 +1141,7 @@ inline auto makeErrorStr(const Event& e, QByteArray msg) return msg.append("; event dump follows:\n").append(e.originalJson()); } -Room::Timeline::difference_type +Room::Timeline::size_type Room::Private::moveEventsToTimeline(RoomEventsRange events, EventsPlacement placement) { @@ -1124,21 +1150,19 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events, // them is almost symmetric to the one for new messages. New messages get // appended from index 0; old messages go backwards from index -1. auto index = timeline.empty() - ? -((placement + 1) / 2) /* 1 -> -1; -1 -> 0 */ - : placement == Older ? timeline.front().index() - : timeline.back().index(); + ? -((placement + 1) / 2) /* 1 -> -1; -1 -> 0 */ + : placement == Older ? timeline.front().index() + : timeline.back().index(); auto baseIndex = index; for (auto&& e : events) { const auto eId = e->id(); Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); Q_ASSERT_X( - !eId.isEmpty(), __FUNCTION__, - makeErrorStr(*e, - "Event with empty id cannot be in the timeline")); + !eId.isEmpty(), __FUNCTION__, + makeErrorStr(*e, "Event with empty id cannot be in the timeline")); Q_ASSERT_X( - !eventsIndex.contains(eId), __FUNCTION__, - makeErrorStr(*e, - "Event is already in the timeline; " + !eventsIndex.contains(eId), __FUNCTION__, + makeErrorStr(*e, "Event is already in the timeline; " "incoming events were not properly deduplicated")); if (placement == Older) timeline.emplace_front(move(e), --index); @@ -1210,8 +1234,9 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) et.restart(); roomChanges |= d->addNewMessageEvents(move(data.timeline)); if (data.timeline.size() > 9 || et.nsecsElapsed() >= profilerMinNsecs()) - qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" - << data.timeline.size() << "event(s)," << et; + qCDebug(PROFILER) + << "*** Room::addNewMessageEvents():" << data.timeline.size() + << "event(s)," << et; } if (roomChanges & TopicChange) emit topicChanged(); @@ -1223,12 +1248,11 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) emit memberListChanged(); roomChanges |= d->setSummary(move(data.summary)); - d->updateDisplayname(); for (auto&& ephemeralEvent : data.ephemeral) roomChanges |= processEphemeralEvent(move(ephemeralEvent)); - // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count + // See https://github.com/quotient-im/libQuotient/wiki/unread_count if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages) { qCDebug(MAIN) << "Setting unread_count to" << data.unreadCount; d->unreadMessages = data.unreadCount; @@ -1237,13 +1261,14 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) if (data.highlightCount != d->highlightCount) { d->highlightCount = data.highlightCount; - emit highlightCountChanged(this); + emit highlightCountChanged(); } if (data.notificationCount != d->notificationCount) { d->notificationCount = data.notificationCount; - emit notificationCountChanged(this); + emit notificationCountChanged(); } if (roomChanges != Change::NoChange) { + d->updateDisplayname(); emit changed(roomChanges); if (!fromCache) connection()->saveRoomState(this); @@ -1275,8 +1300,8 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) const auto txnId = pEvent->transactionId(); // TODO, #133: Enqueue the job rather than immediately trigger it. if (auto call = connection->callApi<SendMessageJob>( - BackgroundRequest, id, pEvent->matrixType(), txnId, - pEvent->contentJson())) { + BackgroundRequest, id, pEvent->matrixType(), txnId, + pEvent->contentJson())) { Room::connect(call, &BaseJob::started, q, [this, txnId] { auto it = q->findPendingEvent(txnId); if (it == unsyncedEvents.end()) { @@ -1285,7 +1310,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) return; } it->setDeparted(); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); Room::connect(call, &BaseJob::failure, q, std::bind(&Room::Private::onEventSendingFailure, this, @@ -1300,7 +1325,7 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) } it->setReachedServer(call->eventId()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); }); } else onEventSendingFailure(txnId); @@ -1315,10 +1340,9 @@ void Room::Private::onEventSendingFailure(const QString& txnId, BaseJob* call) << "could not be sent"; return; } - it->setSendingFailed(call ? call->statusCaption() % ": " - % call->errorString() + it->setSendingFailed(call ? call->statusCaption() % ": " % call->errorString() : tr("The call could not be started")); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); + emit q->pendingEventChanged(int(it - unsyncedEvents.begin())); } QString Room::retryMessage(const QString& txnId) @@ -1340,16 +1364,15 @@ QString Room::retryMessage(const QString& txnId) emit fileTransferFailed(txnId, tr("File upload will be retried")); } - uploadFile(txnId, - QUrl::fromLocalFile( - transferIt->localFileInfo.absoluteFilePath())); + uploadFile(txnId, QUrl::fromLocalFile( + transferIt->localFileInfo.absoluteFilePath())); // FIXME: Content type is no more passed here but it should } } if (it->deliveryStatus() == EventStatus::ReachedServer) { qCWarning(MAIN) - << "The previous attempt has reached the server; two" - " events are likely to be in the timeline after retry"; + << "The previous attempt has reached the server; two" + " events are likely to be in the timeline after retry"; } it->resetStatus(); return d->doSendEvent(it->event()); @@ -1357,9 +1380,10 @@ QString Room::retryMessage(const QString& txnId) void Room::discardMessage(const QString& txnId) { - auto it = std::find_if( - d->unsyncedEvents.begin(), d->unsyncedEvents.end(), - [txnId](const auto& evt) { return evt->transactionId() == txnId; }); + auto it = std::find_if(d->unsyncedEvents.begin(), d->unsyncedEvents.end(), + [txnId](const auto& evt) { + return evt->transactionId() == txnId; + }); Q_ASSERT(it != d->unsyncedEvents.end()); qDebug(EVENTS) << "Discarding transaction" << txnId; const auto& transferIt = d->fileTransfers.find(txnId); @@ -1371,8 +1395,8 @@ void Room::discardMessage(const QString& txnId) emit fileTransferFailed(txnId, tr("File upload cancelled")); } else if (transferIt->status == FileTransferInfo::Completed) { qCWarning(MAIN) - << "File for transaction" << txnId - << "has been uploaded but the message was discarded"; + << "File for transaction" << txnId + << "has been uploaded but the message was discarded"; } } emit pendingEventAboutToDiscard(int(it - d->unsyncedEvents.begin())); @@ -1394,8 +1418,8 @@ QString Room::postHtmlMessage(const QString& plainText, const QString& html, MessageEventType type) { return d->sendEvent<RoomMessageEvent>( - plainText, type, - new EventContent::TextContent(html, QStringLiteral("text/html"))); + plainText, type, + new EventContent::TextContent(html, QStringLiteral("text/html"))); } QString Room::postHtmlText(const QString& plainText, const QString& html) @@ -1408,31 +1432,34 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, { QFileInfo localFile { localPath.toLocalFile() }; Q_ASSERT(localFile.isFile()); + + const auto txnId = connection()->generateTxnId(); // Remote URL will only be known after upload; fill in the local path // to enable the preview while the event is pending. - const auto txnId = - d->addAsPending(makeEvent<RoomMessageEvent>(plainText, localFile, - asGenericFile)) - ->transactionId(); uploadFile(txnId, localPath); + { + auto&& event = makeEvent<RoomMessageEvent>(plainText, localFile, + asGenericFile); + event->setTransactionId(txnId); + d->addAsPending(std::move(event)); + } auto* context = new QObject(this); connect(this, &Room::fileTransferCompleted, context, - [context, this, txnId](const QString& id, QUrl, - const QUrl& mxcUri) { + [context, this, txnId](const QString& id, QUrl, const QUrl& mxcUri) { if (id == txnId) { auto it = findPendingEvent(txnId); if (it != d->unsyncedEvents.end()) { it->setFileUploaded(mxcUri); emit pendingEventChanged( - int(it - d->unsyncedEvents.begin())); + int(it - d->unsyncedEvents.begin())); d->doSendEvent(it->get()); } else { // Normally in this situation we should instruct // the media server to delete the file; alas, there's no // API specced for that. - qCWarning(MAIN) << "File uploaded to" << mxcUri - << "but the event referring to it was " - "cancelled"; + qCWarning(MAIN) + << "File uploaded to" << mxcUri + << "but the event referring to it was cancelled"; } context->deleteLater(); } @@ -1445,8 +1472,7 @@ QString Room::postFile(const QString& plainText, const QUrl& localPath, const auto idx = int(it - d->unsyncedEvents.begin()); emit pendingEventAboutToDiscard(idx); // See #286 on why iterator may not be valid here. - d->unsyncedEvents.erase(d->unsyncedEvents.begin() - + idx); + d->unsyncedEvents.erase(d->unsyncedEvents.begin() + idx); emit pendingEventDiscarded(); } context->deleteLater(); @@ -1470,7 +1496,7 @@ QString Room::postJson(const QString& matrixType, const QJsonObject& eventContent) { return d->sendEvent( - loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent))); + loadEvent<RoomEvent>(basicEventJson(matrixType, eventContent))); } void Room::setName(const QString& newName) @@ -1519,7 +1545,7 @@ void Room::checkVersion() { const auto defaultVersion = connection()->defaultRoomVersion(); const auto stableVersions = connection()->stableRoomVersions(); - Q_ASSERT(!defaultVersion.isEmpty() && successorId().isEmpty()); + Q_ASSERT(!defaultVersion.isEmpty()); // This method is only called after the base state has been loaded // or the server capabilities have been loaded. emit stabilityUpdated(defaultVersion, stableVersions); @@ -1616,8 +1642,8 @@ void Room::unban(const QString& userId) void Room::redactEvent(const QString& eventId, const QString& reason) { - connection()->callApi<RedactEventJob>( - id(), eventId, connection()->generateTxnId(), reason); + connection()->callApi<RedactEventJob>(id(), QUrl::toPercentEncoding(eventId), + connection()->generateTxnId(), reason); } void Room::uploadFile(const QString& id, const QUrl& localFilename, @@ -1660,19 +1686,24 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) const auto* event = d->getEventWithFile(eventId); if (!event) { qCCritical(MAIN) - << eventId - << "is not in the local timeline or has no file content"; + << eventId << "is not in the local timeline or has no file content"; Q_ASSERT(false); return; } - const auto fileUrl = event->content()->fileInfo()->url; + const auto* const fileInfo = event->content()->fileInfo(); + if (!fileInfo->isValid()) { + qCWarning(MAIN) << "Event" << eventId + << "has an empty or malformed mxc URL; won't download"; + return; + } + const auto fileUrl = fileInfo->url; auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { // Build our own file path, starting with temp directory and eventId. filePath = eventId; filePath = QDir::tempPath() % '/' - % filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") - % '#' % d->fileNameToDownload(event); + % filePath.replace(QRegularExpression("[/\\<>|\"*?:]"), "_") + % '#' % d->fileNameToDownload(event); } auto job = connection()->downloadFile(fileUrl, filePath); if (isJobRunning(job)) { @@ -1687,8 +1718,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) connect(job, &BaseJob::success, this, [this, eventId, fileUrl, job] { d->fileTransfers[eventId].status = FileTransferInfo::Completed; emit fileTransferCompleted( - eventId, fileUrl, - QUrl::fromLocalFile(job->targetFileName())); + eventId, fileUrl, QUrl::fromLocalFile(job->targetFileName())); }); connect(job, &BaseJob::failure, this, std::bind(&Private::failedTransfer, d, eventId, @@ -1718,10 +1748,10 @@ void Room::Private::dropDuplicateEvents(RoomEvents& events) const // Multiple-remove (by different criteria), single-erase // 1. Check for duplicates against the timeline. - auto dupsBegin = - remove_if(events.begin(), events.end(), [&](const RoomEventPtr& e) { - return eventsIndex.contains(e->id()); - }); + auto dupsBegin = remove_if(events.begin(), events.end(), + [&](const RoomEventPtr& e) { + return eventsIndex.contains(e->id()); + }); // 2. Check for duplicates within the batch if there are still events. for (auto eIt = events.begin(); distance(eIt, dupsBegin) > 1; ++eIt) @@ -1771,9 +1801,8 @@ RoomEventPtr makeRedacted(const RoomEvent& target, // { QStringLiteral("ban"), QStringLiteral("events"), // QStringLiteral("events_default"), // QStringLiteral("kick"), QStringLiteral("redact"), - // QStringLiteral("state_default"), - // QStringLiteral("users"), QStringLiteral("users_default") - // } } + // QStringLiteral("state_default"), QStringLiteral("users"), + // QStringLiteral("users_default") } } , { RoomAliasesEvent::typeId(), { QStringLiteral("aliases") } } // , { RoomHistoryVisibility::typeId(), @@ -1785,9 +1814,9 @@ RoomEventPtr makeRedacted(const RoomEvent& target, else ++it; } - auto keepContentKeys = find_if( - keepContentKeysMap.begin(), keepContentKeysMap.end(), - [&target](const auto& t) { return target.type() == t.first; }); + auto keepContentKeys = + find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), + [&target](const auto& t) { return target.type() == t.first; }); if (keepContentKeys == keepContentKeysMap.end()) { originalJson.remove(ContentKeyL); originalJson.remove(PrevContentKeyL); @@ -1833,12 +1862,12 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction) const StateEventKey evtKey { oldEvent->matrixType(), oldEvent->stateKey() }; Q_ASSERT(currentState.contains(evtKey)); - if (currentState[evtKey] == oldEvent.get()) { - Q_ASSERT(ti.index() - >= 0); // Historical states can't be in currentState + if (currentState.value(evtKey) == oldEvent.get()) { + Q_ASSERT(ti.index() >= 0); // Historical states can't be in + // currentState qCDebug(MAIN).nospace() - << "Reverting state " << oldEvent->matrixType() << "/" - << oldEvent->stateKey(); + << "Redacting state " << oldEvent->matrixType() << "/" + << oldEvent->stateKey(); // Retarget the current state to the newly made event. if (q->processStateEvent(*ti)) emit q->namesChanged(q); @@ -1879,17 +1908,17 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // Try to find the target in the timeline, then in the batch. if (processRedaction(*r)) continue; - auto targetIt = std::find_if( - events.begin(), redactionIt, - [id = r->redactedEvent()](const RoomEventPtr& ep) { - return ep->id() == id; - }); + auto targetIt = + std::find_if(events.begin(), redactionIt, + [id = r->redactedEvent()](const RoomEventPtr& ep) { + return ep->id() == id; + }); if (targetIt != redactionIt) *targetIt = makeRedacted(**targetIt, *r); else qCDebug(MAIN) - << "Redaction" << r->id() << "ignored: target event" - << r->redactedEvent() << "is not found"; + << "Redaction" << r->id() << "ignored: target event" + << r->redactedEvent() << "is not found"; // If the target event comes later, it comes already redacted. } @@ -1903,11 +1932,11 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) roomChanges |= q->processStateEvent(*eptr); auto timelineSize = timeline.size(); - auto totalInserted = 0; + size_t totalInserted = 0; for (auto it = events.begin(); it != events.end();) { - auto nextPendingPair = - findFirstOf(it, events.end(), unsyncedEvents.begin(), - unsyncedEvents.end(), isEchoEvent); + auto nextPendingPair = findFirstOf(it, events.end(), + unsyncedEvents.begin(), + unsyncedEvents.end(), isEchoEvent); auto nextPending = nextPendingPair.first; if (it != nextPending) { @@ -1926,7 +1955,7 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) it = nextPending + 1; auto* nextPendingEvt = nextPending->get(); const auto pendingEvtIdx = - int(nextPendingPair.second - unsyncedEvents.begin()); + int(nextPendingPair.second - unsyncedEvents.begin()); emit q->pendingEventAboutToMerge(nextPendingEvt, pendingEvtIdx); qDebug(EVENTS) << "Merging pending event from transaction" << nextPendingEvt->transactionId() << "into" @@ -1940,8 +1969,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) // unsyncedEvents (see #286). Fortunately, unsyncedEvents only grows at // its back so we can rely on the index staying valid at least. unsyncedEvents.erase(unsyncedEvents.begin() + pendingEvtIdx); - if (auto insertedSize = - moveEventsToTimeline({ nextPending, it }, Newer)) { + if (auto insertedSize = moveEventsToTimeline({ nextPending, it }, + Newer)) { totalInserted += insertedSize; q->onAddNewTimelineEvents(timeline.cend() - insertedSize); } @@ -1956,9 +1985,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) emit q->callEvent(q, evt); if (totalInserted > 0) { - qCDebug(MAIN) << "Room" << q->objectName() << "received" - << totalInserted << "new events; the last event is now" - << timeline.back(); + qCDebug(MAIN) << "Room" << q->objectName() << "received" << totalInserted + << "new events; the last event is now" << timeline.back(); // The first event in the just-added batch (referred to by `from`) // defines whose read marker can possibly be promoted any further over @@ -1969,9 +1997,8 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events) auto firstWriter = q->user((*from)->senderId()); if (q->readMarker(firstWriter) != timeline.crend()) { roomChanges |= promoteReadMarker(firstWriter, rev_iter_t(from) - 1); - qCDebug(MAIN) << "Auto-promoted read marker for" - << firstWriter->id() << "to" - << *q->readMarker(firstWriter); + qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() + << "to" << *q->readMarker(firstWriter); } updateUnreadCount(timeline.crbegin(), rev_iter_t(from)); @@ -2028,93 +2055,122 @@ Room::Changes Room::processStateEvent(const RoomEvent& e) return Change::NoChange; const auto* oldStateEvent = - std::exchange(d->currentState[{ e.matrixType(), e.stateKey() }], - static_cast<const StateEventBase*>(&e)); + std::exchange(d->currentState[{ e.matrixType(), e.stateKey() }], + static_cast<const StateEventBase*>(&e)); Q_ASSERT(!oldStateEvent || (oldStateEvent->matrixType() == e.matrixType() && oldStateEvent->stateKey() == e.stateKey())); - if (!is<RoomMemberEvent>(e)) + if (!is<RoomMemberEvent>(e)) // Room member events are too numerous qCDebug(EVENTS) << "Room state event:" << e; return visit( - e, [](const RoomNameEvent&) { return NameChange; }, - [this, oldStateEvent](const RoomAliasesEvent& ae) { - const auto previousAliases = oldStateEvent - ? static_cast<const RoomAliasesEvent*>(oldStateEvent) - ->aliases() - : QStringList(); - connection()->updateRoomAliases(id(), previousAliases, - ae.aliases()); - return OtherChange; - }, - [this](const RoomCanonicalAliasEvent& evt) { - setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); - return CanonicalAliasChange; - }, - [](const RoomTopicEvent&) { return TopicChange; }, - [this](const RoomAvatarEvent& evt) { - if (d->avatar.updateUrl(evt.url())) - emit avatarChanged(); - return AvatarChange; - }, - [this](const RoomMemberEvent& evt) { - auto* u = user(evt.userId()); - u->processEvent(evt, this); - if (u == localUser() && memberJoinState(u) == JoinState::Invite - && evt.isDirect()) - connection()->addToDirectChats(this, user(evt.senderId())); - - if (evt.membership() == MembershipType::Join) { - if (memberJoinState(u) != JoinState::Join) { - d->insertMemberIntoMap(u); - connect(u, &User::nameAboutToChange, this, - [=](QString newName, QString, - const Room* context) { - if (context == this) - emit memberAboutToRename(u, newName); - }); - connect(u, &User::nameChanged, this, - [=](QString, QString oldName, - const Room* context) { - if (context == this) - d->renameMember(u, oldName); - }); - emit userAdded(u); - } - } else if (evt.membership() != MembershipType::Join) { - if (memberJoinState(u) == JoinState::Join) { - if (evt.membership() == MembershipType::Invite) - qCWarning(MAIN) - << "Invalid membership change:" << evt; - if (!d->membersLeft.contains(u)) - d->membersLeft.append(u); - d->removeMemberFromMap(u->name(this), u); - emit userRemoved(u); - } + e, [](const RoomNameEvent&) { return NameChange; }, + [this, oldStateEvent](const RoomAliasesEvent& ae) { + const auto previousAliases = + oldStateEvent + ? static_cast<const RoomAliasesEvent*>(oldStateEvent)->aliases() + : QStringList(); + connection()->updateRoomAliases(id(), previousAliases, ae.aliases()); + return OtherChange; + }, + [this](const RoomCanonicalAliasEvent& evt) { + setObjectName(evt.alias().isEmpty() ? d->id : evt.alias()); + return CanonicalAliasChange; + }, + [](const RoomTopicEvent&) { return TopicChange; }, + [this](const RoomAvatarEvent& evt) { + if (d->avatar.updateUrl(evt.url())) + emit avatarChanged(); + return AvatarChange; + }, + [this, oldStateEvent](const RoomMemberEvent& evt) { + auto* u = user(evt.userId()); + const auto* oldMemberEvent = + static_cast<const RoomMemberEvent*>(oldStateEvent); + u->processEvent(evt, this, oldMemberEvent == nullptr); + const auto prevMembership = oldMemberEvent + ? oldMemberEvent->membership() + : MembershipType::Leave; + if (u == localUser() && evt.membership() == MembershipType::Invite + && evt.isDirect()) + connection()->addToDirectChats(this, user(evt.senderId())); + + switch (prevMembership) { + case MembershipType::Invite: + if (evt.membership() != prevMembership) { + d->usersInvited.removeOne(u); + Q_ASSERT(!d->usersInvited.contains(u)); } - return MembersChange; - }, - [this](const EncryptionEvent&) { - emit encryption(); // It can only be done once, so emit it here. - return OtherChange; - }, - [this](const RoomTombstoneEvent& evt) { - const auto successorId = evt.successorRoomId(); - if (auto* successor = connection()->room(successorId)) - emit upgraded(evt.serverMessage(), successor); - else - connectUntil( - connection(), &Connection::loadedRoomState, this, - [this, successorId, - serverMsg = evt.serverMessage()](Room* newRoom) { - if (newRoom->id() != successorId) - return false; - emit upgraded(serverMsg, newRoom); - return true; - }); + break; + case MembershipType::Join: + if (evt.membership() == MembershipType::Invite) + qCWarning(MAIN) + << "Invalid membership change from Join to Invite:" + << evt; + if (evt.membership() != prevMembership) { + disconnect(u, &User::nameAboutToChange, this, nullptr); + disconnect(u, &User::nameChanged, this, nullptr); + d->removeMemberFromMap(u->name(this), u); + emit userRemoved(u); + } + break; + default: + if (evt.membership() == MembershipType::Invite + || evt.membership() == MembershipType::Join) { + d->membersLeft.removeOne(u); + Q_ASSERT(!d->membersLeft.contains(u)); + } + } - return OtherChange; - }); + switch (evt.membership()) { + case MembershipType::Join: + if (prevMembership != MembershipType::Join) { + d->insertMemberIntoMap(u); + connect(u, &User::nameAboutToChange, this, + [=](QString newName, QString, const Room* context) { + if (context == this) + emit memberAboutToRename(u, newName); + }); + connect(u, &User::nameChanged, this, + [=](QString, QString oldName, const Room* context) { + if (context == this) { + d->renameMember(u, oldName); + emit memberRenamed(u); + } + }); + emit userAdded(u); + } + break; + case MembershipType::Invite: + if (!d->usersInvited.contains(u)) + d->usersInvited.push_back(u); + break; + default: + if (!d->membersLeft.contains(u)) + d->membersLeft.append(u); + } + return MembersChange; + }, + [this](const EncryptionEvent&) { + emit encryption(); // It can only be done once, so emit it here. + return OtherChange; + }, + [this](const RoomTombstoneEvent& evt) { + const auto successorId = evt.successorRoomId(); + if (auto* successor = connection()->room(successorId)) + emit upgraded(evt.serverMessage(), successor); + else + connectUntil(connection(), &Connection::loadedRoomState, this, + [this, successorId, + serverMsg = evt.serverMessage()](Room* newRoom) { + if (newRoom->id() != successorId) + return false; + emit upgraded(serverMsg, newRoom); + return true; + }); + + return OtherChange; + }); } Room::Changes Room::processEphemeralEvent(EventPtr&& event) @@ -2175,9 +2231,9 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event) if (evt->eventsWithReceipts().size() > 3 || totalReceipts > 10 || et.nsecsElapsed() >= profilerMinNsecs()) qCDebug(PROFILER) - << "*** Room::processEphemeralEvent(receipts):" - << evt->eventsWithReceipts().size() << "event(s) with" - << totalReceipts << "receipt(s)," << et; + << "*** Room::processEphemeralEvent(receipts):" + << evt->eventsWithReceipts().size() << "event(s) with" + << totalReceipts << "receipt(s)," << et; } return changes; } @@ -2196,8 +2252,8 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event) d->serverReadMarker = readEventId; const auto newMarker = findInTimeline(readEventId); changes |= newMarker != timelineEdge() - ? d->markMessagesAsRead(newMarker) - : d->setLastReadEvent(localUser(), readEventId); + ? d->markMessagesAsRead(newMarker) + : d->setLastReadEvent(localUser(), readEventId); } // For all account data events auto& currentData = d->accountData[event->matrixType()]; @@ -2225,12 +2281,11 @@ Room::Private::buildShortlist(const ContT& users) const // slightly extending the spec. users_shortlist_t shortlist {}; // Prefill with nullptrs std::partial_sort_copy( - users.begin(), users.end(), shortlist.begin(), shortlist.end(), - [this](const User* u1, const User* u2) { - // localUser(), if it's in the list, is sorted below all others - return isLocalUser(u2) - || (!isLocalUser(u1) && u1->id() < u2->id()); - }); + users.begin(), users.end(), shortlist.begin(), shortlist.end(), + [this](const User* u1, const User* u2) { + // localUser(), if it's in the list, is sorted below all others + return isLocalUser(u2) || (!isLocalUser(u1) && u1->id() < u2->id()); + }); return shortlist; } @@ -2261,41 +2316,61 @@ QString Room::Private::calculateDisplayname() const return dispName; // Using m.room.aliases in naming is explicitly discouraged by the spec - // if (!q->aliases().empty() && !q->aliases().at(0).isEmpty()) - // return q->aliases().at(0); // Supplementary code for 3 and 4: build the shortlist of users whose names // will be used to construct the room name. Takes into account MSC688's // "heroes" if available. + const bool localUserIsIn = joinState == JoinState::Join; const bool emptyRoom = membersMap.isEmpty() - || (membersMap.size() == 1 && isLocalUser(*membersMap.begin())); - const auto shortlist = !summary.heroes.omitted() - ? buildShortlist(summary.heroes.value()) - : !emptyRoom ? buildShortlist(membersMap) - : buildShortlist(membersLeft); + || (membersMap.size() == 1 + && isLocalUser(*membersMap.begin())); + const bool nonEmptySummary = !summary.heroes.omitted() + && !summary.heroes->empty(); + auto shortlist = nonEmptySummary ? buildShortlist(summary.heroes.value()) + : !emptyRoom ? buildShortlist(membersMap) + : users_shortlist_t {}; + + // When lazy-loading is on, we can rely on the heroes list. + // If it's off, the below code gathers invited and left members. + // NB: including invitations, if any, into naming is a spec extension. + // This kicks in when there's no lazy loading and it's a room with + // the local user as the only member, with more users invited. + if (!shortlist.front() && localUserIsIn) + shortlist = buildShortlist(usersInvited); + + if (!shortlist.front()) // Still empty shortlist; use left members + shortlist = buildShortlist(membersLeft); QStringList names; for (auto u : shortlist) { if (u == nullptr || isLocalUser(u)) break; - names.push_back(q->roomMembername(u)); + // Only disambiguate if the room is not empty + names.push_back(u->displayname(emptyRoom ? nullptr : q)); } - auto usersCountExceptLocal = emptyRoom - ? membersLeft.size() - int(joinState == JoinState::Leave) - : q->joinedCount() - int(joinState == JoinState::Join); + const auto usersCountExceptLocal = + !emptyRoom + ? q->joinedCount() - int(joinState == JoinState::Join) + : !usersInvited.empty() + ? usersInvited.count() + : membersLeft.size() - int(joinState == JoinState::Leave); if (usersCountExceptLocal > int(shortlist.size())) names << tr( - "%Ln other(s)", - "Used to make a room name from user names: A, B and _N others_", - usersCountExceptLocal); - auto namesList = QLocale().createSeparatedList(names); + "%Ln other(s)", + "Used to make a room name from user names: A, B and _N others_", + usersCountExceptLocal - int(shortlist.size())); + const auto namesList = QLocale().createSeparatedList(names); // 3. Room members if (!emptyRoom) return namesList; + // (Spec extension) Invited users + if (!usersInvited.empty()) + return tr("Empty room (invited: %1)").arg(namesList); + // 4. Users that previously left the room if (membersLeft.size() > 0) return tr("Empty room (was: %1)").arg(namesList); @@ -2339,11 +2414,10 @@ QJsonObject Room::Private::toJson() const } const auto stateObjName = joinState == JoinState::Invite - ? QStringLiteral("invite_state") - : QStringLiteral("state"); - result.insert( - stateObjName, - QJsonObject { { QStringLiteral("events"), stateEvents } }); + ? QStringLiteral("invite_state") + : QStringLiteral("state"); + result.insert(stateObjName, + QJsonObject { { QStringLiteral("events"), stateEvents } }); } if (!accountData.empty()) { @@ -2353,16 +2427,15 @@ QJsonObject Room::Private::toJson() const accountDataEvents.append(e.second->fullJson()); } result.insert(QStringLiteral("account_data"), - QJsonObject { { QStringLiteral("events"), - accountDataEvents } }); + QJsonObject { + { QStringLiteral("events"), accountDataEvents } }); } QJsonObject unreadNotifObj { { SyncRoomData::UnreadCountKey, unreadMessages } }; if (highlightCount > 0) - unreadNotifObj.insert(QStringLiteral("highlight_count"), - highlightCount); + unreadNotifObj.insert(QStringLiteral("highlight_count"), highlightCount); if (notificationCount > 0) unreadNotifObj.insert(QStringLiteral("notification_count"), notificationCount); @@ -2370,8 +2443,7 @@ QJsonObject Room::Private::toJson() const result.insert(QStringLiteral("unread_notifications"), unreadNotifObj); if (et.elapsed() > 30) - qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" - << et; + qCDebug(PROFILER) << "Room::toJson() for" << displayname << "took" << et; return result; } @@ -18,11 +18,13 @@ #pragma once -#include "csapi/message_pagination.h" #include "eventitem.h" +#include "joinstate.h" + +#include "csapi/message_pagination.h" + #include "events/accountdataevents.h" #include "events/roommessageevent.h" -#include "joinstate.h" #include <QtGui/QImage> @@ -30,590 +32,621 @@ #include <memory> #include <utility> -namespace QMatrixClient { - class Event; - class Avatar; - class SyncRoomData; - class RoomMemberEvent; - class Connection; - class User; - class MemberSorter; - class LeaveRoomJob; - class SetRoomStateWithKeyJob; - class RedactEventJob; - - /** The data structure used to expose file transfer information to views - * - * This is specifically tuned to work with QML exposing all traits as - * Q_PROPERTY values. - */ - class FileTransferInfo +namespace QMatrixClient +{ +class Event; +class Avatar; +class SyncRoomData; +class RoomMemberEvent; +class Connection; +class User; +class MemberSorter; +class LeaveRoomJob; +class SetRoomStateWithKeyJob; +class RedactEventJob; + +/** The data structure used to expose file transfer information to views + * + * This is specifically tuned to work with QML exposing all traits as + * Q_PROPERTY values. + */ +class FileTransferInfo +{ + Q_GADGET + Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT) + Q_PROPERTY(bool active READ active CONSTANT) + Q_PROPERTY(bool started READ started CONSTANT) + Q_PROPERTY(bool completed READ completed CONSTANT) + Q_PROPERTY(bool failed READ failed CONSTANT) + Q_PROPERTY(int progress MEMBER progress CONSTANT) + Q_PROPERTY(int total MEMBER total CONSTANT) + Q_PROPERTY(QUrl localDir MEMBER localDir CONSTANT) + Q_PROPERTY(QUrl localPath MEMBER localPath CONSTANT) +public: + enum Status { - Q_GADGET - Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT) - Q_PROPERTY(bool active READ active CONSTANT) - Q_PROPERTY(bool started READ started CONSTANT) - Q_PROPERTY(bool completed READ completed CONSTANT) - Q_PROPERTY(bool failed READ failed CONSTANT) - Q_PROPERTY(int progress MEMBER progress CONSTANT) - Q_PROPERTY(int total MEMBER total CONSTANT) - Q_PROPERTY(QUrl localDir MEMBER localDir CONSTANT) - Q_PROPERTY(QUrl localPath MEMBER localPath CONSTANT) - public: - enum Status { None, Started, Completed, Failed, Cancelled }; - Status status = None; - bool isUpload = false; - int progress = 0; - int total = -1; - QUrl localDir {}; - QUrl localPath {}; - - bool started() const { return status == Started; } - bool completed() const { return status == Completed; } - bool active() const { return started() || completed(); } - bool failed() const { return status == Failed; } + None, + Started, + Completed, + Failed, + Cancelled }; - - class Room : public QObject + Status status = None; + bool isUpload = false; + int progress = 0; + int total = -1; + QUrl localDir {}; + QUrl localPath {}; + + bool started() const { return status == Started; } + bool completed() const { return status == Completed; } + bool active() const { return started() || completed(); } + bool failed() const { return status == Failed; } +}; + +class Room : public QObject +{ + Q_OBJECT + Q_PROPERTY(Connection* connection READ connection CONSTANT) + Q_PROPERTY(User* localUser READ localUser CONSTANT) + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString version READ version NOTIFY baseStateLoaded) + Q_PROPERTY(bool isUnstable READ isUnstable NOTIFY stabilityUpdated) + Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded) + Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded) + Q_PROPERTY(QString name READ name NOTIFY namesChanged) + Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged) + Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) + Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged) + Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) + Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged + STORED false) + Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) + Q_PROPERTY(bool usesEncryption READ usesEncryption NOTIFY encryption) + + Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) + Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) + Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged) + Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged) + Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY memberListChanged) + + Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY + displayedChanged) + Q_PROPERTY(QString firstDisplayedEventId READ firstDisplayedEventId WRITE + setFirstDisplayedEventId NOTIFY firstDisplayedEventChanged) + Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE + setLastDisplayedEventId NOTIFY lastDisplayedEventChanged) + + Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE + markMessagesAsRead NOTIFY readMarkerMoved) + Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY + unreadMessagesChanged) + Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) + Q_PROPERTY(int highlightCount READ highlightCount NOTIFY + highlightCountChanged RESET resetHighlightCount) + Q_PROPERTY(int notificationCount READ notificationCount NOTIFY + notificationCountChanged RESET resetNotificationCount) + Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages + STORED false) + Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) + Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged) + Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged) + + Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob NOTIFY + eventsHistoryJobChanged) + +public: + using Timeline = std::deque<TimelineItem>; + using PendingEvents = std::vector<PendingEventItem>; + using rev_iter_t = Timeline::const_reverse_iterator; + using timeline_iter_t = Timeline::const_iterator; + + enum Change : uint { - Q_OBJECT - Q_PROPERTY(Connection* connection READ connection CONSTANT) - Q_PROPERTY(User* localUser READ localUser CONSTANT) - Q_PROPERTY(QString id READ id CONSTANT) - Q_PROPERTY(QString version READ version NOTIFY baseStateLoaded) - Q_PROPERTY(bool isUnstable READ isUnstable NOTIFY stabilityUpdated) - Q_PROPERTY( - QString predecessorId READ predecessorId NOTIFY baseStateLoaded) - Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded) - Q_PROPERTY(QString name READ name NOTIFY namesChanged) - Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged) - Q_PROPERTY( - QString canonicalAlias READ canonicalAlias NOTIFY namesChanged) - Q_PROPERTY(QString displayName READ displayName NOTIFY namesChanged) - Q_PROPERTY(QString topic READ topic NOTIFY topicChanged) - Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged - STORED false) - Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) - Q_PROPERTY(bool usesEncryption READ usesEncryption NOTIFY encryption) - - Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages) - Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY - memberListChanged) - Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged) - Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged) - Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged) - Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY - memberListChanged) - - Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY - displayedChanged) - Q_PROPERTY(QString firstDisplayedEventId READ firstDisplayedEventId - WRITE setFirstDisplayedEventId NOTIFY - firstDisplayedEventChanged) - Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE - setLastDisplayedEventId NOTIFY - lastDisplayedEventChanged) - - Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE - markMessagesAsRead NOTIFY readMarkerMoved) - Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY - unreadMessagesChanged) - Q_PROPERTY( - int unreadCount READ unreadCount NOTIFY unreadMessagesChanged) - Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged) - Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged) - Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged) - - Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob - NOTIFY eventsHistoryJobChanged) - - public: - using Timeline = std::deque<TimelineItem>; - using PendingEvents = std::vector<PendingEventItem>; - using rev_iter_t = Timeline::const_reverse_iterator; - using timeline_iter_t = Timeline::const_iterator; - - enum Change : uint { - NoChange = 0x0, - NameChange = 0x1, - CanonicalAliasChange = 0x2, - TopicChange = 0x4, - UnreadNotifsChange = 0x8, - AvatarChange = 0x10, - JoinStateChange = 0x20, - TagsChange = 0x40, - MembersChange = 0x80, - /* = 0x100, */ - AccountDataChange = 0x200, - SummaryChange = 0x400, - ReadMarkerChange = 0x800, - OtherChange = 0x8000, - AnyChange = 0xFFFF - }; - Q_DECLARE_FLAGS(Changes, Change) - Q_FLAG(Changes) - - Room(Connection* connection, QString id, JoinState initialJoinState); - ~Room() override; - - // Property accessors - - Connection* connection() const; - User* localUser() const; - const QString& id() const; - QString version() const; - bool isUnstable() const; - QString predecessorId() const; - QString successorId() const; - QString name() const; - QStringList aliases() const; - QString canonicalAlias() const; - QString displayName() const; - QString topic() const; - QString avatarMediaId() const; - QUrl avatarUrl() const; - const Avatar& avatarObject() const; - Q_INVOKABLE JoinState joinState() const; - Q_INVOKABLE QList<User*> usersTyping() const; - QList<User*> membersLeft() const; - - Q_INVOKABLE QList<User*> users() const; - QStringList memberNames() const; - [[deprecated( - "Use joinedCount(), invitedCount(), totalMemberCount()")]] int - memberCount() const; - int timelineSize() const; - bool usesEncryption() const; - int joinedCount() const; - int invitedCount() const; - int totalMemberCount() const; - - GetRoomEventsJob* eventsHistoryJob() const; - - /** - * Returns a square room avatar with the given size and requests it - * from the network if needed - * \return a pixmap with the avatar or a placeholder if there's none - * available yet - */ - Q_INVOKABLE QImage avatar(int dimension); - /** - * Returns a room avatar with the given dimensions and requests it - * from the network if needed - * \return a pixmap with the avatar or a placeholder if there's none - * available yet - */ - Q_INVOKABLE QImage avatar(int width, int height); - - /** - * \brief Get a user object for a given user id - * This is the recommended way to get a user object in a room - * context. The actual object type may be changed in further - * versions to provide room-specific user information (display name, - * avatar etc.). - * \note The method will return a valid user regardless of - * the membership. - */ - Q_INVOKABLE User* user(const QString& userId) const; - - /** - * \brief Check the join state of a given user in this room - * - * \note Banned and invited users are not tracked for now (Leave - * will be returned for them). - * - * \return either of Join, Leave, depending on the given - * user's state in the room - */ - Q_INVOKABLE JoinState memberJoinState(User* user) const; - - /** - * Get a disambiguated name for a given user in - * the context of the room - */ - Q_INVOKABLE QString roomMembername(const User* u) const; - /** - * Get a disambiguated name for a user with this id in - * the context of the room - */ - Q_INVOKABLE QString roomMembername(const QString& userId) const; - - const Timeline& messageEvents() const; - const PendingEvents& pendingEvents() const; - /** - * A convenience method returning the read marker to the position - * before the "oldest" event; same as messageEvents().crend() - */ - rev_iter_t historyEdge() const; - /** - * A convenience method returning the iterator beyond the latest - * arrived event; same as messageEvents().cend() - */ - Timeline::const_iterator syncEdge() const; - /// \deprecated Use historyEdge instead - rev_iter_t timelineEdge() const; - Q_INVOKABLE TimelineItem::index_t minTimelineIndex() const; - Q_INVOKABLE TimelineItem::index_t maxTimelineIndex() const; - Q_INVOKABLE bool - isValidIndex(TimelineItem::index_t timelineIndex) const; - - rev_iter_t findInTimeline(TimelineItem::index_t index) const; - rev_iter_t findInTimeline(const QString& evtId) const; - PendingEvents::iterator findPendingEvent(const QString& txnId); - PendingEvents::const_iterator - findPendingEvent(const QString& txnId) const; - - bool displayed() const; - /// Mark the room as currently displayed to the user - /** - * Marking the room displayed causes the room to obtain the full - * list of members if it's been lazy-loaded before; in the future - * it may do more things bound to "screen time" of the room, e.g. - * measure that "screen time". - */ - void setDisplayed(bool displayed = true); - QString firstDisplayedEventId() const; - rev_iter_t firstDisplayedMarker() const; - void setFirstDisplayedEventId(const QString& eventId); - void setFirstDisplayedEvent(TimelineItem::index_t index); - QString lastDisplayedEventId() const; - rev_iter_t lastDisplayedMarker() const; - void setLastDisplayedEventId(const QString& eventId); - void setLastDisplayedEvent(TimelineItem::index_t index); - - rev_iter_t readMarker(const User* user) const; - rev_iter_t readMarker() const; - QString readMarkerEventId() const; - QList<User*> usersAtEventId(const QString& eventId); - /** - * \brief Mark the event with uptoEventId as read - * - * Finds in the timeline and marks as read the event with - * the specified id; also posts a read receipt to the server either - * for this message or, if it's from the local user, for - * the nearest non-local message before. uptoEventId must be non-empty. - */ - void markMessagesAsRead(QString uptoEventId); - - /// Check whether there are unread messages in the room - bool hasUnreadMessages() const; - - /** Get the number of unread messages in the room - * Depending on the read marker state, this call may return either - * a precise or an estimate number of unread events. Only "notable" - * events (non-redacted message events from users other than local) - * are counted. - * - * In a case when readMarker() == timelineEdge() (the local read - * marker is beyond the local timeline) only the bottom limit of - * the unread messages number can be estimated (and even that may - * be slightly off due to, e.g., redactions of events not loaded - * to the local timeline). - * - * If all messages are read, this function will return -1 (_not_ 0, - * as zero may mean "zero or more unread messages" in a situation - * when the read marker is outside the local timeline. - */ - int unreadCount() const; - - Q_INVOKABLE int notificationCount() const; - Q_INVOKABLE void resetNotificationCount(); - Q_INVOKABLE int highlightCount() const; - Q_INVOKABLE void resetHighlightCount(); - - /** Check whether the room has account data of the given type - * Tags and read markers are not supported by this method _yet_. - */ - bool hasAccountData(const QString& type) const; - - /** Get a generic account data event of the given type - * This returns a generic hashmap for any room account data event - * stored on the server. Tags and read markers cannot be retrieved - * using this method _yet_. - */ - const EventPtr& accountData(const QString& type) const; - - QStringList tagNames() const; - TagsMap tags() const; - TagRecord tag(const QString& name) const; - - /** Add a new tag to this room - * If this room already has this tag, nothing happens. If it's a new - * tag for the room, the respective tag record is added to the set - * of tags and the new set is sent to the server to update other - * clients. - */ - void addTag(const QString& name, const TagRecord& record = {}); - Q_INVOKABLE void addTag(const QString& name, float order); - - /// Remove a tag from the room - Q_INVOKABLE void removeTag(const QString& name); - - /** Overwrite the room's tags - * This completely replaces the existing room's tags with a set - * of new ones and updates the new set on the server. Unlike - * most other methods in Room, this one sends a signal about changes - * immediately, not waiting for confirmation from the server - * (because tags are saved in account data rather than in shared - * room state). - */ - void setTags(TagsMap newTags); - - /// Check whether the list of tags has m.favourite - bool isFavourite() const; - /// Check whether the list of tags has m.lowpriority - bool isLowPriority() const; - - /// Check whether this room is a direct chat - Q_INVOKABLE bool isDirectChat() const; - - /// Get the list of users this room is a direct chat with - QList<User*> directChatUsers() const; - - Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const; - Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const; - Q_INVOKABLE QString fileNameToDownload(const QString& eventId) const; - Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; - Q_INVOKABLE QUrl fileSource(const QString& id) const; - - /** Pretty-prints plain text into HTML - * As of now, it's exactly the same as QMatrixClient::prettyPrint(); - * in the future, it will also linkify room aliases, mxids etc. - * using the room context. - */ - QString prettyPrint(const QString& plainText) const; - - MemberSorter memberSorter() const; - - Q_INVOKABLE void inviteCall(const QString& callId, const int lifetime, - const QString& sdp); - Q_INVOKABLE void sendCallCandidates(const QString& callId, - const QJsonArray& candidates); - Q_INVOKABLE void answerCall(const QString& callId, const int lifetime, - const QString& sdp); - Q_INVOKABLE void answerCall(const QString& callId, const QString& sdp); - Q_INVOKABLE void hangupCall(const QString& callId); - Q_INVOKABLE bool supportsCalls() const; - - public slots: - /** Check whether the room should be upgraded */ - void checkVersion(); - - QString postMessage(const QString& plainText, MessageEventType type); - QString postPlainText(const QString& plainText); - QString postHtmlMessage(const QString& plainText, const QString& html, - MessageEventType type = MessageEventType::Text); - QString postHtmlText(const QString& plainText, const QString& html); - QString postFile(const QString& plainText, const QUrl& localPath, - bool asGenericFile = false); - /** Post a pre-created room message event - * - * Takes ownership of the event, deleting it once the matching one - * arrives with the sync - * \return transaction id associated with the event. - */ - QString postEvent(RoomEvent* event); - QString postJson(const QString& matrixType, - const QJsonObject& eventContent); - QString retryMessage(const QString& txnId); - void discardMessage(const QString& txnId); - void setName(const QString& newName); - void setCanonicalAlias(const QString& newAlias); - void setAliases(const QStringList& aliases); - void setTopic(const QString& newTopic); - - void getPreviousContent(int limit = 10); - - void inviteToRoom(const QString& memberId); - LeaveRoomJob* leaveRoom(); - SetRoomStateWithKeyJob* - setMemberState(const QString& memberId, - const RoomMemberEvent& event) const; - void kickMember(const QString& memberId, const QString& reason = {}); - void ban(const QString& userId, const QString& reason = {}); - void unban(const QString& userId); - void redactEvent(const QString& eventId, const QString& reason = {}); - - void uploadFile(const QString& id, const QUrl& localFilename, - const QString& overrideContentType = {}); - // If localFilename is empty a temporary file is created - void downloadFile(const QString& eventId, - const QUrl& localFilename = {}); - void cancelFileTransfer(const QString& id); - - /// Mark all messages in the room as read - void markAllMessagesAsRead(); - - /// Whether the current user is allowed to upgrade the room - bool canSwitchVersions() const; - - /// Switch the room's version (aka upgrade) - void switchVersion(QString newVersion); - - signals: - /// Initial set of state events has been loaded - /** - * The initial set is what comes from the initial sync for the room. - * This includes all basic things like RoomCreateEvent, - * RoomNameEvent, a (lazy-loaded, not full) set of RoomMemberEvents - * etc. This is a per-room reflection of Connection::loadedRoomState - * \sa Connection::loadedRoomState - */ - void baseStateLoaded(); - void eventsHistoryJobChanged(); - void aboutToAddHistoricalMessages(RoomEventsRange events); - void aboutToAddNewMessages(RoomEventsRange events); - void addedMessages(int fromIndex, int toIndex); - /// The event is about to be appended to the list of pending events - void pendingEventAboutToAdd(RoomEvent* event); - /// An event has been appended to the list of pending events - void pendingEventAdded(); - /// The remote echo has arrived with the sync and will be merged - /// with its local counterpart - /** NB: Requires a sync loop to be emitted */ - void pendingEventAboutToMerge(RoomEvent* serverEvent, - int pendingEventIndex); - /// The remote and local copies of the event have been merged - /** NB: Requires a sync loop to be emitted */ - void pendingEventMerged(); - /// An event will be removed from the list of pending events - void pendingEventAboutToDiscard(int pendingEventIndex); - /// An event has just been removed from the list of pending events - void pendingEventDiscarded(); - /// The status of a pending event has changed - /** \sa PendingEventItem::deliveryStatus */ - void pendingEventChanged(int pendingEventIndex); - /// The server accepted the message - /** This is emitted when an event sending request has successfully - * completed. This does not mean that the event is already in the - * local timeline, only that the server has accepted it. - * \param txnId transaction id assigned by the client during sending - * \param eventId event id assigned by the server upon acceptance - * \sa postEvent, postPlainText, postMessage, postHtmlMessage - * \sa pendingEventMerged, aboutToAddNewMessages - */ - void messageSent(QString txnId, QString eventId); - - /** A common signal for various kinds of changes in the room - * Aside from all changes in the room state - * @param changes a set of flags describing what changes occured - * upon the last sync - * \sa StateChange - */ - void changed(Changes changes); - /** - * \brief The room name, the canonical alias or other aliases changed - * - * Not triggered when displayname changes. - */ - void namesChanged(Room* room); - void displaynameAboutToChange(Room* room); - void displaynameChanged(Room* room, QString oldName); - void topicChanged(); - void avatarChanged(); - void userAdded(User* user); - void userRemoved(User* user); - void memberAboutToRename(User* user, QString newName); - void memberRenamed(User* user); - /// The list of members has changed - /** Emitted no more than once per sync, this is a good signal to - * for cases when some action should be done upon any change in - * the member list. If you need per-item granularity you should use - * userAdded, userRemoved and memberAboutToRename / memberRenamed - * instead. - */ - void memberListChanged(); - /// The previously lazy-loaded members list is now loaded entirely - /// \sa setDisplayed - void allMembersLoaded(); - void encryption(); - - void joinStateChanged(JoinState oldState, JoinState newState); - void typingChanged(); - - void highlightCountChanged(Room* room); - void notificationCountChanged(Room* room); - - void displayedChanged(bool displayed); - void firstDisplayedEventChanged(); - void lastDisplayedEventChanged(); - void lastReadEventChanged(User* user); - void readMarkerMoved(QString fromEventId, QString toEventId); - void readMarkerForUserMoved(User* user, QString fromEventId, - QString toEventId); - void unreadMessagesChanged(Room* room); - - void accountDataAboutToChange(QString type); - void accountDataChanged(QString type); - void tagsAboutToChange(); - void tagsChanged(); - - void replacedEvent(const RoomEvent* newEvent, - const RoomEvent* oldEvent); - - void newFileTransfer(QString id, QUrl localFile); - void fileTransferProgress(QString id, qint64 progress, qint64 total); - void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl); - void fileTransferFailed(QString id, QString errorMessage = {}); - void fileTransferCancelled(QString id); - - void callEvent(Room* room, const RoomEvent* event); - - /// The room's version stability may have changed - void stabilityUpdated(QString recommendedDefault, - QStringList stableVersions); - /// This room has been upgraded and won't receive updates anymore - void upgraded(QString serverMessage, Room* successor); - /// An attempted room upgrade has failed - void upgradeFailed(QString errorMessage); - - /// The room is about to be deleted - void beforeDestruction(Room*); - - protected: - /// Returns true if any of room names/aliases has changed - virtual Changes processStateEvent(const RoomEvent& e); - virtual Changes processEphemeralEvent(EventPtr&& event); - virtual Changes processAccountDataEvent(EventPtr&& event); - virtual void onAddNewTimelineEvents(timeline_iter_t /*from*/) {} - virtual void onAddHistoricalTimelineEvents(rev_iter_t /*from*/) {} - virtual void onRedaction(const RoomEvent& /*prevEvent*/, - const RoomEvent& /*after*/) - { - } - virtual QJsonObject toJson() const; - virtual void updateData(SyncRoomData&& data, bool fromCache = false); - - private: - friend class Connection; - - class Private; - Private* d; - - // This is called from Connection, reflecting a state change that - // arrived from the server. Clients should use - // Connection::joinRoom() and Room::leaveRoom() to change the state. - void setJoinState(JoinState state); + NoChange = 0x0, + NameChange = 0x1, + CanonicalAliasChange = 0x2, + TopicChange = 0x4, + UnreadNotifsChange = 0x8, + AvatarChange = 0x10, + JoinStateChange = 0x20, + TagsChange = 0x40, + MembersChange = 0x80, + /* = 0x100, */ + AccountDataChange = 0x200, + SummaryChange = 0x400, + ReadMarkerChange = 0x800, + OtherChange = 0x8000, + AnyChange = 0xFFFF }; + Q_DECLARE_FLAGS(Changes, Change) + Q_FLAG(Changes) + + Room(Connection* connection, QString id, JoinState initialJoinState); + ~Room() override; + + // Property accessors + + Connection* connection() const; + User* localUser() const; + const QString& id() const; + QString version() const; + bool isUnstable() const; + QString predecessorId() const; + QString successorId() const; + QString name() const; + QStringList aliases() const; + QString canonicalAlias() const; + QString displayName() const; + QString topic() const; + QString avatarMediaId() const; + QUrl avatarUrl() const; + const Avatar& avatarObject() const; + Q_INVOKABLE JoinState joinState() const; + Q_INVOKABLE QList<User*> usersTyping() const; + QList<User*> membersLeft() const; + + Q_INVOKABLE QList<User*> users() const; + QStringList memberNames() const; + [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]] int + memberCount() const; + int timelineSize() const; + bool usesEncryption() const; + int joinedCount() const; + int invitedCount() const; + int totalMemberCount() const; + + GetRoomEventsJob* eventsHistoryJob() const; + + /** + * Returns a square room avatar with the given size and requests it + * from the network if needed + * \return a pixmap with the avatar or a placeholder if there's none + * available yet + */ + Q_INVOKABLE QImage avatar(int dimension); + /** + * Returns a room avatar with the given dimensions and requests it + * from the network if needed + * \return a pixmap with the avatar or a placeholder if there's none + * available yet + */ + Q_INVOKABLE QImage avatar(int width, int height); + + /** + * \brief Get a user object for a given user id + * This is the recommended way to get a user object in a room + * context. The actual object type may be changed in further + * versions to provide room-specific user information (display name, + * avatar etc.). + * \note The method will return a valid user regardless of + * the membership. + */ + Q_INVOKABLE User* user(const QString& userId) const; - class MemberSorter - { - public: - explicit MemberSorter(const Room* r) : room(r) {} + /** + * \brief Check the join state of a given user in this room + * + * \note Banned and invited users are not tracked for now (Leave + * will be returned for them). + * + * \return either of Join, Leave, depending on the given + * user's state in the room + */ + Q_INVOKABLE JoinState memberJoinState(User* user) const; - bool operator()(User* u1, User* u2) const; - bool operator()(User* u1, const QString& u2name) const; + /** + * Get a disambiguated name for a given user in + * the context of the room + */ + Q_INVOKABLE QString roomMembername(const User* u) const; + /** + * Get a disambiguated name for a user with this id in + * the context of the room + */ + Q_INVOKABLE QString roomMembername(const QString& userId) const; - template <typename ContT, typename ValT> - typename ContT::size_type lowerBoundIndex(const ContT& c, - const ValT& v) const - { - return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin(); - } + const Timeline& messageEvents() const; + const PendingEvents& pendingEvents() const; - private: - const Room* room; - }; + /// Check whether all historical messages are already loaded + /** + * \return true if the "oldest" event in the timeline is + * a room creation event and there's no further history + * to load; false otherwise + */ + bool allHistoryLoaded() const; + /** + * A convenience method returning the read marker to the position + * before the "oldest" event; same as messageEvents().crend() + */ + rev_iter_t historyEdge() const; + /** + * A convenience method returning the iterator beyond the latest + * arrived event; same as messageEvents().cend() + */ + Timeline::const_iterator syncEdge() const; + /// \deprecated Use historyEdge instead + rev_iter_t timelineEdge() const; + Q_INVOKABLE TimelineItem::index_t minTimelineIndex() const; + Q_INVOKABLE TimelineItem::index_t maxTimelineIndex() const; + Q_INVOKABLE bool isValidIndex(TimelineItem::index_t timelineIndex) const; + + rev_iter_t findInTimeline(TimelineItem::index_t index) const; + rev_iter_t findInTimeline(const QString& evtId) const; + PendingEvents::iterator findPendingEvent(const QString& txnId); + PendingEvents::const_iterator findPendingEvent(const QString& txnId) const; + + bool displayed() const; + /// Mark the room as currently displayed to the user + /** + * Marking the room displayed causes the room to obtain the full + * list of members if it's been lazy-loaded before; in the future + * it may do more things bound to "screen time" of the room, e.g. + * measure that "screen time". + */ + void setDisplayed(bool displayed = true); + QString firstDisplayedEventId() const; + rev_iter_t firstDisplayedMarker() const; + void setFirstDisplayedEventId(const QString& eventId); + void setFirstDisplayedEvent(TimelineItem::index_t index); + QString lastDisplayedEventId() const; + rev_iter_t lastDisplayedMarker() const; + void setLastDisplayedEventId(const QString& eventId); + void setLastDisplayedEvent(TimelineItem::index_t index); + + rev_iter_t readMarker(const User* user) const; + rev_iter_t readMarker() const; + QString readMarkerEventId() const; + QList<User*> usersAtEventId(const QString& eventId); + /** + * \brief Mark the event with uptoEventId as read + * + * Finds in the timeline and marks as read the event with + * the specified id; also posts a read receipt to the server either + * for this message or, if it's from the local user, for + * the nearest non-local message before. uptoEventId must be non-empty. + */ + void markMessagesAsRead(QString uptoEventId); + + /// Check whether there are unread messages in the room + bool hasUnreadMessages() const; + + /** Get the number of unread messages in the room + * Depending on the read marker state, this call may return either + * a precise or an estimate number of unread events. Only "notable" + * events (non-redacted message events from users other than local) + * are counted. + * + * In a case when readMarker() == timelineEdge() (the local read + * marker is beyond the local timeline) only the bottom limit of + * the unread messages number can be estimated (and even that may + * be slightly off due to, e.g., redactions of events not loaded + * to the local timeline). + * + * If all messages are read, this function will return -1 (_not_ 0, + * as zero may mean "zero or more unread messages" in a situation + * when the read marker is outside the local timeline. + */ + int unreadCount() const; + + Q_INVOKABLE int notificationCount() const; + Q_INVOKABLE void resetNotificationCount(); + Q_INVOKABLE int highlightCount() const; + Q_INVOKABLE void resetHighlightCount(); + + /** Check whether the room has account data of the given type + * Tags and read markers are not supported by this method _yet_. + */ + bool hasAccountData(const QString& type) const; + + /** Get a generic account data event of the given type + * This returns a generic hash map for any room account data event + * stored on the server. Tags and read markers cannot be retrieved + * using this method _yet_. + */ + const EventPtr& accountData(const QString& type) const; + + QStringList tagNames() const; + TagsMap tags() const; + TagRecord tag(const QString& name) const; + + /** Add a new tag to this room + * If this room already has this tag, nothing happens. If it's a new + * tag for the room, the respective tag record is added to the set + * of tags and the new set is sent to the server to update other + * clients. + */ + void addTag(const QString& name, const TagRecord& record = {}); + Q_INVOKABLE void addTag(const QString& name, float order); + + /// Remove a tag from the room + Q_INVOKABLE void removeTag(const QString& name); + + /** Overwrite the room's tags + * This completely replaces the existing room's tags with a set + * of new ones and updates the new set on the server. Unlike + * most other methods in Room, this one sends a signal about changes + * immediately, not waiting for confirmation from the server + * (because tags are saved in account data rather than in shared + * room state). + */ + void setTags(TagsMap newTags); + + /// Check whether the list of tags has m.favourite + bool isFavourite() const; + /// Check whether the list of tags has m.lowpriority + bool isLowPriority() const; + + /// Check whether this room is a direct chat + Q_INVOKABLE bool isDirectChat() const; + + /// Get the list of users this room is a direct chat with + QList<User*> directChatUsers() const; + + Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const; + Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const; + + /// Get a file name for downloading for a given event id + /*! + * The event MUST be RoomMessageEvent and have content + * for downloading. \sa RoomMessageEvent::hasContent + */ + Q_INVOKABLE QString fileNameToDownload(const QString& eventId) const; + + /// Get information on file upload/download + /*! + * \param id uploads are identified by the corresponding event's + * transactionId (because uploads are done before + * the event is even sent), while downloads are using + * the normal event id for identifier. + */ + Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const; + + /// Get the URL to the actual file source in a unified way + /*! + * For uploads it will return a URL to a local file; for downloads + * the URL will be taken from the corresponding room event. + */ + Q_INVOKABLE QUrl fileSource(const QString& id) const; + + /** Pretty-prints plain text into HTML + * As of now, it's exactly the same as QMatrixClient::prettyPrint(); + * in the future, it will also linkify room aliases, mxids etc. + * using the room context. + */ + Q_INVOKABLE QString prettyPrint(const QString& plainText) const; + + MemberSorter memberSorter() const; + + Q_INVOKABLE void inviteCall(const QString& callId, const int lifetime, + const QString& sdp); + Q_INVOKABLE void sendCallCandidates(const QString& callId, + const QJsonArray& candidates); + Q_INVOKABLE void answerCall(const QString& callId, const int lifetime, + const QString& sdp); + Q_INVOKABLE void answerCall(const QString& callId, const QString& sdp); + Q_INVOKABLE void hangupCall(const QString& callId); + Q_INVOKABLE bool supportsCalls() const; + +public slots: + /** Check whether the room should be upgraded */ + void checkVersion(); + + QString postMessage(const QString& plainText, MessageEventType type); + QString postPlainText(const QString& plainText); + QString postHtmlMessage(const QString& plainText, const QString& html, + MessageEventType type = MessageEventType::Text); + QString postHtmlText(const QString& plainText, const QString& html); + QString postFile(const QString& plainText, const QUrl& localPath, + bool asGenericFile = false); + /** Post a pre-created room message event + * + * Takes ownership of the event, deleting it once the matching one + * arrives with the sync + * \return transaction id associated with the event. + */ + QString postEvent(RoomEvent* event); + QString postJson(const QString& matrixType, const QJsonObject& eventContent); + QString retryMessage(const QString& txnId); + void discardMessage(const QString& txnId); + void setName(const QString& newName); + void setCanonicalAlias(const QString& newAlias); + void setAliases(const QStringList& aliases); + void setTopic(const QString& newTopic); + + /// You shouldn't normally call this method; it's here for debugging + void refreshDisplayName(); + + void getPreviousContent(int limit = 10); + + void inviteToRoom(const QString& memberId); + LeaveRoomJob* leaveRoom(); + SetRoomStateWithKeyJob* setMemberState(const QString& memberId, + const RoomMemberEvent& event) const; + void kickMember(const QString& memberId, const QString& reason = {}); + void ban(const QString& userId, const QString& reason = {}); + void unban(const QString& userId); + void redactEvent(const QString& eventId, const QString& reason = {}); + + void uploadFile(const QString& id, const QUrl& localFilename, + const QString& overrideContentType = {}); + // If localFilename is empty a temporary file is created + void downloadFile(const QString& eventId, const QUrl& localFilename = {}); + void cancelFileTransfer(const QString& id); + + /// Mark all messages in the room as read + void markAllMessagesAsRead(); + + /// Whether the current user is allowed to upgrade the room + bool canSwitchVersions() const; + + /// Switch the room's version (aka upgrade) + void switchVersion(QString newVersion); + +signals: + /// Initial set of state events has been loaded + /** + * The initial set is what comes from the initial sync for the room. + * This includes all basic things like RoomCreateEvent, + * RoomNameEvent, a (lazy-loaded, not full) set of RoomMemberEvents + * etc. This is a per-room reflection of Connection::loadedRoomState + * \sa Connection::loadedRoomState + */ + void baseStateLoaded(); + void eventsHistoryJobChanged(); + void aboutToAddHistoricalMessages(RoomEventsRange events); + void aboutToAddNewMessages(RoomEventsRange events); + void addedMessages(int fromIndex, int toIndex); + /// The event is about to be appended to the list of pending events + void pendingEventAboutToAdd(RoomEvent* event); + /// An event has been appended to the list of pending events + void pendingEventAdded(); + /// The remote echo has arrived with the sync and will be merged + /// with its local counterpart + /** NB: Requires a sync loop to be emitted */ + void pendingEventAboutToMerge(RoomEvent* serverEvent, int pendingEventIndex); + /// The remote and local copies of the event have been merged + /** NB: Requires a sync loop to be emitted */ + void pendingEventMerged(); + /// An event will be removed from the list of pending events + void pendingEventAboutToDiscard(int pendingEventIndex); + /// An event has just been removed from the list of pending events + void pendingEventDiscarded(); + /// The status of a pending event has changed + /** \sa PendingEventItem::deliveryStatus */ + void pendingEventChanged(int pendingEventIndex); + /// The server accepted the message + /** This is emitted when an event sending request has successfully + * completed. This does not mean that the event is already in the + * local timeline, only that the server has accepted it. + * \param txnId transaction id assigned by the client during sending + * \param eventId event id assigned by the server upon acceptance + * \sa postEvent, postPlainText, postMessage, postHtmlMessage + * \sa pendingEventMerged, aboutToAddNewMessages + */ + void messageSent(QString txnId, QString eventId); + + /** A common signal for various kinds of changes in the room + * Aside from all changes in the room state + * @param changes a set of flags describing what changes occurred + * upon the last sync + * \sa StateChange + */ + void changed(Changes changes); + /** + * \brief The room name, the canonical alias or other aliases changed + * + * Not triggered when display name changes. + */ + void namesChanged(Room* room); + void displaynameAboutToChange(Room* room); + void displaynameChanged(Room* room, QString oldName); + void topicChanged(); + void avatarChanged(); + void userAdded(User* user); + void userRemoved(User* user); + void memberAboutToRename(User* user, QString newName); + void memberRenamed(User* user); + /// The list of members has changed + /** Emitted no more than once per sync, this is a good signal to + * for cases when some action should be done upon any change in + * the member list. If you need per-item granularity you should use + * userAdded, userRemoved and memberAboutToRename / memberRenamed + * instead. + */ + void memberListChanged(); + /// The previously lazy-loaded members list is now loaded entirely + /// \sa setDisplayed + void allMembersLoaded(); + void encryption(); + + void joinStateChanged(JoinState oldState, JoinState newState); + void typingChanged(); + + void highlightCountChanged(); + void notificationCountChanged(); + + void displayedChanged(bool displayed); + void firstDisplayedEventChanged(); + void lastDisplayedEventChanged(); + void lastReadEventChanged(User* user); + void readMarkerMoved(QString fromEventId, QString toEventId); + void readMarkerForUserMoved(User* user, QString fromEventId, + QString toEventId); + void unreadMessagesChanged(Room* room); + + void accountDataAboutToChange(QString type); + void accountDataChanged(QString type); + void tagsAboutToChange(); + void tagsChanged(); + + void replacedEvent(const RoomEvent* newEvent, const RoomEvent* oldEvent); + + void newFileTransfer(QString id, QUrl localFile); + void fileTransferProgress(QString id, qint64 progress, qint64 total); + void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl); + void fileTransferFailed(QString id, QString errorMessage = {}); + void fileTransferCancelled(QString id); + + void callEvent(Room* room, const RoomEvent* event); + + /// The room's version stability may have changed + void stabilityUpdated(QString recommendedDefault, + QStringList stableVersions); + /// This room has been upgraded and won't receive updates any more + void upgraded(QString serverMessage, Room* successor); + /// An attempted room upgrade has failed + void upgradeFailed(QString errorMessage); + + /// The room is about to be deleted + void beforeDestruction(Room*); + +protected: + /// Returns true if any of room names/aliases has changed + virtual Changes processStateEvent(const RoomEvent& e); + virtual Changes processEphemeralEvent(EventPtr&& event); + virtual Changes processAccountDataEvent(EventPtr&& event); + virtual void onAddNewTimelineEvents(timeline_iter_t /*from*/) {} + virtual void onAddHistoricalTimelineEvents(rev_iter_t /*from*/) {} + virtual void onRedaction(const RoomEvent& /*prevEvent*/, + const RoomEvent& /*after*/) + {} + virtual QJsonObject toJson() const; + virtual void updateData(SyncRoomData&& data, bool fromCache = false); + +private: + friend class Connection; + + class Private; + Private* d; + + // This is called from Connection, reflecting a state change that + // arrived from the server. Clients should use + // Connection::joinRoom() and Room::leaveRoom() to change the state. + void setJoinState(JoinState state); +}; + +class MemberSorter +{ +public: + explicit MemberSorter(const Room* r) + : room(r) + {} + + bool operator()(User* u1, User* u2) const; + bool operator()(User* u1, const QString& u2name) const; + + template <typename ContT, typename ValT> + typename ContT::size_type lowerBoundIndex(const ContT& c, const ValT& v) const + { + return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin(); + } + +private: + const Room* room; +}; } // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::FileTransferInfo) Q_DECLARE_OPERATORS_FOR_FLAGS(QMatrixClient::Room::Changes) diff --git a/lib/settings.cpp b/lib/settings.cpp index e0a1783f..f8f1eae5 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -32,8 +32,7 @@ QVariant Settings::value(const QString& key, const QVariant& defaultValue) const // (QVariant("false") == true in JavaScript). Since we have a mixed // environment where both QSettings and Qt.labs.Settings may potentially // work with same settings, better ensure compatibility. - return value.toString() == QStringLiteral("false") ? QVariant(false) - : value; + return value.toString() == QStringLiteral("false") ? QVariant(false) : value; } bool Settings::contains(const QString& key) const @@ -83,40 +82,44 @@ void SettingsGroup::remove(const QString& key) Settings::remove(fullKey); } -QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", "", +QMC_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {}, setDeviceId) -QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", "", +QMC_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {}, setDeviceName) QMC_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false, setKeepLoggedIn) +static const auto HomeserverKey = QStringLiteral("homeserver"); +static const auto AccessTokenKey = QStringLiteral("access_token"); + QUrl AccountSettings::homeserver() const { - return QUrl::fromUserInput(value("homeserver").toString()); + return QUrl::fromUserInput(value(HomeserverKey).toString()); } void AccountSettings::setHomeserver(const QUrl& url) { - setValue("homeserver", url.toString()); + setValue(HomeserverKey, url.toString()); } QString AccountSettings::userId() const { return group().section('/', -1); } QString AccountSettings::accessToken() const { - return value("access_token").toString(); + return value(AccessTokenKey).toString(); } void AccountSettings::setAccessToken(const QString& accessToken) { qCWarning(MAIN) << "Saving access_token to QSettings is insecure." " Developers, please save access_token separately."; - setValue("access_token", accessToken); + setValue(AccessTokenKey, accessToken); } void AccountSettings::clearAccessToken() { - legacySettings.remove("access_token"); - legacySettings.remove("device_id"); // Force the server to re-issue it - remove("access_token"); + legacySettings.remove(AccessTokenKey); + legacySettings.remove(QStringLiteral("device_id")); // Force the server to + // re-issue it + remove(AccessTokenKey); } diff --git a/lib/settings.h b/lib/settings.h index 0cde47a3..cb09c479 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -24,131 +24,128 @@ class QVariant; -namespace QMatrixClient { - class Settings : public QSettings - { - Q_OBJECT - public: - /** - * Use this function before creating any Settings objects in order - * to setup a read-only location where configuration has previously - * been stored. This will provide an additional fallback in case of - * renaming the organisation/application. - */ - static void setLegacyNames(const QString& organizationName, - const QString& applicationName = {}); +namespace QMatrixClient +{ +class Settings : public QSettings +{ + Q_OBJECT +public: + /** + * Use this function before creating any Settings objects in order + * to setup a read-only location where configuration has previously + * been stored. This will provide an additional fallback in case of + * renaming the organisation/application. + */ + static void setLegacyNames(const QString& organizationName, + const QString& applicationName = {}); #if defined(_MSC_VER) && _MSC_VER < 1900 - // VS 2013 (and probably older) aren't friends with 'using' statements - // that involve private constructors - explicit Settings(QObject* parent = 0) : QSettings(parent) {} + // VS 2013 (and probably older) aren't friends with 'using' statements + // that involve private constructors + explicit Settings(QObject* parent = 0) + : QSettings(parent) + {} #else - using QSettings::QSettings; + using QSettings::QSettings; #endif - Q_INVOKABLE void setValue(const QString& key, const QVariant& value); - Q_INVOKABLE QVariant value(const QString& key, - const QVariant& defaultValue = {}) const; + Q_INVOKABLE void setValue(const QString& key, const QVariant& value); + Q_INVOKABLE QVariant value(const QString& key, + const QVariant& defaultValue = {}) const; + + template <typename T> + T get(const QString& key, const T& defaultValue = {}) const + { + const auto qv = value(key, QVariant()); + return qv.isValid() && qv.canConvert<T>() ? qv.value<T>() : defaultValue; + } + + Q_INVOKABLE bool contains(const QString& key) const; + Q_INVOKABLE QStringList childGroups() const; - template <typename T> - T get(const QString& key, const T& defaultValue = {}) const - { - const auto qv = value(key, QVariant()); - return qv.isValid() && qv.canConvert<T>() ? qv.value<T>() - : defaultValue; - } +private: + static QString legacyOrganizationName; + static QString legacyApplicationName; - Q_INVOKABLE bool contains(const QString& key) const; - Q_INVOKABLE QStringList childGroups() const; +protected: + QSettings legacySettings { legacyOrganizationName, legacyApplicationName }; +}; - private: - static QString legacyOrganizationName; - static QString legacyApplicationName; +class SettingsGroup : public Settings +{ +public: + template <typename... ArgTs> + explicit SettingsGroup(QString path, ArgTs&&... qsettingsArgs) + : Settings(std::forward<ArgTs>(qsettingsArgs)...) + , groupPath(std::move(path)) + {} - protected: - QSettings legacySettings { legacyOrganizationName, - legacyApplicationName }; - }; + Q_INVOKABLE bool contains(const QString& key) const; + Q_INVOKABLE QVariant value(const QString& key, + const QVariant& defaultValue = {}) const; - class SettingsGroup : public Settings + template <typename T> + T get(const QString& key, const T& defaultValue = {}) const { - public: - template <typename... ArgTs> - explicit SettingsGroup(QString path, ArgTs&&... qsettingsArgs) - : Settings(std::forward<ArgTs>(qsettingsArgs)...), - groupPath(std::move(path)) - { - } - - Q_INVOKABLE bool contains(const QString& key) const; - Q_INVOKABLE QVariant value(const QString& key, - const QVariant& defaultValue = {}) const; - - template <typename T> - T get(const QString& key, const T& defaultValue = {}) const - { - const auto qv = value(key, QVariant()); - return qv.isValid() && qv.canConvert<T>() ? qv.value<T>() - : defaultValue; - } - - Q_INVOKABLE QString group() const; - Q_INVOKABLE QStringList childGroups() const; - Q_INVOKABLE void setValue(const QString& key, const QVariant& value); - - Q_INVOKABLE void remove(const QString& key); - - private: - QString groupPath; - }; - -#define QMC_DECLARE_SETTING(type, propname, setter) \ - Q_PROPERTY(type propname READ propname WRITE setter) \ - public: \ - type propname() const; \ - void setter(type newValue); \ - \ - private: - -#define QMC_DEFINE_SETTING(classname, type, propname, qsettingname, \ - defaultValue, setter) \ - type classname::propname() const \ - { \ - return get<type>(QStringLiteral(qsettingname), defaultValue); \ - } \ - \ - void classname::setter(type newValue) \ - { \ - setValue(QStringLiteral(qsettingname), newValue); \ + const auto qv = value(key, QVariant()); + return qv.isValid() && qv.canConvert<T>() ? qv.value<T>() : defaultValue; } - class AccountSettings : public SettingsGroup - { - Q_OBJECT - Q_PROPERTY(QString userId READ userId CONSTANT) - QMC_DECLARE_SETTING(QString, deviceId, setDeviceId) - QMC_DECLARE_SETTING(QString, deviceName, setDeviceName) - QMC_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) - /** \deprecated \sa setAccessToken */ - Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) - public: - template <typename... ArgTs> - explicit AccountSettings(const QString& accountId, - ArgTs... qsettingsArgs) - : SettingsGroup("Accounts/" + accountId, qsettingsArgs...) - { - } - - QString userId() const; - - QUrl homeserver() const; - void setHomeserver(const QUrl& url); - - /** \deprecated \sa setToken */ - QString accessToken() const; - /** \deprecated Storing accessToken in QSettings is unsafe, - * see QMatrixClient/Quaternion#181 */ - void setAccessToken(const QString& accessToken); - Q_INVOKABLE void clearAccessToken(); - }; + Q_INVOKABLE QString group() const; + Q_INVOKABLE QStringList childGroups() const; + Q_INVOKABLE void setValue(const QString& key, const QVariant& value); + + Q_INVOKABLE void remove(const QString& key); + +private: + QString groupPath; +}; + +#define QMC_DECLARE_SETTING(type, propname, setter) \ + Q_PROPERTY(type propname READ propname WRITE setter) \ +public: \ + type propname() const; \ + void setter(type newValue); \ + \ +private: + +#define QMC_DEFINE_SETTING(classname, type, propname, qsettingname, \ + defaultValue, setter) \ + type classname::propname() const \ + { \ + return get<type>(QStringLiteral(qsettingname), defaultValue); \ + } \ + \ + void classname::setter(type newValue) \ + { \ + setValue(QStringLiteral(qsettingname), std::move(newValue)); \ + } + +class AccountSettings : public SettingsGroup +{ + Q_OBJECT + Q_PROPERTY(QString userId READ userId CONSTANT) + QMC_DECLARE_SETTING(QString, deviceId, setDeviceId) + QMC_DECLARE_SETTING(QString, deviceName, setDeviceName) + QMC_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn) + /** \deprecated \sa setAccessToken */ + Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) +public: + template <typename... ArgTs> + explicit AccountSettings(const QString& accountId, ArgTs... qsettingsArgs) + : SettingsGroup("Accounts/" + accountId, qsettingsArgs...) + {} + + QString userId() const; + + QUrl homeserver() const; + void setHomeserver(const QUrl& url); + + /** \deprecated \sa setToken */ + QString accessToken() const; + /** \deprecated Storing accessToken in QSettings is unsafe, + * see QMatrixClient/Quaternion#181 */ + void setAccessToken(const QString& accessToken); + Q_INVOKABLE void clearAccessToken(); +}; } // namespace QMatrixClient diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp index d8fb7bb6..0c39b438 100644 --- a/lib/syncdata.cpp +++ b/lib/syncdata.cpp @@ -26,20 +26,20 @@ using namespace QMatrixClient; const QString SyncRoomData::UnreadCountKey = - QStringLiteral("x-qmatrixclient.unread_count"); + QStringLiteral("x-qmatrixclient.unread_count"); bool RoomSummary::isEmpty() const { return joinedMemberCount.omitted() && invitedMemberCount.omitted() - && heroes.omitted(); + && heroes.omitted(); } bool RoomSummary::merge(const RoomSummary& other) { // Using bitwise OR to prevent computation shortcut. return joinedMemberCount.merge(other.joinedMemberCount) - | invitedMemberCount.merge(other.invitedMemberCount) - | heroes.merge(other.heroes); + | invitedMemberCount.merge(other.invitedMemberCount) + | heroes.merge(other.heroes); } QDebug QMatrixClient::operator<<(QDebug dbg, const RoomSummary& rs) @@ -71,24 +71,23 @@ void JsonObjectConverter<RoomSummary>::fillFrom(const QJsonObject& jo, { fromJson(jo["m.joined_member_count"_ls], rs.joinedMemberCount); fromJson(jo["m.invited_member_count"_ls], rs.invitedMemberCount); - fromJson(jo["m.heroes"], rs.heroes); + fromJson(jo["m.heroes"_ls], rs.heroes); } template <typename EventsArrayT, typename StrT> inline EventsArrayT load(const QJsonObject& batches, StrT keyName) { - return fromJson<EventsArrayT>( - batches[keyName].toObject().value("events"_ls)); + return fromJson<EventsArrayT>(batches[keyName].toObject().value("events"_ls)); } SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, const QJsonObject& room_) - : roomId(roomId_), - joinState(joinState_), - summary(fromJson<RoomSummary>(room_["summary"])), - state(load<StateEvents>(room_, - joinState == JoinState::Invite ? "invite_state"_ls - : "state"_ls)) + : roomId(roomId_) + , joinState(joinState_) + , summary(fromJson<RoomSummary>(room_["summary"_ls])) + , state(load<StateEvents>(room_, joinState == JoinState::Invite + ? "invite_state"_ls + : "state"_ls)) { switch (joinState) { case JoinState::Join: @@ -122,7 +121,7 @@ SyncData::SyncData(const QString& cacheFileName) auto json = loadJson(cacheFileName); auto requiredVersion = std::get<0>(cacheVersion()); auto actualVersion = - json.value("cache_version").toObject().value("major").toInt(); + json.value("cache_version"_ls).toObject().value("major"_ls).toInt(); if (actualVersion == requiredVersion) parseJson(json, cacheFileInfo.absolutePath() + '/'); else @@ -159,10 +158,10 @@ QJsonObject SyncData::loadJson(const QString& fileName) } auto data = roomFile.readAll(); - const auto json = - (data.startsWith('{') ? QJsonDocument::fromJson(data) - : QJsonDocument::fromBinaryData(data)) - .object(); + const auto json = (data.startsWith('{') + ? QJsonDocument::fromJson(data) + : QJsonDocument::fromBinaryData(data)) + .object(); if (json.isEmpty()) { qCWarning(MAIN) << "State cache in" << fileName << "is broken or empty, discarding"; @@ -189,7 +188,8 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) // We have a Qt container on the right and an STL one on the left roomData.reserve(static_cast<size_t>(rs.size())); for (auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt) { - auto roomJson = roomIt->isObject() + auto roomJson = + roomIt->isObject() ? roomIt->toObject() : loadJson(baseDir + fileNameForRoom(roomIt.key())); if (roomJson.isEmpty()) { @@ -199,7 +199,7 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir) roomData.emplace_back(roomIt.key(), JoinState(ii), roomJson); const auto& r = roomData.back(); totalEvents += r.state.size() + r.ephemeral.size() - + r.accountData.size() + r.timeline.size(); + + r.accountData.size() + r.timeline.size(); } totalRooms += rs.size(); } diff --git a/lib/syncdata.h b/lib/syncdata.h index 139af130..49df8db6 100644 --- a/lib/syncdata.h +++ b/lib/syncdata.h @@ -18,97 +18,102 @@ #pragma once -#include "events/stateevent.h" #include "joinstate.h" -namespace QMatrixClient { - /// Room summary, as defined in MSC688 - /** - * Every member of this structure is an Omittable; as per the MSC, only - * changed values are sent from the server so if nothing is in the payload - * the respective member will be omitted. In particular, `heroes.omitted()` - * means that nothing has come from the server; heroes.value().isEmpty() - * means a peculiar case of a room with the only member - the current user. +#include "events/stateevent.h" + +namespace QMatrixClient +{ +/// Room summary, as defined in MSC688 +/** + * Every member of this structure is an Omittable; as per the MSC, only + * changed values are sent from the server so if nothing is in the payload + * the respective member will be omitted. In particular, `heroes.omitted()` + * means that nothing has come from the server; heroes.value().isEmpty() + * means a peculiar case of a room with the only member - the current user. + */ +struct RoomSummary +{ + Omittable<int> joinedMemberCount; + Omittable<int> invitedMemberCount; + Omittable<QStringList> heroes; //< mxids of users to take part in the room + //name + + bool isEmpty() const; + /// Merge the contents of another RoomSummary object into this one + /// \return true, if the current object has changed; false otherwise + bool merge(const RoomSummary& other); + + friend QDebug operator<<(QDebug dbg, const RoomSummary& rs); +}; + +template <> +struct JsonObjectConverter<RoomSummary> +{ + static void dumpTo(QJsonObject& jo, const RoomSummary& rs); + static void fillFrom(const QJsonObject& jo, RoomSummary& rs); +}; + +class SyncRoomData +{ +public: + QString roomId; + JoinState joinState; + RoomSummary summary; + StateEvents state; + RoomEvents timeline; + Events ephemeral; + Events accountData; + + bool timelineLimited; + QString timelinePrevBatch; + int unreadCount; + int highlightCount; + int notificationCount; + + SyncRoomData(const QString& roomId, JoinState joinState_, + const QJsonObject& room_); + SyncRoomData(SyncRoomData&&) = default; + SyncRoomData& operator=(SyncRoomData&&) = default; + + static const QString UnreadCountKey; +}; + +// QVector cannot work with non-copiable objects, std::vector can. +using SyncDataList = std::vector<SyncRoomData>; + +class SyncData +{ +public: + SyncData() = default; + explicit SyncData(const QString& cacheFileName); + /** Parse sync response into room events + * \param json response from /sync or a room state cache + * \return the list of rooms with missing cache files; always + * empty when parsing response from /sync */ - struct RoomSummary { - Omittable<int> joinedMemberCount; - Omittable<int> invitedMemberCount; - Omittable<QStringList> - heroes; //< mxids of users to take part in the room name - - bool isEmpty() const; - /// Merge the contents of another RoomSummary object into this one - /// \return true, if the current object has changed; false otherwise - bool merge(const RoomSummary& other); - - friend QDebug operator<<(QDebug dbg, const RoomSummary& rs); - }; - - template <> struct JsonObjectConverter<RoomSummary> { - static void dumpTo(QJsonObject& jo, const RoomSummary& rs); - static void fillFrom(const QJsonObject& jo, RoomSummary& rs); - }; - - class SyncRoomData - { - public: - QString roomId; - JoinState joinState; - RoomSummary summary; - StateEvents state; - RoomEvents timeline; - Events ephemeral; - Events accountData; - - bool timelineLimited; - QString timelinePrevBatch; - int unreadCount; - int highlightCount; - int notificationCount; - - SyncRoomData(const QString& roomId, JoinState joinState_, - const QJsonObject& room_); - SyncRoomData(SyncRoomData&&) = default; - SyncRoomData& operator=(SyncRoomData&&) = default; - - static const QString UnreadCountKey; - }; - - // QVector cannot work with non-copiable objects, std::vector can. - using SyncDataList = std::vector<SyncRoomData>; - - class SyncData - { - public: - SyncData() = default; - explicit SyncData(const QString& cacheFileName); - /** Parse sync response into room events - * \param json response from /sync or a room state cache - * \return the list of rooms with missing cache files; always - * empty when parsing response from /sync - */ - void parseJson(const QJsonObject& json, const QString& baseDir = {}); - - Events&& takePresenceData(); - Events&& takeAccountData(); - Events&& takeToDeviceEvents(); - SyncDataList&& takeRoomData(); - - QString nextBatch() const { return nextBatch_; } - - QStringList unresolvedRooms() const { return unresolvedRoomIds; } - - static std::pair<int, int> cacheVersion() { return { 10, 0 }; } - static QString fileNameForRoom(QString roomId); - - private: - QString nextBatch_; - Events presenceData; - Events accountData; - Events toDeviceEvents; - SyncDataList roomData; - QStringList unresolvedRoomIds; - - static QJsonObject loadJson(const QString& fileName); - }; + void parseJson(const QJsonObject& json, const QString& baseDir = {}); + + Events&& takePresenceData(); + Events&& takeAccountData(); + Events&& takeToDeviceEvents(); + SyncDataList&& takeRoomData(); + + QString nextBatch() const { return nextBatch_; } + + QStringList unresolvedRooms() const { return unresolvedRoomIds; } + + static std::pair<int, int> cacheVersion() { return { 10, 0 }; } + static QString fileNameForRoom(QString roomId); + +private: + QString nextBatch_; + Events presenceData; + Events accountData; + Events toDeviceEvents; + SyncDataList roomData; + QStringList unresolvedRoomIds; + + static QJsonObject loadJson(const QString& fileName); +}; } // namespace QMatrixClient diff --git a/lib/user.cpp b/lib/user.cpp index 43792e74..8adb1dae 100644 --- a/lib/user.cpp +++ b/lib/user.cpp @@ -20,18 +20,22 @@ #include "avatar.h" #include "connection.h" +#include "room.h" + #include "csapi/content-repo.h" #include "csapi/profile.h" #include "csapi/room_state.h" + #include "events/event.h" #include "events/roommemberevent.h" -#include "room.h" +#include <QtCore/QCryptographicHash> #include <QtCore/QElapsedTimer> #include <QtCore/QPointer> #include <QtCore/QRegularExpression> #include <QtCore/QStringBuilder> #include <QtCore/QTimer> +#include <QtCore/QtEndian> #include <functional> @@ -41,23 +45,39 @@ using std::move; class User::Private { - public: +public: static Avatar makeAvatar(QUrl url) { return Avatar(move(url)); } - Private(QString userId, Connection* connection) - : userId(move(userId)), connection(connection) + qreal makeHueF() { + Q_ASSERT(!userId.isEmpty()); + QByteArray hash = QCryptographicHash::hash(userId.toUtf8(), + QCryptographicHash::Sha1); + QDataStream dataStream(qToLittleEndian(hash).left(2)); + dataStream.setByteOrder(QDataStream::LittleEndian); + quint16 hashValue; + dataStream >> hashValue; + const auto hueF = qreal(hashValue) / std::numeric_limits<quint16>::max(); + Q_ASSERT((0 <= hueF) && (hueF <= 1)); + return hueF; } + Private(QString userId, Connection* connection) + : userId(move(userId)) + , connection(connection) + , hueF(makeHueF()) + {} + QString userId; Connection* connection; QString bridged; QString mostUsedName; QMultiHash<QString, const Room*> otherNames; + qreal hueF; Avatar mostUsedAvatar { makeAvatar({}) }; std::vector<Avatar> otherAvatars; - auto otherAvatar(QUrl url) + auto otherAvatar(const QUrl& url) { return std::find_if(otherAvatars.begin(), otherAvatars.end(), [&url](const auto& av) { return av.url() == url; }); @@ -67,10 +87,9 @@ class User::Private mutable int totalRooms = 0; QString nameForRoom(const Room* r, const QString& hint = {}) const; - void setNameForRoom(const Room* r, QString newName, QString oldName); + void setNameForRoom(const Room* r, QString newName, const QString& oldName); QUrl avatarUrlForRoom(const Room* r, const QUrl& hint = {}) const; - void setAvatarForRoom(const Room* r, const QUrl& newUrl, - const QUrl& oldUrl); + void setAvatarForRoom(const Room* r, const QUrl& newUrl, const QUrl& oldUrl); void setAvatarOnServer(QString contentUri, User* q); }; @@ -78,7 +97,7 @@ class User::Private QString User::Private::nameForRoom(const Room* r, const QString& hint) const { // If the hint is accurate, this function is O(1) instead of O(n) - if (hint == mostUsedName || otherNames.contains(hint, r)) + if (!hint.isNull() && (hint == mostUsedName || otherNames.contains(hint, r))) return hint; return otherNames.key(r, mostUsedName); } @@ -86,7 +105,7 @@ QString User::Private::nameForRoom(const Room* r, const QString& hint) const static constexpr int MIN_JOINED_ROOMS_TO_LOG = 20; void User::Private::setNameForRoom(const Room* r, QString newName, - QString oldName) + const QString& oldName) { Q_ASSERT(oldName != newName); Q_ASSERT(oldName == mostUsedName || otherNames.contains(oldName, r)); @@ -103,14 +122,14 @@ void User::Private::setNameForRoom(const Room* r, QString newName, Q_ASSERT(totalRooms > 1); QElapsedTimer et; if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) { - qCDebug(MAIN) - << "Switching the most used name of user" << userId - << "from" << mostUsedName << "to" << newName; + qCDebug(MAIN) << "Switching the most used name of user" << userId + << "from" << mostUsedName << "to" << newName; qCDebug(MAIN) << "The user is in" << totalRooms << "rooms"; et.start(); } - for (auto* r1 : connection->roomMap()) + const auto& roomMap = connection->roomMap(); + for (auto* r1 : roomMap) if (nameForRoom(r1) == mostUsedName) otherNames.insert(mostUsedName, r1); @@ -152,21 +171,21 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl, } if (newUrl != mostUsedAvatar.url()) { // Check if the new avatar is about to become most used. - if (avatarsToRooms.count(newUrl) - >= totalRooms - avatarsToRooms.size()) { + if (avatarsToRooms.count(newUrl) >= totalRooms - avatarsToRooms.size()) { QElapsedTimer et; if (totalRooms > MIN_JOINED_ROOMS_TO_LOG) { qCDebug(MAIN) - << "Switching the most used avatar of user" << userId - << "from" << mostUsedAvatar.url().toDisplayString() - << "to" << newUrl.toDisplayString(); + << "Switching the most used avatar of user" << userId + << "from" << mostUsedAvatar.url().toDisplayString() << "to" + << newUrl.toDisplayString(); et.start(); } avatarsToRooms.remove(newUrl); auto nextMostUsedIt = otherAvatar(newUrl); Q_ASSERT(nextMostUsedIt != otherAvatars.end()); std::swap(mostUsedAvatar, *nextMostUsedIt); - for (const auto* r1 : connection->roomMap()) + const auto& roomMap = connection->roomMap(); + for (const auto* r1 : roomMap) if (avatarUrlForRoom(r1) == nextMostUsedIt->url()) avatarsToRooms.insert(nextMostUsedIt->url(), r1); @@ -181,7 +200,8 @@ void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl, } User::User(QString userId, Connection* connection) - : QObject(connection), d(new Private(move(userId), connection)) + : QObject(connection) + , d(new Private(move(userId), connection)) { setObjectName(userId); } @@ -205,6 +225,8 @@ bool User::isGuest() const return *it == ':'; } +int User::hue() const { return int(hueF() * 359); } + QString User::name(const Room* room) const { return d->nameForRoom(room); } QString User::rawName(const Room* room) const @@ -245,8 +267,9 @@ void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl, void User::rename(const QString& newName) { - auto job = connection()->callApi<SetDisplayNameJob>(id(), newName); - connect(job, &BaseJob::success, this, [=] { updateName(newName); }); + const auto actualNewName = sanitized(newName); + connect(connection()->callApi<SetDisplayNameJob>(id(), actualNewName), + &BaseJob::success, this, [=] { updateName(actualNewName); }); } void User::rename(const QString& newName, const Room* r) @@ -259,24 +282,25 @@ void User::rename(const QString& newName, const Room* r) } Q_ASSERT_X(r->memberJoinState(this) == JoinState::Join, __FUNCTION__, "Attempt to rename a user that's not a room member"); + const auto actualNewName = sanitized(newName); MemberEventContent evtC; - evtC.displayName = newName; - auto job = r->setMemberState(id(), RoomMemberEvent(move(evtC))); - connect(job, &BaseJob::success, this, [=] { updateName(newName, r); }); + evtC.displayName = actualNewName; + connect(r->setMemberState(id(), RoomMemberEvent(move(evtC))), + &BaseJob::success, this, [=] { updateName(actualNewName, r); }); } bool User::setAvatar(const QString& fileName) { - return avatarObject().upload( - connection(), fileName, - std::bind(&Private::setAvatarOnServer, d.data(), _1, this)); + return avatarObject().upload(connection(), fileName, + std::bind(&Private::setAvatarOnServer, + d.data(), _1, this)); } bool User::setAvatar(QIODevice* source) { - return avatarObject().upload( - connection(), source, - std::bind(&Private::setAvatarOnServer, d.data(), _1, this)); + return avatarObject().upload(connection(), source, + std::bind(&Private::setAvatarOnServer, + d.data(), _1, this)); } void User::requestDirectChat() { connection()->requestDirectChat(this); } @@ -346,19 +370,18 @@ QUrl User::avatarUrl(const Room* room) const return avatarObject(room).url(); } -void User::processEvent(const RoomMemberEvent& event, const Room* room) +void User::processEvent(const RoomMemberEvent& event, const Room* room, + bool firstMention) { Q_ASSERT(room); + + if (firstMention) + ++d->totalRooms; + if (event.membership() != MembershipType::Invite && event.membership() != MembershipType::Join) return; - auto aboutToEnter = room->memberJoinState(this) == JoinState::Leave - && (event.membership() == MembershipType::Join - || event.membership() == MembershipType::Invite); - if (aboutToEnter) - ++d->totalRooms; - auto newName = event.displayName(); // `bridged` value uses the same notification signal as the name; // it is assumed that first setting of the bridge occurs together with @@ -366,29 +389,31 @@ void User::processEvent(const RoomMemberEvent& event, const Room* room) // exceptionally rare (the only reasonable case being that the bridge // changes the naming convention). For the same reason room-specific // bridge tags are not supported at all. - QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$"); + QRegularExpression reSuffix( + QStringLiteral(" \\((IRC|Gitter|Telegram)\\)$")); auto match = reSuffix.match(newName); if (match.hasMatch()) { if (d->bridged != match.captured(1)) { if (!d->bridged.isEmpty()) qCWarning(MAIN) - << "Bridge for user" << id() << "changed:" << d->bridged - << "->" << match.captured(1); + << "Bridge for user" << id() << "changed:" << d->bridged + << "->" << match.captured(1); d->bridged = match.captured(1); } newName.truncate(match.capturedStart(0)); } if (event.prevContent()) { // FIXME: the hint doesn't work for bridged users - auto oldNameHint = - d->nameForRoom(room, event.prevContent()->displayName); + auto oldNameHint = d->nameForRoom(room, + event.prevContent()->displayName); updateName(newName, oldNameHint, room); - updateAvatarUrl( - event.avatarUrl(), - d->avatarUrlForRoom(room, event.prevContent()->avatarUrl), - room); + updateAvatarUrl(event.avatarUrl(), + d->avatarUrlForRoom(room, event.prevContent()->avatarUrl), + room); } else { updateName(newName, room); updateAvatarUrl(event.avatarUrl(), d->avatarUrlForRoom(room), room); } } + +qreal User::hueF() const { return d->hueF; } @@ -19,135 +19,148 @@ #pragma once #include "avatar.h" + #include <QtCore/QObject> #include <QtCore/QString> -namespace QMatrixClient { - class Connection; - class Room; - class RoomMemberEvent; - - class User : public QObject - { - Q_OBJECT - Q_PROPERTY(QString id READ id CONSTANT) - Q_PROPERTY(bool isGuest READ isGuest CONSTANT) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) - Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged - STORED false) - Q_PROPERTY( - QString fullName READ fullName NOTIFY nameChanged STORED false) - Q_PROPERTY( - QString bridgeName READ bridged NOTIFY nameChanged STORED false) - Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged - STORED false) - Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) - public: - User(QString userId, Connection* connection); - ~User() override; - - Connection* connection() const; - - /** Get unique stable user id - * User id is generated by the server and is not changed ever. - */ - QString id() const; - - /** Get the name chosen by the user - * This may be empty if the user didn't choose the name or cleared - * it. If the user is bridged, the bridge postfix (such as '(IRC)') - * is stripped out. No disambiguation for the room is done. - * \sa displayName, rawName - */ - QString name(const Room* room = nullptr) const; - - /** Get the user name along with the bridge postfix - * This function is similar to name() but appends the bridge postfix - * (such as '(IRC)') to the user name. No disambiguation is done. - * \sa name, displayName - */ - QString rawName(const Room* room = nullptr) const; - - /** Get the displayed user name - * When \p room is null, this method returns result of name() if - * the name is non-empty; otherwise it returns user id. - * When \p room is non-null, this call is equivalent to - * Room::roomMembername invocation for the user (i.e. the user's - * disambiguated room-specific name is returned). - * \sa name, id, fullName, Room::roomMembername - */ - QString displayname(const Room* room = nullptr) const; - - /** Get user name and id in one string - * The constructed string follows the format 'name (id)' - * which the spec recommends for users disambiguation in - * a room context and in other places. - * \sa displayName, Room::roomMembername - */ - QString fullName(const Room* room = nullptr) const; - - /** - * Returns the name of bridge the user is connected from or empty. - */ - QString bridged() const; - - /** Whether the user is a guest - * As of now, the function relies on the convention used in Synapse - * that guests and only guests have all-numeric IDs. This may or - * may not work with non-Synapse servers. - */ - bool isGuest() const; - - const Avatar& avatarObject(const Room* room = nullptr) const; - Q_INVOKABLE QImage avatar(int dimension, const Room* room = nullptr); - Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight, - const Room* room = nullptr); - QImage avatar(int width, int height, const Room* room, - const Avatar::get_callback_t& callback); - - QString avatarMediaId(const Room* room = nullptr) const; - QUrl avatarUrl(const Room* room = nullptr) const; - - void processEvent(const RoomMemberEvent& event, const Room* r); - - public slots: - /** Set a new name in the global user profile */ - void rename(const QString& newName); - /** Set a new name for the user in one room */ - void rename(const QString& newName, const Room* r); - /** Upload the file and use it as an avatar */ - bool setAvatar(const QString& fileName); - /** Upload contents of the QIODevice and set that as an avatar */ - bool setAvatar(QIODevice* source); - /** Create or find a direct chat with this user - * The resulting chat is returned asynchronously via - * Connection::directChatAvailable() - */ - void requestDirectChat(); - /** Add the user to the ignore list */ - void ignore(); - /** Remove the user from the ignore list */ - void unmarkIgnore(); - /** Check whether the user is in ignore list */ - bool isIgnored() const; - - signals: - void nameAboutToChange(QString newName, QString oldName, - const Room* roomContext); - void nameChanged(QString newName, QString oldName, - const Room* roomContext); - void avatarChanged(User* user, const Room* roomContext); - - private slots: - void updateName(const QString& newName, const Room* room = nullptr); - void updateName(const QString& newName, const QString& oldName, - const Room* room = nullptr); - void updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl, - const Room* room = nullptr); - - private: - class Private; - QScopedPointer<Private> d; - }; -} +namespace QMatrixClient +{ +class Connection; +class Room; +class RoomMemberEvent; + +class User : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(bool isGuest READ isGuest CONSTANT) + Q_PROPERTY(int hue READ hue CONSTANT) + Q_PROPERTY(qreal hueF READ hueF CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false) + Q_PROPERTY(QString fullName READ fullName NOTIFY nameChanged STORED false) + Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false) + Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged + STORED false) + Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged) +public: + User(QString userId, Connection* connection); + ~User() override; + + Connection* connection() const; + + /** Get unique stable user id + * User id is generated by the server and is not changed ever. + */ + QString id() const; + + /** Get the name chosen by the user + * This may be empty if the user didn't choose the name or cleared + * it. If the user is bridged, the bridge postfix (such as '(IRC)') + * is stripped out. No disambiguation for the room is done. + * \sa displayName, rawName + */ + QString name(const Room* room = nullptr) const; + + /** Get the user name along with the bridge postfix + * This function is similar to name() but appends the bridge postfix + * (such as '(IRC)') to the user name. No disambiguation is done. + * \sa name, displayName + */ + QString rawName(const Room* room = nullptr) const; + + /** Get the displayed user name + * When \p room is null, this method returns result of name() if + * the name is non-empty; otherwise it returns user id. + * When \p room is non-null, this call is equivalent to + * Room::roomMembername invocation for the user (i.e. the user's + * disambiguated room-specific name is returned). + * \sa name, id, fullName, Room::roomMembername + */ + QString displayname(const Room* room = nullptr) const; + + /** Get user name and id in one string + * The constructed string follows the format 'name (id)' + * which the spec recommends for users disambiguation in + * a room context and in other places. + * \sa displayName, Room::roomMembername + */ + QString fullName(const Room* room = nullptr) const; + + /** + * Returns the name of bridge the user is connected from or empty. + */ + QString bridged() const; + + /** Whether the user is a guest + * As of now, the function relies on the convention used in Synapse + * that guests and only guests have all-numeric IDs. This may or + * may not work with non-Synapse servers. + */ + bool isGuest() const; + + /** Hue color component of this user based on id. + * The implementation is based on XEP-0392: + * https://xmpp.org/extensions/xep-0392.html + * Naming and ranges are the same as QColor's hue methods: + * https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision + */ + int hue() const; + qreal hueF() const; + + const Avatar& avatarObject(const Room* room = nullptr) const; + Q_INVOKABLE QImage avatar(int dimension, const Room* room = nullptr); + Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight, + const Room* room = nullptr); + QImage avatar(int width, int height, const Room* room, + const Avatar::get_callback_t& callback); + + QString avatarMediaId(const Room* room = nullptr) const; + QUrl avatarUrl(const Room* room = nullptr) const; + + /// This method is for internal use and should not be called + /// from client code + // FIXME: Move it away to private in lib 0.6 + void processEvent(const RoomMemberEvent& event, const Room* r, + bool firstMention); + +public slots: + /** Set a new name in the global user profile */ + void rename(const QString& newName); + /** Set a new name for the user in one room */ + void rename(const QString& newName, const Room* r); + /** Upload the file and use it as an avatar */ + bool setAvatar(const QString& fileName); + /** Upload contents of the QIODevice and set that as an avatar */ + bool setAvatar(QIODevice* source); + /** Create or find a direct chat with this user + * The resulting chat is returned asynchronously via + * Connection::directChatAvailable() + */ + void requestDirectChat(); + /** Add the user to the ignore list */ + void ignore(); + /** Remove the user from the ignore list */ + void unmarkIgnore(); + /** Check whether the user is in ignore list */ + bool isIgnored() const; + +signals: + void nameAboutToChange(QString newName, QString oldName, + const Room* roomContext); + void nameChanged(QString newName, QString oldName, const Room* roomContext); + void avatarChanged(User* user, const Room* roomContext); + +private slots: + void updateName(const QString& newName, const Room* room = nullptr); + void updateName(const QString& newName, const QString& oldName, + const Room* room = nullptr); + void updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl, + const Room* room = nullptr); + +private: + class Private; + QScopedPointer<Private> d; +}; +} // namespace QMatrixClient Q_DECLARE_METATYPE(QMatrixClient::User*) diff --git a/lib/util.cpp b/lib/util.cpp index a7c745d4..be2665c5 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -23,63 +23,73 @@ #include <QtCore/QStandardPaths> #include <QtCore/QStringBuilder> -static const auto RegExpOptions = QRegularExpression::CaseInsensitiveOption - | QRegularExpression::OptimizeOnFirstUsageOption - | QRegularExpression::UseUnicodePropertiesOption; +static const auto RegExpOptions = + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::OptimizeOnFirstUsageOption + | QRegularExpression::UseUnicodePropertiesOption; // Converts all that looks like a URL into HTML links -static void linkifyUrls(QString& htmlEscapedText) +void QMatrixClient::linkifyUrls(QString& htmlEscapedText) { + // Note: outer parentheses are a part of C++ raw string delimiters, not of + // the regex (see http://en.cppreference.com/w/cpp/language/string_literal). + // Note2: the next-outer parentheses are \N in the replacement. + + // generic url: // regexp is originally taken from Konsole (https://github.com/KDE/konsole) - // full url: // protocolname:// or www. followed by anything other than whitespaces, // <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, ), :, // comma or dot - // Note: outer parentheses are a part of C++ raw string delimiters, not of - // the regex (see http://en.cppreference.com/w/cpp/language/string_literal). - // Note2: yet another pair of outer parentheses are \1 in the replacement. static const QRegularExpression FullUrlRegExp( - QStringLiteral( - R"(((www\.(?!\.)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"), - RegExpOptions); + QStringLiteral( + R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp|magnet)://)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"), + RegExpOptions); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] static const QRegularExpression EmailAddressRegExp( - QStringLiteral(R"((mailto:)?(\b(\w|\.|-)+@(\w|\.|-)+\.\w+\b))"), - RegExpOptions); + QStringLiteral(R"(\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"), + RegExpOptions); // An interim liberal implementation of // https://matrix.org/docs/spec/appendices.html#identifier-grammar static const QRegularExpression MxIdRegExp( - QStringLiteral( - R"((^|[^<>/])([!#@][-a-z0-9_=/.]{1,252}:[-.a-z0-9]+))"), - RegExpOptions); + QStringLiteral( + R"((^|[^<>/])([!#@][-a-z0-9_=/.]{1,252}:(?:\w|\.|-)+\.\w+(?::\d{1,5})?))"), + RegExpOptions); - // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,& + // NOTE: htmlEscapedText is already HTML-escaped! No literal <,>,&," htmlEscapedText.replace(EmailAddressRegExp, QStringLiteral(R"(<a href="mailto:\2">\1\2</a>)")); htmlEscapedText.replace(FullUrlRegExp, QStringLiteral(R"(<a href="\1">\1</a>)")); htmlEscapedText.replace( - MxIdRegExp, - QStringLiteral(R"(\1<a href="https://matrix.to/#/\2">\2</a>)")); + MxIdRegExp, + QStringLiteral(R"(\1<a href="https://matrix.to/#/\2">\2</a>)")); } -QString QMatrixClient::prettyPrint(const QString& plainText) +QString QMatrixClient::sanitized(const QString& plainText) { - auto pt = QStringLiteral("<span style='white-space:pre-wrap'>") - + plainText.toHtmlEscaped() + QStringLiteral("</span>"); - pt.replace('\n', QStringLiteral("<br/>")); + auto text = plainText; + text.remove(QChar(0x202e)); // RLO + text.remove(QChar(0x202d)); // LRO + text.remove(QChar(0xfffc)); // Object replacement character + return text; +} +QString QMatrixClient::prettyPrint(const QString& plainText) +{ + auto pt = plainText.toHtmlEscaped(); linkifyUrls(pt); - return pt; + pt.replace('\n', QStringLiteral("<br/>")); + return QStringLiteral("<span style='white-space:pre-wrap'>") + pt + + QStringLiteral("</span>"); } QString QMatrixClient::cacheLocation(const QString& dirName) { const QString cachePath = - QStandardPaths::writableLocation(QStandardPaths::CacheLocation) - % '/' % dirName % '/'; + QStandardPaths::writableLocation(QStandardPaths::CacheLocation) % '/' + % dirName % '/'; QDir dir; if (!dir.exists(cachePath)) dir.mkpath(cachePath); @@ -89,8 +99,8 @@ QString QMatrixClient::cacheLocation(const QString& dirName) // Tests for function_traits<> #ifdef Q_CC_CLANG -#pragma clang diagnostic push -#pragma ide diagnostic ignored "OCSimplifyInspection" +# pragma clang diagnostic push +# pragma ide diagnostic ignored "OCSimplifyInspection" #endif using namespace QMatrixClient; @@ -106,14 +116,16 @@ void f2(int, QString); static_assert(std::is_same<fn_arg_t<decltype(f2), 1>, QString>::value, "Test fn_arg_t<>"); -struct S { +struct S +{ int mf(); }; static_assert(is_callable_v<decltype(&S::mf)>, "Test member function"); static_assert(returns<int, decltype(&S::mf)>(), "Test returns<> with member function"); -struct Fo { +struct Fo +{ int operator()(); }; static_assert(is_callable_v<Fo>, "Test is_callable<> with function object"); @@ -121,7 +133,8 @@ static_assert(function_traits<Fo>::arg_number == 0, "Test function object"); static_assert(std::is_same<fn_return_t<Fo>, int>::value, "Test return type of function object"); -struct Fo1 { +struct Fo1 +{ void operator()(int); }; static_assert(function_traits<Fo1>::arg_number == 1, "Test function object 1"); @@ -136,10 +149,14 @@ static_assert(std::is_same<fn_return_t<decltype(l)>, int>::value, "Test fn_return_t<> with lambda"); #endif -template <typename T> struct fn_object { +template <typename T> +struct fn_object +{ static int smf(double) { return 0; } }; -template <> struct fn_object<QString> { +template <> +struct fn_object<QString> +{ void operator()(QString); }; static_assert(is_callable_v<fn_object<QString>>, "Test function object"); @@ -152,10 +169,14 @@ static_assert(!is_callable_v<fn_object<int>>, "Test non-function object"); // static_assert(returns<int, decltype(&fn_object<int>::smf)>(), // "Test returns<> with static member function"); -template <typename T> QString ft(T&&); +template <typename T> +QString ft(T&&) +{ + return {}; +} static_assert(std::is_same<fn_arg_t<decltype(ft<QString>)>, QString&&>(), "Test function templates"); #ifdef Q_CC_CLANG -#pragma clang diagnostic pop +# pragma clang diagnostic pop #endif @@ -21,26 +21,26 @@ #include <QtCore/QLatin1String> #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) -#include <QtCore/QDebug> -#include <QtCore/QMetaEnum> +# include <QtCore/QDebug> +# include <QtCore/QMetaEnum> #endif #include <functional> #include <memory> #if __has_cpp_attribute(fallthrough) -#define FALLTHROUGH [[fallthrough]] +# define FALLTHROUGH [[fallthrough]] #elif __has_cpp_attribute(clang::fallthrough) -#define FALLTHROUGH [[clang::fallthrough]] +# define FALLTHROUGH [[clang::fallthrough]] #elif __has_cpp_attribute(gnu::fallthrough) -#define FALLTHROUGH [[gnu::fallthrough]] +# define FALLTHROUGH [[gnu::fallthrough]] #else -#define FALLTHROUGH // -fallthrough +# define FALLTHROUGH // -fallthrough #endif // Along the lines of Q_DISABLE_COPY -#define DISABLE_MOVE(_ClassName) \ - _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ +#define DISABLE_MOVE(_ClassName) \ + _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; #if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) @@ -51,264 +51,294 @@ Q_DECL_CONSTEXPR typename std::add_const<T>::type& qAsConst(T& t) Q_DECL_NOTHROW return t; } // prevent rvalue arguments: -template <typename T> static void qAsConst(const T&&) Q_DECL_EQ_DELETE; +template <typename T> +static void qAsConst(const T&&) Q_DECL_EQ_DELETE; #endif // MSVC 2015 and older GCC's don't handle initialisation from initializer lists // right in the absense of a constructor; MSVC 2015, notably, fails with // "error C2440: 'return': cannot convert from 'initializer list' to '<type>'" -#if (defined(_MSC_VER) && _MSC_VER < 1910) \ - || (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 4) -#define BROKEN_INITIALIZER_LISTS +#if (defined(_MSC_VER) && _MSC_VER < 1910) \ + || (defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 4) +# define BROKEN_INITIALIZER_LISTS #endif -namespace QMatrixClient { - // The below enables pretty-printing of enums in logs +namespace QMatrixClient +{ +// The below enables pretty-printing of enums in logs #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) -#define REGISTER_ENUM(EnumName) Q_ENUM(EnumName) +# define REGISTER_ENUM(EnumName) Q_ENUM(EnumName) #else - // Thanks to Olivier for spelling it and for making Q_ENUM to replace it: - // https://woboq.com/blog/q_enum.html -#define REGISTER_ENUM(EnumName) \ - Q_ENUMS(EnumName) \ - friend QDebug operator<<(QDebug dbg, EnumName val) \ - { \ - static int enumIdx = staticMetaObject.indexOfEnumerator(#EnumName); \ - return dbg << Event::staticMetaObject.enumerator(enumIdx).valueToKey( \ - int(val)); \ - } +// Thanks to Olivier for spelling it and for making Q_ENUM to replace it: +// https://woboq.com/blog/q_enum.html +# define REGISTER_ENUM(EnumName) \ + Q_ENUMS(EnumName) \ + friend QDebug operator<<(QDebug dbg, EnumName val) \ + { \ + static int enumIdx = staticMetaObject.indexOfEnumerator(#EnumName); \ + return dbg << Event::staticMetaObject.enumerator(enumIdx).valueToKey( \ + int(val)); \ + } #endif - /** static_cast<> for unique_ptr's */ - template <typename T1, typename PtrT2> - inline auto unique_ptr_cast(PtrT2&& p) +/** static_cast<> for unique_ptr's */ +template <typename T1, typename PtrT2> +inline auto unique_ptr_cast(PtrT2&& p) +{ + return std::unique_ptr<T1>(static_cast<T1*>(p.release())); +} + +struct NoneTag +{}; +constexpr NoneTag none {}; + +/** A crude substitute for `optional` while we're not C++17 + * + * Only works with default-constructible types. + */ +template <typename T> +class Omittable +{ + static_assert(!std::is_reference<T>::value, + "You cannot make an Omittable<> with a reference type"); + +public: + using value_type = std::decay_t<T>; + + explicit Omittable() + : Omittable(none) + {} + Omittable(NoneTag) + : _value(value_type()) + , _omitted(true) + {} + Omittable(const value_type& val) + : _value(val) + {} + Omittable(value_type&& val) + : _value(std::move(val)) + {} + Omittable<T>& operator=(const value_type& val) { - return std::unique_ptr<T1>(static_cast<T1*>(p.release())); + _value = val; + _omitted = false; + return *this; + } + Omittable<T>& operator=(value_type&& val) + { + // For some reason GCC complains about -Wmaybe-uninitialized + // in the context of using Omittable<bool> with converters.h; + // though the logic looks very much benign (GCC bug???) + _value = std::move(val); + _omitted = false; + return *this; } - struct NoneTag { - }; - constexpr NoneTag none {}; + bool operator==(const value_type& rhs) const + { + return !omitted() && value() == rhs; + } + friend bool operator==(const value_type& lhs, + const Omittable<value_type>& rhs) + { + return rhs == lhs; + } + bool operator!=(const value_type& rhs) const { return !operator==(rhs); } + friend bool operator!=(const value_type& lhs, + const Omittable<value_type>& rhs) + { + return !(rhs == lhs); + } - /** A crude substitute for `optional` while we're not C++17 - * - * Only works with default-constructible types. - */ - template <typename T> class Omittable + bool omitted() const { return _omitted; } + const value_type& value() const { - static_assert(!std::is_reference<T>::value, - "You cannot make an Omittable<> with a reference type"); - - public: - using value_type = std::decay_t<T>; - - explicit Omittable() : Omittable(none) {} - Omittable(NoneTag) : _value(value_type()), _omitted(true) {} - Omittable(const value_type& val) : _value(val) {} - Omittable(value_type&& val) : _value(std::move(val)) {} - Omittable<T>& operator=(const value_type& val) - { - _value = val; - _omitted = false; - return *this; - } - Omittable<T>& operator=(value_type&& val) - { - // For some reason GCC complains about -Wmaybe-uninitialized - // in the context of using Omittable<bool> with converters.h; - // though the logic looks very much benign (GCC bug???) - _value = std::move(val); - _omitted = false; - return *this; - } + Q_ASSERT(!_omitted); + return _value; + } + value_type& editValue() + { + _omitted = false; + return _value; + } + /// Merge the value from another Omittable + /// \return true if \p other is not omitted and the value of + /// the current Omittable was different (or omitted); + /// in other words, if the current Omittable has changed; + /// false otherwise + template <typename T1> + auto merge(const Omittable<T1>& other) + -> std::enable_if_t<std::is_convertible<T1, T>::value, bool> + { + if (other.omitted() || (!_omitted && _value == other.value())) + return false; + _omitted = false; + _value = other.value(); + return true; + } + value_type&& release() + { + _omitted = true; + return std::move(_value); + } - bool operator==(const value_type& rhs) const - { - return !omitted() && value() == rhs; - } - friend bool operator==(const value_type& lhs, - const Omittable<value_type>& rhs) - { - return rhs == lhs; - } - bool operator!=(const value_type& rhs) const - { - return !operator==(rhs); - } - friend bool operator!=(const value_type& lhs, - const Omittable<value_type>& rhs) - { - return !(rhs == lhs); - } + const value_type* operator->() const& { return &value(); } + value_type* operator->() & { return &editValue(); } + const value_type& operator*() const& { return value(); } + value_type& operator*() & { return editValue(); } - bool omitted() const { return _omitted; } - const value_type& value() const - { - Q_ASSERT(!_omitted); - return _value; - } - value_type& editValue() - { - _omitted = false; - return _value; - } - /// Merge the value from another Omittable - /// \return true if \p other is not omitted and the value of - /// the current Omittable was different (or omitted); - /// in other words, if the current Omittable has changed; - /// false otherwise - template <typename T1> - auto merge(const Omittable<T1>& other) - -> std::enable_if_t<std::is_convertible<T1, T>::value, bool> - { - if (other.omitted() || (!_omitted && _value == other.value())) - return false; - _omitted = false; - _value = other.value(); - return true; - } - value_type&& release() - { - _omitted = true; - return std::move(_value); - } - - const value_type* operator->() const& { return &value(); } - value_type* operator->() & { return &editValue(); } - const value_type& operator*() const& { return value(); } - value_type& operator*() & { return editValue(); } +private: + T _value; + bool _omitted = false; +}; - private: - T _value; - bool _omitted = false; - }; +namespace _impl +{ + template <typename AlwaysVoid, typename> + struct fn_traits; +} - namespace _impl { - template <typename AlwaysVoid, typename> struct fn_traits; - } +/** Determine traits of an arbitrary function/lambda/functor + * Doesn't work with generic lambdas and function objects that have + * operator() overloaded. + * \sa + * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 + */ +template <typename T> +struct function_traits : public _impl::fn_traits<void, T> +{}; - /** Determine traits of an arbitrary function/lambda/functor - * Doesn't work with generic lambdas and function objects that have - * operator() overloaded. - * \sa - * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765 - */ - template <typename T> - struct function_traits : public _impl::fn_traits<void, T> { +// Specialisation for a function +template <typename ReturnT, typename... ArgTs> +struct function_traits<ReturnT(ArgTs...)> +{ + static constexpr auto is_callable = true; + using return_type = ReturnT; + using arg_types = std::tuple<ArgTs...>; + using function_type = std::function<ReturnT(ArgTs...)>; + static constexpr auto arg_number = std::tuple_size<arg_types>::value; +}; + +namespace _impl +{ + template <typename AlwaysVoid, typename T> + struct fn_traits + { + static constexpr auto is_callable = false; }; - // Specialisation for a function - template <typename ReturnT, typename... ArgTs> - struct function_traits<ReturnT(ArgTs...)> { - static constexpr auto is_callable = true; - using return_type = ReturnT; - using arg_types = std::tuple<ArgTs...>; - using function_type = std::function<ReturnT(ArgTs...)>; - static constexpr auto arg_number = std::tuple_size<arg_types>::value; - }; + template <typename T> + struct fn_traits<decltype(void(&T::operator())), T> + : public fn_traits<void, decltype(&T::operator())> + {}; // A generic function object that has (non-overloaded) operator() + + // Specialisation for a member function + template <typename ReturnT, typename ClassT, typename... ArgTs> + struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...)> + : function_traits<ReturnT(ArgTs...)> + {}; + + // Specialisation for a const member function + template <typename ReturnT, typename ClassT, typename... ArgTs> + struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) const> + : function_traits<ReturnT(ArgTs...)> + {}; +} // namespace _impl + +template <typename FnT> +using fn_return_t = typename function_traits<FnT>::return_type; + +template <typename FnT, int ArgN = 0> +using fn_arg_t = + std::tuple_element_t<ArgN, typename function_traits<FnT>::arg_types>; + +template <typename R, typename FnT> +constexpr bool returns() +{ + return std::is_same<fn_return_t<FnT>, R>::value; +} - namespace _impl { - template <typename AlwaysVoid, typename T> struct fn_traits { - static constexpr auto is_callable = false; - }; - - template <typename T> - struct fn_traits<decltype(void(&T::operator())), T> - : public fn_traits<void, decltype(&T::operator())> { - }; // A generic function object that has (non-overloaded) operator() - - // Specialisation for a member function - template <typename ReturnT, typename ClassT, typename... ArgTs> - struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...)> - : function_traits<ReturnT(ArgTs...)> { - }; - - // Specialisation for a const member function - template <typename ReturnT, typename ClassT, typename... ArgTs> - struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) const> - : function_traits<ReturnT(ArgTs...)> { - }; - } // namespace _impl - - template <typename FnT> - using fn_return_t = typename function_traits<FnT>::return_type; - - template <typename FnT, int ArgN = 0> - using fn_arg_t = - std::tuple_element_t<ArgN, - typename function_traits<FnT>::arg_types>; - - template <typename R, typename FnT> constexpr bool returns() - { - return std::is_same<fn_return_t<FnT>, R>::value; - } +// Poor-man's is_invokable +template <typename T> +constexpr auto is_callable_v = function_traits<T>::is_callable; - // Poor-man's is_invokable - template <typename T> - constexpr auto is_callable_v = function_traits<T>::is_callable; +inline auto operator"" _ls(const char* s, std::size_t size) +{ + return QLatin1String(s, int(size)); +} - inline auto operator"" _ls(const char* s, std::size_t size) +/** An abstraction over a pair of iterators + * This is a very basic range type over a container with iterators that + * are at least ForwardIterators. Inspired by Ranges TS. + */ +template <typename ArrayT> +class Range +{ + // Looking forward for Ranges TS to produce something (in C++23?..) + using iterator = typename ArrayT::iterator; + using const_iterator = typename ArrayT::const_iterator; + using size_type = typename ArrayT::size_type; + +public: + Range(ArrayT& arr) + : from(std::begin(arr)) + , to(std::end(arr)) + {} + Range(iterator from, iterator to) + : from(from) + , to(to) + {} + + size_type size() const { - return QLatin1String(s, int(size)); + Q_ASSERT(std::distance(from, to) >= 0); + return size_type(std::distance(from, to)); } + bool empty() const { return from == to; } + const_iterator begin() const { return from; } + const_iterator end() const { return to; } + iterator begin() { return from; } + iterator end() { return to; } + +private: + iterator from; + iterator to; +}; + +/** A replica of std::find_first_of that returns a pair of iterators + * + * Convenient for cases when you need to know which particular "first of" + * [sFirst, sLast) has been found in [first, last). + */ +template <typename InputIt, typename ForwardIt, typename Pred> +inline std::pair<InputIt, ForwardIt> findFirstOf(InputIt first, InputIt last, + ForwardIt sFirst, + ForwardIt sLast, Pred pred) +{ + for (; first != last; ++first) + for (auto it = sFirst; it != sLast; ++it) + if (pred(*first, *it)) + return std::make_pair(first, it); - /** An abstraction over a pair of iterators - * This is a very basic range type over a container with iterators that - * are at least ForwardIterators. Inspired by Ranges TS. - */ - template <typename ArrayT> class Range - { - // Looking forward for Ranges TS to produce something (in C++23?..) - using iterator = typename ArrayT::iterator; - using const_iterator = typename ArrayT::const_iterator; - using size_type = typename ArrayT::size_type; - - public: - Range(ArrayT& arr) : from(std::begin(arr)), to(std::end(arr)) {} - Range(iterator from, iterator to) : from(from), to(to) {} - - size_type size() const - { - Q_ASSERT(std::distance(from, to) >= 0); - return size_type(std::distance(from, to)); - } - bool empty() const { return from == to; } - const_iterator begin() const { return from; } - const_iterator end() const { return to; } - iterator begin() { return from; } - iterator end() { return to; } - - private: - iterator from; - iterator to; - }; + return std::make_pair(last, sLast); +} - /** A replica of std::find_first_of that returns a pair of iterators - * - * Convenient for cases when you need to know which particular "first of" - * [sFirst, sLast) has been found in [first, last). - */ - template <typename InputIt, typename ForwardIt, typename Pred> - inline std::pair<InputIt, ForwardIt> - findFirstOf(InputIt first, InputIt last, ForwardIt sFirst, ForwardIt sLast, - Pred pred) - { - for (; first != last; ++first) - for (auto it = sFirst; it != sLast; ++it) - if (pred(*first, *it)) - return std::make_pair(first, it); +/** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */ +void linkifyUrls(QString& htmlEscapedText); - return std::make_pair(last, sLast); - } +/** Sanitize the text before showing in HTML + * This does toHtmlEscaped() and removes Unicode BiDi marks. + */ +QString sanitized(const QString& plainText); - /** Pretty-prints plain text into HTML - * This includes HTML escaping of <,>,",& and URLs linkification. - */ - QString prettyPrint(const QString& plainText); +/** Pretty-print plain text into HTML + * This includes HTML escaping of <,>,",& and calling linkifyUrls() + */ +QString prettyPrint(const QString& plainText); - /** Return a path to cache directory after making sure that it exists - * The returned path has a trailing slash, clients don't need to append it. - * \param dir path to cache directory relative to the standard cache path - */ - QString cacheLocation(const QString& dirName); +/** Return a path to cache directory after making sure that it exists + * The returned path has a trailing slash, clients don't need to append it. + * \param dir path to cache directory relative to the standard cache path + */ +QString cacheLocation(const QString& dirName); } // namespace QMatrixClient |