From e9b445909f2707ca199e102e8cae2f11f7b64701 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 6 Dec 2017 09:07:37 +0900 Subject: converters.h: support QByteArray --- converters.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/converters.h b/converters.h index 733c2c0e..5593ee24 100644 --- a/converters.h +++ b/converters.h @@ -44,6 +44,11 @@ namespace QMatrixClient return QJsonArray::fromStringList(strings); } + inline QJsonValue toJson(const QByteArray& bytes) + { + return QJsonValue(static_cast(bytes)); + } + template struct FromJson { @@ -133,4 +138,12 @@ namespace QMatrixClient template <> struct FromJson : FromJson> { }; + template <> struct FromJson + { + QByteArray operator()(QJsonValue jv) const + { + return fromJson(jv).toLatin1(); + } + }; + } // namespace QMatrixClient -- cgit v1.2.3 From 67fad643e30eb38f51e496f6f4257c04eb0d8b92 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 6 Dec 2017 09:10:50 +0900 Subject: Connection: undeprecate joinRoom() This is for the same reason as Connection::getThumbnail() - it's the only non-template (hence, supported by QML and Qt queued signal connections) way to invoke joining a room. --- connection.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.h b/connection.h index 256dbd5f..fc6f4588 100644 --- a/connection.h +++ b/connection.h @@ -182,6 +182,8 @@ namespace QMatrixClient int requestedWidth, int requestedHeight) const; + virtual JoinRoomJob* joinRoom(const QString& roomAlias); + // Old API that will be abolished any time soon. DO NOT USE. /** @deprecated Use callApi() or Room::postMessage() instead */ @@ -190,8 +192,6 @@ namespace QMatrixClient /** @deprecated Use callApi() or Room::postReceipt() instead */ virtual PostReceiptJob* postReceipt(Room* room, RoomEvent* event) const; - /** @deprecated Use callApi() instead */ - virtual JoinRoomJob* joinRoom(const QString& roomAlias); /** @deprecated Use callApi() or Room::leaveRoom() instead */ virtual void leaveRoom( Room* room ); /** @deprecated User callApi() or Room::getPreviousContent() instead */ -- cgit v1.2.3 From 70d9ca7b870e35109dfdbe9ab3c63ba094a75379 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 6 Dec 2017 19:02:11 +0900 Subject: converters.h: Use perfect forwarding --- converters.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/converters.h b/converters.h index 5593ee24..9b78eb37 100644 --- a/converters.h +++ b/converters.h @@ -25,9 +25,9 @@ namespace QMatrixClient { template - inline QJsonValue toJson(T val) + inline QJsonValue toJson(T&& val) { - return QJsonValue(val); + return QJsonValue(std::forward(val)); } template -- cgit v1.2.3 From e88627976488b0b6eea6da3389eefb860980d661 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 19 Oct 2017 19:23:10 +0900 Subject: Generated jobs: Don't dump empty strings to body parameters This is important for (soon to be added) LoginJob, since the server is sensitive to getting an (even empty) entity for "medium" as opposed to omitting it entirely. This cannot be addressed on the spec level; on the other hand, removing empty parameters from the payload reduces useless bytes getting on the wire. --- jobs/generated/banning.cpp | 25 ++++++++++++++----------- jobs/generated/inviting.cpp | 11 ++++++----- jobs/generated/kicking.cpp | 14 ++++++++------ jobs/generated/leaving.cpp | 6 ++---- jobs/generated/logout.cpp | 3 +-- jobs/generated/profile.cpp | 33 +++++++++++++++++---------------- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index c47d3419..ebb4c96c 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -15,21 +15,24 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); BanJob::BanJob(QString roomId, QString user_id, QString reason) : BaseJob(HttpVerb::Post, "BanJob", basePath % "/rooms/" % roomId % "/ban", - Query { }, - Data { - { "user_id", toJson(user_id) }, - { "reason", toJson(reason) } - } + Query { } ) -{ } +{ + Data _data; + _data.insert("user_id", toJson(user_id)); + if (!reason.isEmpty()) + _data.insert("reason", toJson(reason)); + setRequestData(_data); +} UnbanJob::UnbanJob(QString roomId, QString user_id) : BaseJob(HttpVerb::Post, "UnbanJob", basePath % "/rooms/" % roomId % "/unban", - Query { }, - Data { - { "user_id", toJson(user_id) } - } + Query { } ) -{ } +{ + Data _data; + _data.insert("user_id", toJson(user_id)); + setRequestData(_data); +} diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 11384c5e..73c73076 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -15,10 +15,11 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); InviteUserJob::InviteUserJob(QString roomId, QString user_id) : BaseJob(HttpVerb::Post, "InviteUserJob", basePath % "/rooms/" % roomId % "/invite", - Query { }, - Data { - { "user_id", toJson(user_id) } - } + Query { } ) -{ } +{ + Data _data; + _data.insert("user_id", toJson(user_id)); + setRequestData(_data); +} diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index e75b900a..28d51d05 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -15,11 +15,13 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); KickJob::KickJob(QString roomId, QString user_id, QString reason) : BaseJob(HttpVerb::Post, "KickJob", basePath % "/rooms/" % roomId % "/kick", - Query { }, - Data { - { "user_id", toJson(user_id) }, - { "reason", toJson(reason) } - } + Query { } ) -{ } +{ + Data _data; + _data.insert("user_id", toJson(user_id)); + if (!reason.isEmpty()) + _data.insert("reason", toJson(reason)); + setRequestData(_data); +} diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index e443612e..392f1ca8 100644 --- a/jobs/generated/leaving.cpp +++ b/jobs/generated/leaving.cpp @@ -15,16 +15,14 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); LeaveRoomJob::LeaveRoomJob(QString roomId) : BaseJob(HttpVerb::Post, "LeaveRoomJob", basePath % "/rooms/" % roomId % "/leave", - Query { }, - Data { } + Query { } ) { } ForgetRoomJob::ForgetRoomJob(QString roomId) : BaseJob(HttpVerb::Post, "ForgetRoomJob", basePath % "/rooms/" % roomId % "/forget", - Query { }, - Data { } + Query { } ) { } diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index a5848e7c..c2480ff9 100644 --- a/jobs/generated/logout.cpp +++ b/jobs/generated/logout.cpp @@ -15,8 +15,7 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); LogoutJob::LogoutJob() : BaseJob(HttpVerb::Post, "LogoutJob", basePath % "/logout", - Query { }, - Data { } + Query { } ) { } diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index 201bca79..f24db15a 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -15,12 +15,14 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); SetDisplayNameJob::SetDisplayNameJob(QString userId, QString displayname) : BaseJob(HttpVerb::Put, "SetDisplayNameJob", basePath % "/profile/" % userId % "/displayname", - Query { }, - Data { - { "displayname", toJson(displayname) } - } + Query { } ) -{ } +{ + Data _data; + if (!displayname.isEmpty()) + _data.insert("displayname", toJson(displayname)); + setRequestData(_data); +} class GetDisplayNameJob::Private { @@ -32,8 +34,7 @@ class GetDisplayNameJob::Private GetDisplayNameJob::GetDisplayNameJob(QString userId) : BaseJob(HttpVerb::Get, "GetDisplayNameJob", basePath % "/profile/" % userId % "/displayname", - Query { }, - Data { } + Query { }, Data { }, false ), d(new Private) { } @@ -59,12 +60,14 @@ BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) SetAvatarUrlJob::SetAvatarUrlJob(QString userId, QString avatar_url) : BaseJob(HttpVerb::Put, "SetAvatarUrlJob", basePath % "/profile/" % userId % "/avatar_url", - Query { }, - Data { - { "avatar_url", toJson(avatar_url) } - } + Query { } ) -{ } +{ + Data _data; + if (!avatar_url.isEmpty()) + _data.insert("avatar_url", toJson(avatar_url)); + setRequestData(_data); +} class GetAvatarUrlJob::Private { @@ -76,8 +79,7 @@ class GetAvatarUrlJob::Private GetAvatarUrlJob::GetAvatarUrlJob(QString userId) : BaseJob(HttpVerb::Get, "GetAvatarUrlJob", basePath % "/profile/" % userId % "/avatar_url", - Query { }, - Data { } + Query { }, Data { }, false ), d(new Private) { } @@ -111,8 +113,7 @@ class GetUserProfileJob::Private GetUserProfileJob::GetUserProfileJob(QString userId) : BaseJob(HttpVerb::Get, "GetUserProfileJob", basePath % "/profile/" % userId, - Query { }, - Data { } + Query { }, Data { }, false ), d(new Private) { } -- cgit v1.2.3 From 6fc3c79c2b3ac318d3a140594ded28020f9a8afb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 30 Nov 2017 03:36:29 +0900 Subject: Generated jobs: Apply naming convention to parameters It's now camelCase everywhere, even if The Spec uses snake_case (it is not consistent in that respect). --- connection.cpp | 4 ++-- jobs/generated/banning.cpp | 8 ++++---- jobs/generated/banning.h | 4 ++-- jobs/generated/inviting.cpp | 4 ++-- jobs/generated/inviting.h | 2 +- jobs/generated/kicking.cpp | 4 ++-- jobs/generated/kicking.h | 2 +- jobs/generated/login.cpp | 42 +++++++++++++++++++++--------------------- jobs/generated/login.h | 10 +++++----- jobs/generated/profile.cpp | 22 +++++++++++----------- jobs/generated/profile.h | 6 +++--- 11 files changed, 54 insertions(+), 54 deletions(-) diff --git a/connection.cpp b/connection.cpp index 78ef9754..c29f09fe 100644 --- a/connection.cpp +++ b/connection.cpp @@ -165,8 +165,8 @@ void Connection::doConnectToServer(const QString& user, const QString& password, deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [=] { - d->connectWithToken(loginJob->user_id(), loginJob->access_token(), - loginJob->device_id()); + d->connectWithToken(loginJob->userId(), loginJob->accessToken(), + loginJob->deviceId()); }); connect(loginJob, &BaseJob::failure, this, [=] { diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index ebb4c96c..67b0e8a1 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -12,27 +12,27 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -BanJob::BanJob(QString roomId, QString user_id, QString reason) +BanJob::BanJob(QString roomId, QString userId, QString reason) : BaseJob(HttpVerb::Post, "BanJob", basePath % "/rooms/" % roomId % "/ban", Query { } ) { Data _data; - _data.insert("user_id", toJson(user_id)); + _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); } -UnbanJob::UnbanJob(QString roomId, QString user_id) +UnbanJob::UnbanJob(QString roomId, QString userId) : BaseJob(HttpVerb::Post, "UnbanJob", basePath % "/rooms/" % roomId % "/unban", Query { } ) { Data _data; - _data.insert("user_id", toJson(user_id)); + _data.insert("user_id", toJson(userId)); setRequestData(_data); } diff --git a/jobs/generated/banning.h b/jobs/generated/banning.h index b9d5b8db..e225a6ef 100644 --- a/jobs/generated/banning.h +++ b/jobs/generated/banning.h @@ -18,13 +18,13 @@ namespace QMatrixClient class BanJob : public BaseJob { public: - explicit BanJob(QString roomId, QString user_id, QString reason = {}); + explicit BanJob(QString roomId, QString userId, QString reason = {}); }; class UnbanJob : public BaseJob { public: - explicit UnbanJob(QString roomId, QString user_id); + explicit UnbanJob(QString roomId, QString userId); }; diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 73c73076..e72bf734 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -12,14 +12,14 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -InviteUserJob::InviteUserJob(QString roomId, QString user_id) +InviteUserJob::InviteUserJob(QString roomId, QString userId) : BaseJob(HttpVerb::Post, "InviteUserJob", basePath % "/rooms/" % roomId % "/invite", Query { } ) { Data _data; - _data.insert("user_id", toJson(user_id)); + _data.insert("user_id", toJson(userId)); setRequestData(_data); } diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h index ac0fd5fa..e4b61beb 100644 --- a/jobs/generated/inviting.h +++ b/jobs/generated/inviting.h @@ -18,7 +18,7 @@ namespace QMatrixClient class InviteUserJob : public BaseJob { public: - explicit InviteUserJob(QString roomId, QString user_id); + explicit InviteUserJob(QString roomId, QString userId); }; diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 28d51d05..a0719aa2 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -12,14 +12,14 @@ using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -KickJob::KickJob(QString roomId, QString user_id, QString reason) +KickJob::KickJob(QString roomId, QString userId, QString reason) : BaseJob(HttpVerb::Post, "KickJob", basePath % "/rooms/" % roomId % "/kick", Query { } ) { Data _data; - _data.insert("user_id", toJson(user_id)); + _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); setRequestData(_data); diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h index 658193d5..e7daada8 100644 --- a/jobs/generated/kicking.h +++ b/jobs/generated/kicking.h @@ -18,7 +18,7 @@ namespace QMatrixClient class KickJob : public BaseJob { public: - explicit KickJob(QString roomId, QString user_id, QString reason = {}); + explicit KickJob(QString roomId, QString userId, QString reason = {}); }; diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 0c57c684..7bd905ee 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -15,14 +15,14 @@ static const auto basePath = QStringLiteral("/_matrix/client/r0"); class LoginJob::Private { public: - QString user_id; - QString access_token; - QString home_server; - QString device_id; + QString userId; + QString accessToken; + QString homeServer; + QString deviceId; }; -LoginJob::LoginJob(QString type, QString user, QString medium, QString address, QString password, QString token, QString device_id, QString initial_device_display_name) +LoginJob::LoginJob(QString type, QString user, QString medium, QString address, QString password, QString token, QString deviceId, QString initialDeviceDisplayName) : BaseJob(HttpVerb::Post, "LoginJob", basePath % "/login", Query { }, Data { }, false @@ -40,10 +40,10 @@ LoginJob::LoginJob(QString type, QString user, QString medium, QString address, _data.insert("password", toJson(password)); if (!token.isEmpty()) _data.insert("token", toJson(token)); - if (!device_id.isEmpty()) - _data.insert("device_id", toJson(device_id)); - if (!initial_device_display_name.isEmpty()) - _data.insert("initial_device_display_name", toJson(initial_device_display_name)); + if (!deviceId.isEmpty()) + _data.insert("device_id", toJson(deviceId)); + if (!initialDeviceDisplayName.isEmpty()) + _data.insert("initial_device_display_name", toJson(initialDeviceDisplayName)); setRequestData(_data); } @@ -52,37 +52,37 @@ LoginJob::~LoginJob() delete d; } -const QString& LoginJob::user_id() const +const QString& LoginJob::userId() const { - return d->user_id; + return d->userId; } -const QString& LoginJob::access_token() const +const QString& LoginJob::accessToken() const { - return d->access_token; + return d->accessToken; } -const QString& LoginJob::home_server() const +const QString& LoginJob::homeServer() const { - return d->home_server; + return d->homeServer; } -const QString& LoginJob::device_id() const +const QString& LoginJob::deviceId() const { - return d->device_id; + return d->deviceId; } BaseJob::Status LoginJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->user_id = fromJson(json.value("user_id")); + d->userId = fromJson(json.value("user_id")); - d->access_token = fromJson(json.value("access_token")); + d->accessToken = fromJson(json.value("access_token")); - d->home_server = fromJson(json.value("home_server")); + d->homeServer = fromJson(json.value("home_server")); - d->device_id = fromJson(json.value("device_id")); + d->deviceId = fromJson(json.value("device_id")); return Success; } diff --git a/jobs/generated/login.h b/jobs/generated/login.h index dc89206d..8fcca09a 100644 --- a/jobs/generated/login.h +++ b/jobs/generated/login.h @@ -18,14 +18,14 @@ namespace QMatrixClient class LoginJob : public BaseJob { public: - explicit LoginJob(QString type, QString user = {}, QString medium = {}, QString address = {}, QString password = {}, QString token = {}, QString device_id = {}, QString initial_device_display_name = {}); + explicit LoginJob(QString type, QString user = {}, QString medium = {}, QString address = {}, QString password = {}, QString token = {}, QString deviceId = {}, QString initialDeviceDisplayName = {}); ~LoginJob() override; - const QString& user_id() const; - const QString& access_token() const; - const QString& home_server() const; - const QString& device_id() const; + const QString& userId() const; + const QString& accessToken() const; + const QString& homeServer() const; + const QString& deviceId() const; protected: Status parseJson(const QJsonDocument& data) override; diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index f24db15a..f682ad31 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -57,22 +57,22 @@ BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) return Success; } -SetAvatarUrlJob::SetAvatarUrlJob(QString userId, QString avatar_url) +SetAvatarUrlJob::SetAvatarUrlJob(QString userId, QString avatarUrl) : BaseJob(HttpVerb::Put, "SetAvatarUrlJob", basePath % "/profile/" % userId % "/avatar_url", Query { } ) { Data _data; - if (!avatar_url.isEmpty()) - _data.insert("avatar_url", toJson(avatar_url)); + if (!avatarUrl.isEmpty()) + _data.insert("avatar_url", toJson(avatarUrl)); setRequestData(_data); } class GetAvatarUrlJob::Private { public: - QString avatar_url; + QString avatarUrl; }; @@ -88,16 +88,16 @@ GetAvatarUrlJob::~GetAvatarUrlJob() delete d; } -const QString& GetAvatarUrlJob::avatar_url() const +const QString& GetAvatarUrlJob::avatarUrl() const { - return d->avatar_url; + return d->avatarUrl; } BaseJob::Status GetAvatarUrlJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->avatar_url = fromJson(json.value("avatar_url")); + d->avatarUrl = fromJson(json.value("avatar_url")); return Success; } @@ -105,7 +105,7 @@ BaseJob::Status GetAvatarUrlJob::parseJson(const QJsonDocument& data) class GetUserProfileJob::Private { public: - QString avatar_url; + QString avatarUrl; QString displayname; }; @@ -122,9 +122,9 @@ GetUserProfileJob::~GetUserProfileJob() delete d; } -const QString& GetUserProfileJob::avatar_url() const +const QString& GetUserProfileJob::avatarUrl() const { - return d->avatar_url; + return d->avatarUrl; } const QString& GetUserProfileJob::displayname() const @@ -136,7 +136,7 @@ BaseJob::Status GetUserProfileJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->avatar_url = fromJson(json.value("avatar_url")); + d->avatarUrl = fromJson(json.value("avatar_url")); d->displayname = fromJson(json.value("displayname")); diff --git a/jobs/generated/profile.h b/jobs/generated/profile.h index 8e2b195b..cd460ac3 100644 --- a/jobs/generated/profile.h +++ b/jobs/generated/profile.h @@ -40,7 +40,7 @@ namespace QMatrixClient class SetAvatarUrlJob : public BaseJob { public: - explicit SetAvatarUrlJob(QString userId, QString avatar_url = {}); + explicit SetAvatarUrlJob(QString userId, QString avatarUrl = {}); }; class GetAvatarUrlJob : public BaseJob @@ -50,7 +50,7 @@ namespace QMatrixClient ~GetAvatarUrlJob() override; - const QString& avatar_url() const; + const QString& avatarUrl() const; protected: Status parseJson(const QJsonDocument& data) override; @@ -66,7 +66,7 @@ namespace QMatrixClient ~GetUserProfileJob() override; - const QString& avatar_url() const; + const QString& avatarUrl() const; const QString& displayname() const; protected: -- cgit v1.2.3 From 15b2ea9e4e7077b8547f5f7f39156b0227c338e8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 30 Nov 2017 11:56:59 +0900 Subject: Connection: no more default to matrix.org; update HS URL from /login response --- connection.cpp | 8 ++++---- connection.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/connection.cpp b/connection.cpp index c29f09fe..3e4f1efb 100644 --- a/connection.cpp +++ b/connection.cpp @@ -77,10 +77,9 @@ Connection::Connection(const QUrl& server, QObject* parent) d->q = this; // All d initialization should occur before this line } -Connection::Connection() - : Connection(QUrl("https://matrix.org")) -{ -} +Connection::Connection(QObject* parent) + : Connection({}, parent) +{ } Connection::~Connection() { @@ -165,6 +164,7 @@ void Connection::doConnectToServer(const QString& user, const QString& password, deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [=] { + setHomeserver(loginJob->homeServer()); d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); }); diff --git a/connection.h b/connection.h index fc6f4588..ecebb2e7 100644 --- a/connection.h +++ b/connection.h @@ -57,8 +57,8 @@ namespace QMatrixClient using user_factory_t = std::function; + explicit Connection(QObject* parent = nullptr); explicit Connection(const QUrl& server, QObject* parent = nullptr); - Connection(); virtual ~Connection(); QHash, Room*> roomMap() const; -- cgit v1.2.3 From c18b0c5be800a39898b6f6619209d1f96d022afa Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 6 Dec 2017 19:24:03 +0900 Subject: jobs/generated: don't rely on QString's COW; firmer formatting --- jobs/generated/banning.cpp | 5 +++-- jobs/generated/banning.h | 9 +++------ jobs/generated/inviting.cpp | 3 ++- jobs/generated/inviting.h | 5 +---- jobs/generated/kicking.cpp | 3 ++- jobs/generated/kicking.h | 5 +---- jobs/generated/leaving.cpp | 5 +++-- jobs/generated/leaving.h | 9 +++------ jobs/generated/login.cpp | 9 ++------- jobs/generated/login.h | 5 +---- jobs/generated/logout.cpp | 1 + jobs/generated/logout.h | 3 --- jobs/generated/profile.cpp | 21 ++++++--------------- jobs/generated/profile.h | 21 +++++++++------------ 14 files changed, 37 insertions(+), 67 deletions(-) diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index 67b0e8a1..61677da6 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -6,13 +6,14 @@ #include "banning.h" #include "converters.h" + #include using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -BanJob::BanJob(QString roomId, QString userId, QString reason) +BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, "BanJob", basePath % "/rooms/" % roomId % "/ban", Query { } @@ -25,7 +26,7 @@ BanJob::BanJob(QString roomId, QString userId, QString reason) setRequestData(_data); } -UnbanJob::UnbanJob(QString roomId, QString userId) +UnbanJob::UnbanJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, "UnbanJob", basePath % "/rooms/" % roomId % "/unban", Query { } diff --git a/jobs/generated/banning.h b/jobs/generated/banning.h index e225a6ef..6db096ee 100644 --- a/jobs/generated/banning.h +++ b/jobs/generated/banning.h @@ -12,20 +12,17 @@ namespace QMatrixClient { - // Operations class BanJob : public BaseJob { public: - explicit BanJob(QString roomId, QString userId, QString reason = {}); - + explicit BanJob(const QString& roomId, const QString& userId, const QString& reason = {}); }; + class UnbanJob : public BaseJob { public: - explicit UnbanJob(QString roomId, QString userId); - + explicit UnbanJob(const QString& roomId, const QString& userId); }; - } // namespace QMatrixClient diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index e72bf734..78c9a2f6 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -6,13 +6,14 @@ #include "inviting.h" #include "converters.h" + #include using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -InviteUserJob::InviteUserJob(QString roomId, QString userId) +InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) : BaseJob(HttpVerb::Post, "InviteUserJob", basePath % "/rooms/" % roomId % "/invite", Query { } diff --git a/jobs/generated/inviting.h b/jobs/generated/inviting.h index e4b61beb..225cb516 100644 --- a/jobs/generated/inviting.h +++ b/jobs/generated/inviting.h @@ -12,14 +12,11 @@ namespace QMatrixClient { - // Operations class InviteUserJob : public BaseJob { public: - explicit InviteUserJob(QString roomId, QString userId); - + explicit InviteUserJob(const QString& roomId, const QString& userId); }; - } // namespace QMatrixClient diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index a0719aa2..5d6f1a64 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -6,13 +6,14 @@ #include "kicking.h" #include "converters.h" + #include using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -KickJob::KickJob(QString roomId, QString userId, QString reason) +KickJob::KickJob(const QString& roomId, const QString& userId, const QString& reason) : BaseJob(HttpVerb::Post, "KickJob", basePath % "/rooms/" % roomId % "/kick", Query { } diff --git a/jobs/generated/kicking.h b/jobs/generated/kicking.h index e7daada8..7c834e45 100644 --- a/jobs/generated/kicking.h +++ b/jobs/generated/kicking.h @@ -12,14 +12,11 @@ namespace QMatrixClient { - // Operations class KickJob : public BaseJob { public: - explicit KickJob(QString roomId, QString userId, QString reason = {}); - + explicit KickJob(const QString& roomId, const QString& userId, const QString& reason = {}); }; - } // namespace QMatrixClient diff --git a/jobs/generated/leaving.cpp b/jobs/generated/leaving.cpp index 392f1ca8..2cf7fda3 100644 --- a/jobs/generated/leaving.cpp +++ b/jobs/generated/leaving.cpp @@ -6,20 +6,21 @@ #include "leaving.h" #include "converters.h" + #include using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -LeaveRoomJob::LeaveRoomJob(QString roomId) +LeaveRoomJob::LeaveRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, "LeaveRoomJob", basePath % "/rooms/" % roomId % "/leave", Query { } ) { } -ForgetRoomJob::ForgetRoomJob(QString roomId) +ForgetRoomJob::ForgetRoomJob(const QString& roomId) : BaseJob(HttpVerb::Post, "ForgetRoomJob", basePath % "/rooms/" % roomId % "/forget", Query { } diff --git a/jobs/generated/leaving.h b/jobs/generated/leaving.h index 96304084..28ba3d92 100644 --- a/jobs/generated/leaving.h +++ b/jobs/generated/leaving.h @@ -12,20 +12,17 @@ namespace QMatrixClient { - // Operations class LeaveRoomJob : public BaseJob { public: - explicit LeaveRoomJob(QString roomId); - + explicit LeaveRoomJob(const QString& roomId); }; + class ForgetRoomJob : public BaseJob { public: - explicit ForgetRoomJob(QString roomId); - + explicit ForgetRoomJob(const QString& roomId); }; - } // namespace QMatrixClient diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 7bd905ee..892d0c81 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -6,6 +6,7 @@ #include "login.h" #include "converters.h" + #include using namespace QMatrixClient; @@ -19,10 +20,9 @@ class LoginJob::Private QString accessToken; QString homeServer; QString deviceId; - }; -LoginJob::LoginJob(QString type, QString user, QString medium, QString address, QString password, QString token, QString deviceId, QString initialDeviceDisplayName) +LoginJob::LoginJob(const QString& type, const QString& user, const QString& medium, const QString& address, const QString& password, const QString& token, const QString& deviceId, const QString& initialDeviceDisplayName) : BaseJob(HttpVerb::Post, "LoginJob", basePath % "/login", Query { }, Data { }, false @@ -75,15 +75,10 @@ const QString& LoginJob::deviceId() const BaseJob::Status LoginJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->userId = fromJson(json.value("user_id")); - d->accessToken = fromJson(json.value("access_token")); - d->homeServer = fromJson(json.value("home_server")); - d->deviceId = fromJson(json.value("device_id")); - return Success; } diff --git a/jobs/generated/login.h b/jobs/generated/login.h index 8fcca09a..1c017877 100644 --- a/jobs/generated/login.h +++ b/jobs/generated/login.h @@ -12,14 +12,12 @@ namespace QMatrixClient { - // Operations class LoginJob : public BaseJob { public: - explicit LoginJob(QString type, QString user = {}, QString medium = {}, QString address = {}, QString password = {}, QString token = {}, QString deviceId = {}, QString initialDeviceDisplayName = {}); - + explicit LoginJob(const QString& type, const QString& user = {}, const QString& medium = {}, const QString& address = {}, const QString& password = {}, const QString& token = {}, const QString& deviceId = {}, const QString& initialDeviceDisplayName = {}); ~LoginJob() override; const QString& userId() const; @@ -34,5 +32,4 @@ namespace QMatrixClient class Private; Private* d; }; - } // namespace QMatrixClient diff --git a/jobs/generated/logout.cpp b/jobs/generated/logout.cpp index c2480ff9..c250bddf 100644 --- a/jobs/generated/logout.cpp +++ b/jobs/generated/logout.cpp @@ -6,6 +6,7 @@ #include "logout.h" #include "converters.h" + #include using namespace QMatrixClient; diff --git a/jobs/generated/logout.h b/jobs/generated/logout.h index 28e85d8f..ae9e54b8 100644 --- a/jobs/generated/logout.h +++ b/jobs/generated/logout.h @@ -11,14 +11,11 @@ namespace QMatrixClient { - // Operations class LogoutJob : public BaseJob { public: explicit LogoutJob(); - }; - } // namespace QMatrixClient diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index f682ad31..42a2350f 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -6,13 +6,14 @@ #include "profile.h" #include "converters.h" + #include using namespace QMatrixClient; static const auto basePath = QStringLiteral("/_matrix/client/r0"); -SetDisplayNameJob::SetDisplayNameJob(QString userId, QString displayname) +SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displayname) : BaseJob(HttpVerb::Put, "SetDisplayNameJob", basePath % "/profile/" % userId % "/displayname", Query { } @@ -28,10 +29,9 @@ class GetDisplayNameJob::Private { public: QString displayname; - }; -GetDisplayNameJob::GetDisplayNameJob(QString userId) +GetDisplayNameJob::GetDisplayNameJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetDisplayNameJob", basePath % "/profile/" % userId % "/displayname", Query { }, Data { }, false @@ -51,13 +51,11 @@ const QString& GetDisplayNameJob::displayname() const BaseJob::Status GetDisplayNameJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->displayname = fromJson(json.value("displayname")); - return Success; } -SetAvatarUrlJob::SetAvatarUrlJob(QString userId, QString avatarUrl) +SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl) : BaseJob(HttpVerb::Put, "SetAvatarUrlJob", basePath % "/profile/" % userId % "/avatar_url", Query { } @@ -73,10 +71,9 @@ class GetAvatarUrlJob::Private { public: QString avatarUrl; - }; -GetAvatarUrlJob::GetAvatarUrlJob(QString userId) +GetAvatarUrlJob::GetAvatarUrlJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetAvatarUrlJob", basePath % "/profile/" % userId % "/avatar_url", Query { }, Data { }, false @@ -96,9 +93,7 @@ const QString& GetAvatarUrlJob::avatarUrl() const BaseJob::Status GetAvatarUrlJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->avatarUrl = fromJson(json.value("avatar_url")); - return Success; } @@ -107,10 +102,9 @@ class GetUserProfileJob::Private public: QString avatarUrl; QString displayname; - }; -GetUserProfileJob::GetUserProfileJob(QString userId) +GetUserProfileJob::GetUserProfileJob(const QString& userId) : BaseJob(HttpVerb::Get, "GetUserProfileJob", basePath % "/profile/" % userId, Query { }, Data { }, false @@ -135,11 +129,8 @@ const QString& GetUserProfileJob::displayname() const BaseJob::Status GetUserProfileJob::parseJson(const QJsonDocument& data) { auto json = data.object(); - d->avatarUrl = fromJson(json.value("avatar_url")); - d->displayname = fromJson(json.value("displayname")); - return Success; } diff --git a/jobs/generated/profile.h b/jobs/generated/profile.h index cd460ac3..30e858de 100644 --- a/jobs/generated/profile.h +++ b/jobs/generated/profile.h @@ -12,20 +12,18 @@ namespace QMatrixClient { - // Operations class SetDisplayNameJob : public BaseJob { public: - explicit SetDisplayNameJob(QString userId, QString displayname = {}); - + explicit SetDisplayNameJob(const QString& userId, const QString& displayname = {}); }; + class GetDisplayNameJob : public BaseJob { public: - explicit GetDisplayNameJob(QString userId); - + explicit GetDisplayNameJob(const QString& userId); ~GetDisplayNameJob() override; const QString& displayname() const; @@ -37,17 +35,17 @@ namespace QMatrixClient class Private; Private* d; }; + class SetAvatarUrlJob : public BaseJob { public: - explicit SetAvatarUrlJob(QString userId, QString avatarUrl = {}); - + explicit SetAvatarUrlJob(const QString& userId, const QString& avatarUrl = {}); }; + class GetAvatarUrlJob : public BaseJob { public: - explicit GetAvatarUrlJob(QString userId); - + explicit GetAvatarUrlJob(const QString& userId); ~GetAvatarUrlJob() override; const QString& avatarUrl() const; @@ -59,11 +57,11 @@ namespace QMatrixClient class Private; Private* d; }; + class GetUserProfileJob : public BaseJob { public: - explicit GetUserProfileJob(QString userId); - + explicit GetUserProfileJob(const QString& userId); ~GetUserProfileJob() override; const QString& avatarUrl() const; @@ -76,5 +74,4 @@ namespace QMatrixClient class Private; Private* d; }; - } // namespace QMatrixClient -- cgit v1.2.3 From 80588a782ce702384802e3e0cb469f71e5640ef4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 6 Dec 2017 21:21:27 +0900 Subject: Make BaseJob::Data consume QByteArray as well, not only QJsonObject This is needed to support cases of content-repo, where the request/response bodies are not JSON. --- jobs/basejob.h | 33 ++++++++++++++++++--------------- jobs/generated/banning.cpp | 4 ++-- jobs/generated/inviting.cpp | 2 +- jobs/generated/kicking.cpp | 2 +- jobs/generated/login.cpp | 2 +- jobs/generated/profile.cpp | 4 ++-- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/jobs/basejob.h b/jobs/basejob.h index f8b367c6..66812774 100644 --- a/jobs/basejob.h +++ b/jobs/basejob.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -69,29 +70,31 @@ namespace QMatrixClient } }; /** - * A simple wrapper around QJsonObject that represents a JSON data - * section of an HTTP request to a Matrix server. Facilitates - * creation from a list of key-value string pairs and dumping of - * a resulting JSON to a QByteArray. + * 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 Data : public QJsonObject + class Data { public: - using QJsonObject::QJsonObject; Data() = default; - explicit Data(const QJsonObject& o) : QJsonObject(o) { } -#if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0)) - // This method exists in QJsonObject of newer Qt versions - Data(const std::initializer_list< QPair >& l) + Data(const QByteArray& a) : _payload(a) { } + Data(const QJsonObject& jo) + : _payload(fromJson(QJsonDocument(jo))) { } + Data(const QJsonArray& ja) + : _payload(fromJson(QJsonDocument(ja))) { } + QByteArray serialize() const { - for (auto i: l) - insert(i.first, i.second); + return _payload; } -#endif - QByteArray serialize() const + + private: + static QByteArray fromJson(const QJsonDocument& jd) { - return QJsonDocument(*this).toJson(); + return jd.toJson(QJsonDocument::Compact); } + QByteArray _payload; }; /** diff --git a/jobs/generated/banning.cpp b/jobs/generated/banning.cpp index 61677da6..96f80ea8 100644 --- a/jobs/generated/banning.cpp +++ b/jobs/generated/banning.cpp @@ -19,7 +19,7 @@ BanJob::BanJob(const QString& roomId, const QString& userId, const QString& reas Query { } ) { - Data _data; + QJsonObject _data; _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); @@ -32,7 +32,7 @@ UnbanJob::UnbanJob(const QString& roomId, const QString& userId) Query { } ) { - Data _data; + QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); } diff --git a/jobs/generated/inviting.cpp b/jobs/generated/inviting.cpp index 78c9a2f6..5f89adf7 100644 --- a/jobs/generated/inviting.cpp +++ b/jobs/generated/inviting.cpp @@ -19,7 +19,7 @@ InviteUserJob::InviteUserJob(const QString& roomId, const QString& userId) Query { } ) { - Data _data; + QJsonObject _data; _data.insert("user_id", toJson(userId)); setRequestData(_data); } diff --git a/jobs/generated/kicking.cpp b/jobs/generated/kicking.cpp index 5d6f1a64..86dde629 100644 --- a/jobs/generated/kicking.cpp +++ b/jobs/generated/kicking.cpp @@ -19,7 +19,7 @@ KickJob::KickJob(const QString& roomId, const QString& userId, const QString& re Query { } ) { - Data _data; + QJsonObject _data; _data.insert("user_id", toJson(userId)); if (!reason.isEmpty()) _data.insert("reason", toJson(reason)); diff --git a/jobs/generated/login.cpp b/jobs/generated/login.cpp index 892d0c81..4c159517 100644 --- a/jobs/generated/login.cpp +++ b/jobs/generated/login.cpp @@ -28,7 +28,7 @@ LoginJob::LoginJob(const QString& type, const QString& user, const QString& medi Query { }, Data { }, false ), d(new Private) { - Data _data; + QJsonObject _data; _data.insert("type", toJson(type)); if (!user.isEmpty()) _data.insert("user", toJson(user)); diff --git a/jobs/generated/profile.cpp b/jobs/generated/profile.cpp index 42a2350f..6ec566f7 100644 --- a/jobs/generated/profile.cpp +++ b/jobs/generated/profile.cpp @@ -19,7 +19,7 @@ SetDisplayNameJob::SetDisplayNameJob(const QString& userId, const QString& displ Query { } ) { - Data _data; + QJsonObject _data; if (!displayname.isEmpty()) _data.insert("displayname", toJson(displayname)); setRequestData(_data); @@ -61,7 +61,7 @@ SetAvatarUrlJob::SetAvatarUrlJob(const QString& userId, const QString& avatarUrl Query { } ) { - Data _data; + QJsonObject _data; if (!avatarUrl.isEmpty()) _data.insert("avatar_url", toJson(avatarUrl)); setRequestData(_data); -- cgit v1.2.3 From 879e0bd17abedcc93d005eb7b66f6e9aa762200a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Dec 2017 11:40:46 +0900 Subject: New generated jobs for: room directory, 3PIDs, redactions, typing notifications and others Closes #128 (the issue has the full list of jobs arriving herewith). --- jobs/generated/administrative_contact.cpp | 119 ++++++++++++++ jobs/generated/administrative_contact.h | 70 +++++++++ jobs/generated/directory.cpp | 71 +++++++++ jobs/generated/directory.h | 46 ++++++ jobs/generated/list_public_rooms.cpp | 253 ++++++++++++++++++++++++++++++ jobs/generated/list_public_rooms.h | 101 ++++++++++++ jobs/generated/receipts.cpp | 24 +++ jobs/generated/receipts.h | 23 +++ jobs/generated/redaction.cpp | 50 ++++++ jobs/generated/redaction.h | 32 ++++ jobs/generated/third_party_membership.cpp | 28 ++++ jobs/generated/third_party_membership.h | 22 +++ jobs/generated/typing.cpp | 27 ++++ jobs/generated/typing.h | 22 +++ jobs/generated/versions.cpp | 45 ++++++ jobs/generated/versions.h | 33 ++++ jobs/generated/whoami.cpp | 48 ++++++ jobs/generated/whoami.h | 32 ++++ 18 files changed, 1046 insertions(+) create mode 100644 jobs/generated/administrative_contact.cpp create mode 100644 jobs/generated/administrative_contact.h create mode 100644 jobs/generated/directory.cpp create mode 100644 jobs/generated/directory.h create mode 100644 jobs/generated/list_public_rooms.cpp create mode 100644 jobs/generated/list_public_rooms.h create mode 100644 jobs/generated/receipts.cpp create mode 100644 jobs/generated/receipts.h create mode 100644 jobs/generated/redaction.cpp create mode 100644 jobs/generated/redaction.h create mode 100644 jobs/generated/third_party_membership.cpp create mode 100644 jobs/generated/third_party_membership.h create mode 100644 jobs/generated/typing.cpp create mode 100644 jobs/generated/typing.h create mode 100644 jobs/generated/versions.cpp create mode 100644 jobs/generated/versions.h create mode 100644 jobs/generated/whoami.cpp create mode 100644 jobs/generated/whoami.h diff --git a/jobs/generated/administrative_contact.cpp b/jobs/generated/administrative_contact.cpp new file mode 100644 index 00000000..705c5d54 --- /dev/null +++ b/jobs/generated/administrative_contact.cpp @@ -0,0 +1,119 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "administrative_contact.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +GetAccount3PIDsJob::ThirdPartyIdentifier::operator QJsonObject() const +{ + QJsonObject o; + o.insert("medium", toJson(medium)); + o.insert("address", toJson(address)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + GetAccount3PIDsJob::ThirdPartyIdentifier operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + GetAccount3PIDsJob::ThirdPartyIdentifier result; + result.medium = + fromJson(o.value("medium")); + result.address = + fromJson(o.value("address")); + + return result; + } + }; +} // namespace QMatrixClient + +class GetAccount3PIDsJob::Private +{ + public: + QVector threepids; +}; + +GetAccount3PIDsJob::GetAccount3PIDsJob() + : BaseJob(HttpVerb::Get, "GetAccount3PIDsJob", + basePath % "/account/3pid", + Query { } + ), d(new Private) +{ } + +GetAccount3PIDsJob::~GetAccount3PIDsJob() +{ + delete d; +} + +const QVector& GetAccount3PIDsJob::threepids() const +{ + return d->threepids; +} + +BaseJob::Status GetAccount3PIDsJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->threepids = fromJson>(json.value("threepids")); + return Success; +} + +Post3PIDsJob::ThreePidCredentials::operator QJsonObject() const +{ + QJsonObject o; + o.insert("client_secret", toJson(clientSecret)); + o.insert("id_server", toJson(idServer)); + o.insert("sid", toJson(sid)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + Post3PIDsJob::ThreePidCredentials operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + Post3PIDsJob::ThreePidCredentials result; + result.clientSecret = + fromJson(o.value("client_secret")); + result.idServer = + fromJson(o.value("id_server")); + result.sid = + fromJson(o.value("sid")); + + return result; + } + }; +} // namespace QMatrixClient + +Post3PIDsJob::Post3PIDsJob(const ThreePidCredentials& threePidCreds, bool bind) + : BaseJob(HttpVerb::Post, "Post3PIDsJob", + basePath % "/account/3pid", + Query { } + ) +{ + QJsonObject _data; + _data.insert("three_pid_creds", toJson(threePidCreds)); + _data.insert("bind", toJson(bind)); + setRequestData(_data); +} + +RequestTokenTo3PIDJob::RequestTokenTo3PIDJob() + : BaseJob(HttpVerb::Post, "RequestTokenTo3PIDJob", + basePath % "/account/3pid/email/requestToken", + Query { }, Data { }, false + ) +{ } + diff --git a/jobs/generated/administrative_contact.h b/jobs/generated/administrative_contact.h new file mode 100644 index 00000000..fa6beba9 --- /dev/null +++ b/jobs/generated/administrative_contact.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + +#include "converters.h" + +namespace QMatrixClient +{ + // Operations + + class GetAccount3PIDsJob : public BaseJob + { + public: + // Inner data structures + + struct ThirdPartyIdentifier + { + QString medium; + QString address; + + operator QJsonObject() const; + }; + + // End of inner data structures + + explicit GetAccount3PIDsJob(); + ~GetAccount3PIDsJob() override; + + const QVector& threepids() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; + + class Post3PIDsJob : public BaseJob + { + public: + // Inner data structures + + struct ThreePidCredentials + { + QString clientSecret; + QString idServer; + QString sid; + + operator QJsonObject() const; + }; + + // End of inner data structures + + explicit Post3PIDsJob(const ThreePidCredentials& threePidCreds, bool bind = {}); + }; + + class RequestTokenTo3PIDJob : public BaseJob + { + public: + explicit RequestTokenTo3PIDJob(); + }; +} // namespace QMatrixClient diff --git a/jobs/generated/directory.cpp b/jobs/generated/directory.cpp new file mode 100644 index 00000000..dcec75ac --- /dev/null +++ b/jobs/generated/directory.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "directory.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0/directory"); + +SetRoomAliasJob::SetRoomAliasJob(const QString& roomAlias, const QString& roomId) + : BaseJob(HttpVerb::Put, "SetRoomAliasJob", + basePath % "/room/" % roomAlias, + Query { } + ) +{ + QJsonObject _data; + if (!roomId.isEmpty()) + _data.insert("room_id", toJson(roomId)); + setRequestData(_data); +} + +class GetRoomIdByAliasJob::Private +{ + public: + QString roomId; + QVector servers; +}; + +GetRoomIdByAliasJob::GetRoomIdByAliasJob(const QString& roomAlias) + : BaseJob(HttpVerb::Get, "GetRoomIdByAliasJob", + basePath % "/room/" % roomAlias, + Query { }, Data { }, false + ), d(new Private) +{ } + +GetRoomIdByAliasJob::~GetRoomIdByAliasJob() +{ + delete d; +} + +const QString& GetRoomIdByAliasJob::roomId() const +{ + return d->roomId; +} + +const QVector& GetRoomIdByAliasJob::servers() const +{ + return d->servers; +} + +BaseJob::Status GetRoomIdByAliasJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->roomId = fromJson(json.value("room_id")); + d->servers = fromJson>(json.value("servers")); + return Success; +} + +DeleteRoomAliasJob::DeleteRoomAliasJob(const QString& roomAlias) + : BaseJob(HttpVerb::Delete, "DeleteRoomAliasJob", + basePath % "/room/" % roomAlias, + Query { } + ) +{ } + diff --git a/jobs/generated/directory.h b/jobs/generated/directory.h new file mode 100644 index 00000000..1dd4e7ed --- /dev/null +++ b/jobs/generated/directory.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include +#include + + +namespace QMatrixClient +{ + // Operations + + class SetRoomAliasJob : public BaseJob + { + public: + explicit SetRoomAliasJob(const QString& roomAlias, const QString& roomId = {}); + }; + + class GetRoomIdByAliasJob : public BaseJob + { + public: + explicit GetRoomIdByAliasJob(const QString& roomAlias); + ~GetRoomIdByAliasJob() override; + + const QString& roomId() const; + const QVector& servers() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; + + class DeleteRoomAliasJob : public BaseJob + { + public: + explicit DeleteRoomAliasJob(const QString& roomAlias); + }; +} // namespace QMatrixClient diff --git a/jobs/generated/list_public_rooms.cpp b/jobs/generated/list_public_rooms.cpp new file mode 100644 index 00000000..8a96966f --- /dev/null +++ b/jobs/generated/list_public_rooms.cpp @@ -0,0 +1,253 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "list_public_rooms.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +GetPublicRoomsJob::PublicRoomsChunk::operator QJsonObject() const +{ + QJsonObject o; + o.insert("aliases", toJson(aliases)); + o.insert("canonical_alias", toJson(canonicalAlias)); + o.insert("name", toJson(name)); + o.insert("num_joined_members", toJson(numJoinedMembers)); + o.insert("room_id", toJson(roomId)); + o.insert("topic", toJson(topic)); + o.insert("world_readable", toJson(worldReadable)); + o.insert("guest_can_join", toJson(guestCanJoin)); + o.insert("avatar_url", toJson(avatarUrl)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + GetPublicRoomsJob::PublicRoomsChunk operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + GetPublicRoomsJob::PublicRoomsChunk result; + result.aliases = + fromJson>(o.value("aliases")); + result.canonicalAlias = + fromJson(o.value("canonical_alias")); + result.name = + fromJson(o.value("name")); + result.numJoinedMembers = + fromJson(o.value("num_joined_members")); + result.roomId = + fromJson(o.value("room_id")); + result.topic = + fromJson(o.value("topic")); + result.worldReadable = + fromJson(o.value("world_readable")); + result.guestCanJoin = + fromJson(o.value("guest_can_join")); + result.avatarUrl = + fromJson(o.value("avatar_url")); + + return result; + } + }; +} // namespace QMatrixClient + +class GetPublicRoomsJob::Private +{ + public: + QVector chunk; + QString nextBatch; + QString prevBatch; + double totalRoomCountEstimate; +}; + +GetPublicRoomsJob::GetPublicRoomsJob(double limit, const QString& since, const QString& server) + : BaseJob(HttpVerb::Get, "GetPublicRoomsJob", + basePath % "/publicRooms", + Query { + { "limit", toJson(limit).toString() }, + { "since", toJson(since).toString() }, + { "server", toJson(server).toString() } + }, Data { }, false + ), d(new Private) +{ } + +GetPublicRoomsJob::~GetPublicRoomsJob() +{ + delete d; +} + +const QVector& GetPublicRoomsJob::chunk() const +{ + return d->chunk; +} + +const QString& GetPublicRoomsJob::nextBatch() const +{ + return d->nextBatch; +} + +const QString& GetPublicRoomsJob::prevBatch() const +{ + return d->prevBatch; +} + +double GetPublicRoomsJob::totalRoomCountEstimate() const +{ + return d->totalRoomCountEstimate; +} + +BaseJob::Status GetPublicRoomsJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + if (!json.contains("chunk")) + return { JsonParseError, + "The key 'chunk' not found in the response" }; + d->chunk = fromJson>(json.value("chunk")); + d->nextBatch = fromJson(json.value("next_batch")); + d->prevBatch = fromJson(json.value("prev_batch")); + d->totalRoomCountEstimate = fromJson(json.value("total_room_count_estimate")); + return Success; +} + +QueryPublicRoomsJob::Filter::operator QJsonObject() const +{ + QJsonObject o; + o.insert("generic_search_term", toJson(genericSearchTerm)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + QueryPublicRoomsJob::Filter operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + QueryPublicRoomsJob::Filter result; + result.genericSearchTerm = + fromJson(o.value("generic_search_term")); + + return result; + } + }; +} // namespace QMatrixClient + +QueryPublicRoomsJob::PublicRoomsChunk::operator QJsonObject() const +{ + QJsonObject o; + o.insert("aliases", toJson(aliases)); + o.insert("canonical_alias", toJson(canonicalAlias)); + o.insert("name", toJson(name)); + o.insert("num_joined_members", toJson(numJoinedMembers)); + o.insert("room_id", toJson(roomId)); + o.insert("topic", toJson(topic)); + o.insert("world_readable", toJson(worldReadable)); + o.insert("guest_can_join", toJson(guestCanJoin)); + o.insert("avatar_url", toJson(avatarUrl)); + + return o; +} +namespace QMatrixClient +{ + template <> struct FromJson + { + QueryPublicRoomsJob::PublicRoomsChunk operator()(QJsonValue jv) + { + QJsonObject o = jv.toObject(); + QueryPublicRoomsJob::PublicRoomsChunk result; + result.aliases = + fromJson>(o.value("aliases")); + result.canonicalAlias = + fromJson(o.value("canonical_alias")); + result.name = + fromJson(o.value("name")); + result.numJoinedMembers = + fromJson(o.value("num_joined_members")); + result.roomId = + fromJson(o.value("room_id")); + result.topic = + fromJson(o.value("topic")); + result.worldReadable = + fromJson(o.value("world_readable")); + result.guestCanJoin = + fromJson(o.value("guest_can_join")); + result.avatarUrl = + fromJson(o.value("avatar_url")); + + return result; + } + }; +} // namespace QMatrixClient + +class QueryPublicRoomsJob::Private +{ + public: + QVector chunk; + QString nextBatch; + QString prevBatch; + double totalRoomCountEstimate; +}; + +QueryPublicRoomsJob::QueryPublicRoomsJob(const QString& server, double limit, const QString& since, const Filter& filter) + : BaseJob(HttpVerb::Post, "QueryPublicRoomsJob", + basePath % "/publicRooms", + Query { + { "server", toJson(server).toString() } + } + ), d(new Private) +{ + QJsonObject _data; + _data.insert("limit", toJson(limit)); + if (!since.isEmpty()) + _data.insert("since", toJson(since)); + _data.insert("filter", toJson(filter)); + setRequestData(_data); +} + +QueryPublicRoomsJob::~QueryPublicRoomsJob() +{ + delete d; +} + +const QVector& QueryPublicRoomsJob::chunk() const +{ + return d->chunk; +} + +const QString& QueryPublicRoomsJob::nextBatch() const +{ + return d->nextBatch; +} + +const QString& QueryPublicRoomsJob::prevBatch() const +{ + return d->prevBatch; +} + +double QueryPublicRoomsJob::totalRoomCountEstimate() const +{ + return d->totalRoomCountEstimate; +} + +BaseJob::Status QueryPublicRoomsJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + if (!json.contains("chunk")) + return { JsonParseError, + "The key 'chunk' not found in the response" }; + d->chunk = fromJson>(json.value("chunk")); + d->nextBatch = fromJson(json.value("next_batch")); + d->prevBatch = fromJson(json.value("prev_batch")); + d->totalRoomCountEstimate = fromJson(json.value("total_room_count_estimate")); + return Success; +} + diff --git a/jobs/generated/list_public_rooms.h b/jobs/generated/list_public_rooms.h new file mode 100644 index 00000000..74dd8626 --- /dev/null +++ b/jobs/generated/list_public_rooms.h @@ -0,0 +1,101 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include +#include + +#include "converters.h" + +namespace QMatrixClient +{ + // Operations + + class GetPublicRoomsJob : public BaseJob + { + public: + // Inner data structures + + struct PublicRoomsChunk + { + QVector aliases; + QString canonicalAlias; + QString name; + double numJoinedMembers; + QString roomId; + QString topic; + bool worldReadable; + bool guestCanJoin; + QString avatarUrl; + + operator QJsonObject() const; + }; + + // End of inner data structures + + explicit GetPublicRoomsJob(double limit = {}, const QString& since = {}, const QString& server = {}); + ~GetPublicRoomsJob() override; + + const QVector& chunk() const; + const QString& nextBatch() const; + const QString& prevBatch() const; + double totalRoomCountEstimate() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; + + class QueryPublicRoomsJob : public BaseJob + { + public: + // Inner data structures + + struct Filter + { + QString genericSearchTerm; + + operator QJsonObject() const; + }; + + struct PublicRoomsChunk + { + QVector aliases; + QString canonicalAlias; + QString name; + double numJoinedMembers; + QString roomId; + QString topic; + bool worldReadable; + bool guestCanJoin; + QString avatarUrl; + + operator QJsonObject() const; + }; + + // End of inner data structures + + explicit QueryPublicRoomsJob(const QString& server = {}, double limit = {}, const QString& since = {}, const Filter& filter = {}); + ~QueryPublicRoomsJob() override; + + const QVector& chunk() const; + const QString& nextBatch() const; + const QString& prevBatch() const; + double totalRoomCountEstimate() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; +} // namespace QMatrixClient diff --git a/jobs/generated/receipts.cpp b/jobs/generated/receipts.cpp new file mode 100644 index 00000000..2820b583 --- /dev/null +++ b/jobs/generated/receipts.cpp @@ -0,0 +1,24 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "receipts.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +PostReceiptJob::PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt) + : BaseJob(HttpVerb::Post, "PostReceiptJob", + basePath % "/rooms/" % roomId % "/receipt/" % receiptType % "/" % eventId, + Query { } + ) +{ + setRequestData(Data(receipt)); +} + diff --git a/jobs/generated/receipts.h b/jobs/generated/receipts.h new file mode 100644 index 00000000..6f36d7fc --- /dev/null +++ b/jobs/generated/receipts.h @@ -0,0 +1,23 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include +#include + + +namespace QMatrixClient +{ + // Operations + + class PostReceiptJob : public BaseJob + { + public: + explicit PostReceiptJob(const QString& roomId, const QString& receiptType, const QString& eventId, const QJsonObject& receipt = {}); + }; +} // namespace QMatrixClient diff --git a/jobs/generated/redaction.cpp b/jobs/generated/redaction.cpp new file mode 100644 index 00000000..a9b8ed7e --- /dev/null +++ b/jobs/generated/redaction.cpp @@ -0,0 +1,50 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "redaction.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +class RedactEventJob::Private +{ + public: + QString eventId; +}; + +RedactEventJob::RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason) + : BaseJob(HttpVerb::Put, "RedactEventJob", + basePath % "/rooms/" % roomId % "/redact/" % eventId % "/" % txnId, + Query { } + ), d(new Private) +{ + QJsonObject _data; + if (!reason.isEmpty()) + _data.insert("reason", toJson(reason)); + setRequestData(_data); +} + +RedactEventJob::~RedactEventJob() +{ + delete d; +} + +const QString& RedactEventJob::eventId() const +{ + return d->eventId; +} + +BaseJob::Status RedactEventJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->eventId = fromJson(json.value("event_id")); + return Success; +} + diff --git a/jobs/generated/redaction.h b/jobs/generated/redaction.h new file mode 100644 index 00000000..600e0daa --- /dev/null +++ b/jobs/generated/redaction.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + // Operations + + class RedactEventJob : public BaseJob + { + public: + explicit RedactEventJob(const QString& roomId, const QString& eventId, const QString& txnId, const QString& reason = {}); + ~RedactEventJob() override; + + const QString& eventId() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; +} // namespace QMatrixClient diff --git a/jobs/generated/third_party_membership.cpp b/jobs/generated/third_party_membership.cpp new file mode 100644 index 00000000..7a2aa4f4 --- /dev/null +++ b/jobs/generated/third_party_membership.cpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "third_party_membership.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +InviteBy3PIDJob::InviteBy3PIDJob(const QString& roomId, const QString& idServer, const QString& medium, const QString& address) + : BaseJob(HttpVerb::Post, "InviteBy3PIDJob", + basePath % "/rooms/" % roomId % "/invite", + Query { } + ) +{ + QJsonObject _data; + _data.insert("id_server", toJson(idServer)); + _data.insert("medium", toJson(medium)); + _data.insert("address", toJson(address)); + setRequestData(_data); +} + diff --git a/jobs/generated/third_party_membership.h b/jobs/generated/third_party_membership.h new file mode 100644 index 00000000..6c1193ed --- /dev/null +++ b/jobs/generated/third_party_membership.h @@ -0,0 +1,22 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + // Operations + + class InviteBy3PIDJob : public BaseJob + { + public: + explicit InviteBy3PIDJob(const QString& roomId, const QString& idServer, const QString& medium, const QString& address); + }; +} // namespace QMatrixClient diff --git a/jobs/generated/typing.cpp b/jobs/generated/typing.cpp new file mode 100644 index 00000000..44bbb131 --- /dev/null +++ b/jobs/generated/typing.cpp @@ -0,0 +1,27 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "typing.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +SetTypingJob::SetTypingJob(const QString& userId, const QString& roomId, bool typing, int timeout) + : BaseJob(HttpVerb::Put, "SetTypingJob", + basePath % "/rooms/" % roomId % "/typing/" % userId, + Query { } + ) +{ + QJsonObject _data; + _data.insert("typing", toJson(typing)); + _data.insert("timeout", toJson(timeout)); + setRequestData(_data); +} + diff --git a/jobs/generated/typing.h b/jobs/generated/typing.h new file mode 100644 index 00000000..e20bca1a --- /dev/null +++ b/jobs/generated/typing.h @@ -0,0 +1,22 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + // Operations + + class SetTypingJob : public BaseJob + { + public: + explicit SetTypingJob(const QString& userId, const QString& roomId, bool typing, int timeout = {}); + }; +} // namespace QMatrixClient diff --git a/jobs/generated/versions.cpp b/jobs/generated/versions.cpp new file mode 100644 index 00000000..66b31290 --- /dev/null +++ b/jobs/generated/versions.cpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "versions.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client"); + +class GetVersionsJob::Private +{ + public: + QVector versions; +}; + +GetVersionsJob::GetVersionsJob() + : BaseJob(HttpVerb::Get, "GetVersionsJob", + basePath % "/versions", + Query { }, Data { }, false + ), d(new Private) +{ } + +GetVersionsJob::~GetVersionsJob() +{ + delete d; +} + +const QVector& GetVersionsJob::versions() const +{ + return d->versions; +} + +BaseJob::Status GetVersionsJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + d->versions = fromJson>(json.value("versions")); + return Success; +} + diff --git a/jobs/generated/versions.h b/jobs/generated/versions.h new file mode 100644 index 00000000..eab8cf9e --- /dev/null +++ b/jobs/generated/versions.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include +#include + + +namespace QMatrixClient +{ + // Operations + + class GetVersionsJob : public BaseJob + { + public: + explicit GetVersionsJob(); + ~GetVersionsJob() override; + + const QVector& versions() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; +} // namespace QMatrixClient diff --git a/jobs/generated/whoami.cpp b/jobs/generated/whoami.cpp new file mode 100644 index 00000000..dce091ec --- /dev/null +++ b/jobs/generated/whoami.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#include "whoami.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +class GetTokenOwnerJob::Private +{ + public: + QString userId; +}; + +GetTokenOwnerJob::GetTokenOwnerJob() + : BaseJob(HttpVerb::Get, "GetTokenOwnerJob", + basePath % "/account/whoami", + Query { } + ), d(new Private) +{ } + +GetTokenOwnerJob::~GetTokenOwnerJob() +{ + delete d; +} + +const QString& GetTokenOwnerJob::userId() const +{ + return d->userId; +} + +BaseJob::Status GetTokenOwnerJob::parseJson(const QJsonDocument& data) +{ + auto json = data.object(); + if (!json.contains("user_id")) + return { JsonParseError, + "The key 'user_id' not found in the response" }; + d->userId = fromJson(json.value("user_id")); + return Success; +} + diff --git a/jobs/generated/whoami.h b/jobs/generated/whoami.h new file mode 100644 index 00000000..1b04f337 --- /dev/null +++ b/jobs/generated/whoami.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + // Operations + + class GetTokenOwnerJob : public BaseJob + { + public: + explicit GetTokenOwnerJob(); + ~GetTokenOwnerJob() override; + + const QString& userId() const; + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + class Private; + Private* d; + }; +} // namespace QMatrixClient -- cgit v1.2.3 From 6453ab3ea9a2781940d60e49bc28c38db0fbac79 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Dec 2017 17:55:25 +0900 Subject: PasswordLogin: Fix the legacy job class to compile with new BaseJob::Data --- jobs/passwordlogin.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/jobs/passwordlogin.cpp b/jobs/passwordlogin.cpp index 9af025e6..8abfe66a 100644 --- a/jobs/passwordlogin.cpp +++ b/jobs/passwordlogin.cpp @@ -29,18 +29,15 @@ class PasswordLogin::Private }; PasswordLogin::PasswordLogin(QString user, QString password) - : BaseJob(HttpVerb::Post, "PasswordLogin" - , "_matrix/client/r0/login" - , Query() - , Data( - { { "type", QStringLiteral("m.login.password") } - , { "user", user } - , { "password", password } - }) - , false - ) + : BaseJob(HttpVerb::Post, "PasswordLogin", + "_matrix/client/r0/login", Query(), Data(), false) , d(new Private) { + QJsonObject _data; + _data.insert("type", QStringLiteral("m.login.password")); + _data.insert("user", user); + _data.insert("password", password); + setRequestData(_data); } PasswordLogin::~PasswordLogin() -- cgit v1.2.3 From 1796fb03406bc0a12a513218f6aaa34e9cd60734 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 7 Dec 2017 17:55:50 +0900 Subject: Fix compilation with Qt 5.2 --- converters.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/converters.h b/converters.h index 9b78eb37..00d1d339 100644 --- a/converters.h +++ b/converters.h @@ -46,7 +46,11 @@ namespace QMatrixClient inline QJsonValue toJson(const QByteArray& bytes) { - return QJsonValue(static_cast(bytes)); +#if QT_VERSION < QT_VERSION_CHECK(5, 3, 0) + return QJsonValue(QLatin1String(bytes.constData())); +#else + return QJsonValue(bytes.constData()); +#endif } template -- cgit v1.2.3 From 404e37d64a71baf3c9ca44ed2a16288876fb2995 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 15:29:40 +0900 Subject: Fix setting the homeserver hostname (not URL) coming from /login --- connection.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/connection.cpp b/connection.cpp index 3e4f1efb..368abc8e 100644 --- a/connection.cpp +++ b/connection.cpp @@ -164,7 +164,8 @@ void Connection::doConnectToServer(const QString& user, const QString& password, deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [=] { - setHomeserver(loginJob->homeServer()); + setHomeserver(QUrl::fromUserInput( + homeserver().scheme() + "://" + loginJob->homeServer())); d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); }); @@ -488,11 +489,11 @@ QByteArray Connection::generateTxnId() void Connection::setHomeserver(const QUrl& url) { - if (d->data->baseUrl() == url) + if (homeserver() == url) return; d->data->setBaseUrl(url); - emit homeserverChanged(url); + emit homeserverChanged(homeserver()); } static constexpr int CACHE_VERSION_MAJOR = 1; -- cgit v1.2.3 From c46fccdfacaa299eee9bfb96d86d2c784db3e9ca Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 15:32:26 +0900 Subject: Elaborate a deprecation comment --- room.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/room.h b/room.h index f7aa363c..702c22f3 100644 --- a/room.h +++ b/room.h @@ -159,7 +159,8 @@ namespace QMatrixClient void postMessage(const QString& plainText, MessageEventType type = MessageEventType::Text); void postMessage(const RoomMessageEvent& event); - /** @deprecated */ + /** @deprecated If you have a custom event type, construct the event + * and pass it as a whole to postMessage() */ void postMessage(const QString& type, const QString& plainText); void setTopic(const QString& newTopic); -- cgit v1.2.3 From 2b4952b6686f647c5470033de2cb14577cfb41f1 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Dec 2017 16:27:32 +0900 Subject: Log room creation only once --- connection.cpp | 1 - room.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index 368abc8e..d4f6bf61 100644 --- a/connection.cpp +++ b/connection.cpp @@ -446,7 +446,6 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return nullptr; } d->roomMap.insert(roomKey, room); - qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; emit newRoom(room); } if (joinState == JoinState::Invite) diff --git a/room.cpp b/room.cpp index 6e6e7e39..3beac1b2 100644 --- a/room.cpp +++ b/room.cpp @@ -146,7 +146,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; - qCDebug(MAIN) << "New Room:" << id; + qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } Room::~Room() -- cgit v1.2.3 From 552031b1809a9bb7e596d12e21e50c40c91b0f21 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Dec 2017 17:36:04 +0900 Subject: Use the generated PostReceiptJob; don't compile unused job classes Rewire Connection::postReceipt() to the generated job too; this call is still deprecated though. --- CMakeLists.txt | 2 -- connection.cpp | 4 ++-- room.cpp | 5 +++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 29a5c811..1cf871a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,10 +71,8 @@ set(libqmatrixclient_SRCS events/receiptevent.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp - jobs/passwordlogin.cpp jobs/sendeventjob.cpp jobs/setroomstatejob.cpp - jobs/postreceiptjob.cpp jobs/joinroomjob.cpp jobs/roommessagesjob.cpp jobs/syncjob.cpp diff --git a/connection.cpp b/connection.cpp index d4f6bf61..4fb661f3 100644 --- a/connection.cpp +++ b/connection.cpp @@ -23,8 +23,8 @@ #include "room.h" #include "jobs/generated/login.h" #include "jobs/generated/logout.h" +#include "jobs/generated/receipts.h" #include "jobs/sendeventjob.h" -#include "jobs/postreceiptjob.h" #include "jobs/joinroomjob.h" #include "jobs/roommessagesjob.h" #include "jobs/syncjob.h" @@ -281,7 +281,7 @@ void Connection::postMessage(Room* room, const QString& type, const QString& mes PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { - return callApi(room->id(), event->id()); + return callApi(room->id(), "m.read", event->id()); } JoinRoomJob* Connection::joinRoom(const QString& roomAlias) diff --git a/room.cpp b/room.cpp index 3beac1b2..764f4d23 100644 --- a/room.cpp +++ b/room.cpp @@ -22,6 +22,7 @@ #include "jobs/generated/inviting.h" #include "jobs/generated/banning.h" #include "jobs/generated/leaving.h" +#include "jobs/generated/receipts.h" #include "jobs/setroomstatejob.h" #include "events/simplestateevents.h" #include "events/roomavatarevent.h" @@ -30,7 +31,6 @@ #include "events/receiptevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" -#include "jobs/postreceiptjob.h" #include "avatar.h" #include "connection.h" #include "user.h" @@ -283,7 +283,8 @@ void Room::markMessagesAsRead(Room::rev_iter_t upToMarker) { if ((*markers.second)->senderId() != localUser()->id()) { - connection()->callApi(id(), (*markers.second)->id()); + connection()->callApi( + id(), "m.read", (*markers.second)->id()); break; } } -- cgit v1.2.3 From 6572b4836231597de6a340296ee07fbeb13ebece Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Dec 2017 22:23:21 +0900 Subject: EventType: Add more enumeration values --- events/event.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/events/event.h b/events/event.h index cc99b57b..20e7012d 100644 --- a/events/event.h +++ b/events/event.h @@ -37,11 +37,12 @@ namespace QMatrixClient Typing, Receipt, RoomEventBase = 0x1000, RoomMessage = RoomEventBase + 1, - RoomEncryptedMessage, + RoomEncryptedMessage, Redaction, RoomStateEventBase = 0x1800, RoomName = RoomStateEventBase + 1, RoomAliases, RoomCanonicalAlias, RoomMember, RoomTopic, - RoomAvatar, RoomEncryption, + RoomAvatar, RoomEncryption, RoomCreate, RoomJoinRules, + RoomPowerLevels, Reserved = 0x2000 }; -- cgit v1.2.3 From c38366210643ef0956884531910d7ece3d6a4cac Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 9 Dec 2017 22:24:16 +0900 Subject: Introduce RoomEventsView Will be used in Room to work with ranges of events. --- events/event.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/events/event.h b/events/event.h index 20e7012d..8681f4db 100644 --- a/events/event.h +++ b/events/event.h @@ -156,6 +156,27 @@ namespace QMatrixClient }; using RoomEvents = EventsBatch; + /** + * Conceptually similar to QStringView (but much more primitive), it's a + * simple abstraction over a pair of RoomEvents::const_iterator values + * referring to the beginning and the end of a range in a RoomEvents + * container. + */ + struct RoomEventsView + { + RoomEvents::const_iterator from; + RoomEvents::const_iterator to; + + RoomEvents::size_type size() const + { + Q_ASSERT(std::distance(from, to) >= 0); + return RoomEvents::size_type(std::distance(from, to)); + } + bool empty() const { return from == to; } + RoomEvents::const_iterator begin() const { return from; } + RoomEvents::const_iterator end() const { return to; } + }; + template class StateEvent: public RoomEvent { -- cgit v1.2.3 From 6ecdefd8463c0b393dc51535ddb4b4283b5891d8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 09:49:08 +0900 Subject: RedactionEvent and RoomEvent::redactedBecause() A RoomEvent now has an optional pointer to a RedactionEvent that is non-null if the room event is redacted. transactionId and serverTimestamp are only filled if the event is not redacted. There's no way to construct a redacted event as of yet. --- events/event.cpp | 31 ++++++++++++++++++++++++++----- events/event.h | 18 ++++++++++++++++-- events/redactionevent.cpp | 1 + events/redactionevent.h | 43 +++++++++++++++++++++++++++++++++++++++++++ events/roommessageevent.cpp | 6 +++--- libqmatrixclient.pri | 2 ++ 6 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 events/redactionevent.cpp create mode 100644 events/redactionevent.h diff --git a/events/event.cpp b/events/event.cpp index 44b742c1..d779f293 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -24,6 +24,7 @@ #include "roomavatarevent.h" #include "typingevent.h" #include "receiptevent.h" +#include "redactionevent.h" #include "logging.h" #include @@ -33,7 +34,8 @@ using namespace QMatrixClient; Event::Event(Type type, const QJsonObject& rep) : _type(type), _originalJson(rep) { - if (!rep.contains("content")) + if (!rep.contains("content") && + !rep.value("unsigned").toObject().contains("redacted_because")) { qCWarning(EVENTS) << "Event without 'content' node"; qCWarning(EVENTS) << formatJson << rep; @@ -80,13 +82,12 @@ Event* Event::fromJson(const QJsonObject& obj) TypingEvent, ReceiptEvent>(obj, obj["type"].toString()); } +RoomEvent::RoomEvent(Event::Type type) : Event(type) { } + RoomEvent::RoomEvent(Type type, const QJsonObject& rep) : Event(type, rep), _id(rep["event_id"].toString()) - , _serverTimestamp( - QMatrixClient::fromJson(rep["origin_server_ts"])) , _roomId(rep["room_id"].toString()) , _senderId(rep["sender"].toString()) - , _txnId(rep["unsigned"].toObject().value("transactionId").toString()) { // if (_id.isEmpty()) // { @@ -103,10 +104,29 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& rep) // qCWarning(EVENTS) << "Can't find sender in a room event"; // qCWarning(EVENTS) << formatJson << rep; // } + auto unsignedData = rep["unsigned"].toObject(); + auto redaction = unsignedData.value("redacted_because"); + if (redaction.isObject()) + { + _redactedBecause.reset(new RedactionEvent(redaction.toObject())); + return; + } + + _serverTimestamp = + QMatrixClient::fromJson(rep["origin_server_ts"]); + _txnId = unsignedData.value("transactionId").toString(); if (!_txnId.isEmpty()) qCDebug(EVENTS) << "Event transactionId:" << _txnId; } +RoomEvent::~RoomEvent() +{ /* Let QScopedPointer do its job */ } + +QString RoomEvent::redactionReason() const +{ + return isRedacted() ? _redactedBecause->reason() : QString{}; +} + void RoomEvent::addId(const QString& id) { Q_ASSERT(_id.isEmpty()); Q_ASSERT(!id.isEmpty()); @@ -118,5 +138,6 @@ RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) return makeIfMatches(obj, obj["type"].toString()); + RoomAvatarEvent, EncryptionEvent, RedactionEvent> + (obj, obj["type"].toString()); } diff --git a/events/event.h b/events/event.h index 8681f4db..62d9c23b 100644 --- a/events/event.h +++ b/events/event.h @@ -104,6 +104,8 @@ namespace QMatrixClient }; using Events = EventsBatch; + class RedactionEvent; + /** This class corresponds to m.room.* events */ class RoomEvent : public Event { @@ -112,15 +114,26 @@ namespace QMatrixClient Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString transactionId READ transactionId CONSTANT) + Q_PROPERTY(QString redactionReason READ redactionReason) + Q_PROPERTY(bool isRedacted READ isRedacted) + Q_PROPERTY(QString transactionId READ transactionId) public: - explicit RoomEvent(Type type) : Event(type) { } + // RedactionEvent is an incomplete type here so we cannot inline + // constructors and destructors + explicit RoomEvent(Type type); RoomEvent(Type type, const QJsonObject& rep); + ~RoomEvent(); const QString& id() const { return _id; } const QDateTime& timestamp() const { return _serverTimestamp; } const QString& roomId() const { return _roomId; } const QString& senderId() const { return _senderId; } + bool isRedacted() const { return redactedBecause(); } + RedactionEvent* redactedBecause() const + { + return _redactedBecause.data(); + } + QString redactionReason() const; const QString& transactionId() const { return _txnId; } /** @@ -152,6 +165,7 @@ namespace QMatrixClient QDateTime _serverTimestamp; QString _roomId; QString _senderId; + QScopedPointer _redactedBecause; QString _txnId; }; using RoomEvents = EventsBatch; diff --git a/events/redactionevent.cpp b/events/redactionevent.cpp new file mode 100644 index 00000000..bf467718 --- /dev/null +++ b/events/redactionevent.cpp @@ -0,0 +1 @@ +#include "redactionevent.h" diff --git a/events/redactionevent.h b/events/redactionevent.h new file mode 100644 index 00000000..fa6902ab --- /dev/null +++ b/events/redactionevent.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "event.h" + +namespace QMatrixClient +{ + class RedactionEvent : public RoomEvent + { + public: + static constexpr const char* const TypeId = "m.room.redaction"; + + RedactionEvent(const QJsonObject& obj) + : RoomEvent(Type::Redaction, obj) + , _redactedEvent(obj.value("redacts").toString()) + , _reason(contentJson().value("reason").toString()) + { } + + const QString& redactedEvent() const { return _redactedEvent; } + const QString& reason() const { return _reason; } + + private: + QString _redactedEvent; + QString _reason; + }; +} // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index f06474e9..bc41abf6 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -58,7 +58,6 @@ QString msgTypeToJson(MsgType enumType) if (it != msgTypes.end()) return it->jsonType; - qCCritical(EVENTS) << "Unknown msgtype:" << enumType; return {}; } @@ -69,8 +68,7 @@ MsgType jsonToMsgType(const QString& jsonType) if (it != msgTypes.end()) return it->enumType; - qCCritical(EVENTS) << "Unknown msgtype:" << jsonType; - return {}; + return MsgType::Unknown; } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, @@ -81,6 +79,8 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) : RoomEvent(Type::RoomMessage, obj), _content(nullptr) { + if (isRedacted()) + return; const QJsonObject content = contentJson(); if ( content.contains("msgtype") && content.contains("body") ) { diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 86648860..49442197 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -18,6 +18,7 @@ HEADERS += \ $$PWD/events/roomavatarevent.h \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ + $$PWD/events/redactionevent.h \ $$PWD/jobs/basejob.h \ $$PWD/jobs/checkauthmethods.h \ $$PWD/jobs/passwordlogin.h \ @@ -44,6 +45,7 @@ SOURCES += \ $$PWD/events/roommemberevent.cpp \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ + $$PWD/events/redactionevent.cpp \ $$PWD/jobs/basejob.cpp \ $$PWD/jobs/checkauthmethods.cpp \ $$PWD/jobs/passwordlogin.cpp \ -- cgit v1.2.3 From df7f9bd2c26ca67fb3d221f9d3a68e34fb25a235 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 16:10:54 +0900 Subject: Process incoming redactions This only applies to new messages; historical redaction events are just skipped because historical events are already redacted on the server side. Closes #117. --- examples/qmc-example.cpp | 22 +++--- room.cpp | 170 +++++++++++++++++++++++++++++++++++++++-------- room.h | 22 ++++-- 3 files changed, 173 insertions(+), 41 deletions(-) diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index dc0c94e4..62ae8310 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -15,15 +15,21 @@ void onNewRoom(Room* r) { cout << "New room: " << r->id().toStdString() << endl; QObject::connect(r, &Room::namesChanged, [=] { - cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl; - cout << " Name: " << r->name().toStdString() << endl; - cout << " Canonical alias: " << r->canonicalAlias().toStdString() << endl; + cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl + << " Name: " << r->name().toStdString() << endl + << " Canonical alias: " << r->canonicalAlias().toStdString() + << endl << endl; }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEvents evs) { - cout << "New events in room " << r->id().toStdString() << ":" << endl; - for (auto e: evs) + QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsView events) { + cout << events.size() << " new event(s) in room " + << r->id().toStdString() << ":" << endl; + for (auto e: events) { - cout << string(e->originalJson()) << endl; + cout << "From: " + << r->roomMembername(e->senderId()).toStdString() + << endl << "Timestamp:" + << e->timestamp().toString().toStdString() << endl + << "JSON:" << endl << e->originalJson().toStdString() << endl; } }); } @@ -36,7 +42,7 @@ int main(int argc, char* argv[]) auto conn = new Connection(QUrl("https://matrix.org")); conn->connectToServer(argv[1], argv[2], "QMatrixClient example application"); - QObject::connect(conn, &Connection::connected, [=] { + auto c = QObject::connect(conn, &Connection::connected, [=] { cout << "Connected" << endl; conn->sync(); }); diff --git a/room.cpp b/room.cpp index 764f4d23..48486ab8 100644 --- a/room.cpp +++ b/room.cpp @@ -29,6 +29,7 @@ #include "events/roommemberevent.h" #include "events/typingevent.h" #include "events/receiptevent.h" +#include "events/redactionevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" #include "avatar.h" @@ -99,8 +100,9 @@ class Room::Private bool isEventNotable(const RoomEvent* e) const { - return e->senderId() != connection->userId() && - e->type() == EventType::RoomMessage; + return !e->isRedacted() && + e->senderId() != connection->userId() && + e->type() == EventType::RoomMessage; } void appendEvent(RoomEvent* e) @@ -118,11 +120,14 @@ class Room::Private * Removes events from the passed container that are already in the timeline */ void dropDuplicateEvents(RoomEvents* events) const; + void checkUnreadMessages(RoomEventsView events); void setLastReadEvent(User* u, const QString& eventId); rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker, bool force = false); + void processRedaction(const RedactionEvent* redaction); + QJsonObject toJson() const; private: @@ -677,6 +682,83 @@ void Room::Private::dropDuplicateEvents(RoomEvents* events) const events->erase(dupsBegin, events->end()); } +void Room::Private::processRedaction(const RedactionEvent* redaction) +{ + Q_ASSERT(redaction && redaction->type() == EventType::Redaction); + + const auto pIdx = eventsIndex.find(redaction->redactedEvent()); + if (pIdx == eventsIndex.end()) + { + qCDebug(MAIN) << "Redaction" << redaction->id() + << "ignored: target event not found"; + return; // If the target events comes later, it comes already redacted. + } + Q_ASSERT(q->isValidIndex(*pIdx)); + + auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())]; + + // Apply the redaction procedure from chapter 6.5 of The Spec + auto originalJson = ti->originalJsonObject(); + if (originalJson.value("unsigned").toObject() + .value("redacted_because").toObject() + .value("event_id") == redaction->id()) + { + qCDebug(MAIN) << "Redaction" << redaction->id() + << "of event" << ti.event()->id() << "already done, skipping"; + return; + } + static const QStringList keepKeys = + { "event_id", "type", "room_id", "sender", "state_key", + "prev_content", "content" }; + static const + std::vector> keepContentKeysMap + { { Event::Type::RoomMember, { "membership" } } + , { Event::Type::RoomCreate, { "creator" } } + , { Event::Type::RoomJoinRules, { "join_rule" } } + , { Event::Type::RoomPowerLevels, + { "ban", "events", "events_default", "kick", "redact", + "state_default", "users", "users_default" } } + , { Event::Type::RoomAliases, { "alias" } } + }; + for (auto it = originalJson.begin(); it != originalJson.end();) + { + if (!keepKeys.contains(it.key())) + it = originalJson.erase(it); // TODO: shred the value + else + ++it; + } + auto keepContentKeys = + find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), + [&](const std::pair& t) + { return ti->type() == t.first; } ); + if (keepContentKeys == keepContentKeysMap.end()) + { + originalJson.remove("content"); + originalJson.remove("prev_content"); + } else { + auto content = originalJson.take("content").toObject(); + for (auto it = content.begin(); it != content.end(); ) + { + if (!keepContentKeys->second.contains(it.key())) + it = content.erase(it); + else + ++it; + } + originalJson.insert("content", content); + } + auto unsignedData = originalJson.take("unsigned").toObject(); + unsignedData["redacted_because"] = redaction->originalJsonObject(); + originalJson.insert("unsigned", unsignedData); + + // Make a new event from the redacted JSON, exchange events, + // notify everyone and delete the old event + auto oldEvent = ti.replaceEvent(RoomEvent::fromJson(originalJson)); + q->onRedaction(oldEvent, ti); + qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction->id(); + emit q->replacedEvent(oldEvent, ti.event()); + delete oldEvent; +} + Connection* Room::connection() const { Q_ASSERT(d->connection); @@ -688,62 +770,94 @@ User* Room::localUser() const return connection()->user(); } +inline bool isRedaction(Event* e) +{ + return e->type() == EventType::Redaction; +} + void Room::addNewMessageEvents(RoomEvents events) { + auto timelineSize = d->timeline.size(); + d->dropDuplicateEvents(&events); - if (events.empty()) - return; - emit aboutToAddNewMessages(events); - doAddNewMessageEvents(events); - emit addedMessages(); + // We want to process redactions in the order of arrival (covering the + // case of one redaction superseding another one), hence stable partition. + const auto normalsBegin = + std::stable_partition(events.begin(), events.end(), isRedaction); + RoomEventsView redactions { events.begin(), normalsBegin }, + normalEvents { normalsBegin, events.end() }; + if (!normalEvents.empty()) + { + emit aboutToAddNewMessages(normalEvents); + doAddNewMessageEvents(normalEvents); + } + for (auto* r: redactions) + d->processRedaction(static_cast(r)); + if (!normalEvents.empty()) + { + d->checkUnreadMessages(normalEvents); + emit addedMessages(); + } + + Q_ASSERT(d->timeline.size() == timelineSize + normalEvents.size()); } -void Room::doAddNewMessageEvents(const RoomEvents& events) +void Room::doAddNewMessageEvents(RoomEventsView events) { Q_ASSERT(!events.empty()); - - Timeline::size_type newUnreadMessages = 0; for (auto e: events) - { d->appendEvent(e); - newUnreadMessages += d->isEventNotable(e); - } - qCDebug(MAIN) << "Room" << displayName() << "received" << events.size() - << "(with" << newUnreadMessages << "notable)" - << "new events; the last event is now" << d->timeline.back(); + qCDebug(MAIN) + << "Room" << displayName() << "received" << events.size() + << "new events; the last event is now" << d->timeline.back(); +} + +void Room::Private::checkUnreadMessages(RoomEventsView events) +{ + auto newUnreadMessages = + count_if(events.from, events.to, + [=] (const RoomEvent* e) { return isEventNotable(e); }); // The first event in the batch defines whose read marker can possibly be // promoted any further over the same author's events newly arrived. // Others will need explicit read receipts from the server (or, for // the local user, markMessagesAsRead() invocation) to promote their // read markers over the new message events. - User* firstWriter = connection()->user(events.front()->senderId()); - if (readMarker(firstWriter) != timelineEdge()) + User* firstWriter = connection->user((*events.from)->senderId()); + if (q->readMarker(firstWriter) != timeline.crend()) { - d->promoteReadMarker(firstWriter, findInTimeline(events.front()->id())); + promoteReadMarker(firstWriter, q->findInTimeline((*events.from)->id())); qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() - << "to" << *readMarker(firstWriter); + << "to" << *q->readMarker(firstWriter); } - if( !d->unreadMessages && newUnreadMessages > 0) + if(!unreadMessages && newUnreadMessages > 0) { - d->unreadMessages = true; - emit unreadMessagesChanged(this); - qCDebug(MAIN) << "Room" << displayName() << "has unread messages"; + unreadMessages = true; + emit q->unreadMessagesChanged(q); + qCDebug(MAIN) << "Room" << displayname << "has unread messages"; } } void Room::addHistoricalMessageEvents(RoomEvents events) { + auto timelineSize = d->timeline.size(); + d->dropDuplicateEvents(&events); - if (events.empty()) + auto redactionsBegin = + std::remove_if(events.begin(), events.end(), isRedaction); + RoomEventsView normalEvents { events.begin(), redactionsBegin }; + if (normalEvents.empty()) return; - emit aboutToAddHistoricalMessages(events); - doAddHistoricalMessageEvents(events); + + emit aboutToAddHistoricalMessages(normalEvents); + doAddHistoricalMessageEvents(normalEvents); emit addedMessages(); + + Q_ASSERT(d->timeline.size() == timelineSize + normalEvents.size()); } -void Room::doAddHistoricalMessageEvents(const RoomEvents& events) +void Room::doAddHistoricalMessageEvents(RoomEventsView events) { Q_ASSERT(!events.empty()); diff --git a/room.h b/room.h index 702c22f3..bef66b86 100644 --- a/room.h +++ b/room.h @@ -38,6 +38,8 @@ namespace QMatrixClient class User; class MemberSorter; class LeaveRoomJob; + class RedactEventJob; + class Room; class TimelineItem { @@ -49,9 +51,17 @@ namespace QMatrixClient TimelineItem(RoomEvent* e, index_t number) : evt(e), idx(number) { } RoomEvent* event() const { return evt.get(); } - RoomEvent* operator->() const { return event(); } //< Synonym for event() + RoomEvent* operator->() const { return event(); } //< Synonym for event()-> index_t index() const { return idx; } + // Used for event redaction + RoomEvent* replaceEvent(RoomEvent* other) + { + auto* old = evt.release(); + evt.reset(other); + return old; + } + private: std::unique_ptr evt; index_t idx; @@ -176,8 +186,8 @@ namespace QMatrixClient void markAllMessagesAsRead(); signals: - void aboutToAddHistoricalMessages(const RoomEvents& events); - void aboutToAddNewMessages(const RoomEvents& events); + void aboutToAddHistoricalMessages(RoomEventsView events); + void aboutToAddNewMessages(RoomEventsView events); void addedMessages(); /** @@ -200,12 +210,14 @@ namespace QMatrixClient void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); + void replacedEvent(RoomEvent* before, RoomEvent* after); protected: - virtual void doAddNewMessageEvents(const RoomEvents& events); - virtual void doAddHistoricalMessageEvents(const RoomEvents& events); + virtual void doAddNewMessageEvents(RoomEventsView events); + virtual void doAddHistoricalMessageEvents(RoomEventsView events); virtual void processStateEvents(const RoomEvents& events); virtual void processEphemeralEvent(Event* event); + virtual void onRedaction(RoomEvent*, TimelineItem&) { } private: class Private; -- cgit v1.2.3 From d8a7d9cbef6d90b6b15ffbf26bf43f5555dbaefd Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 15:37:41 +0900 Subject: Enable sending requests for redaction to the server Closes #118 --- room.cpp | 7 +++++++ room.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/room.cpp b/room.cpp index 48486ab8..dfca6bde 100644 --- a/room.cpp +++ b/room.cpp @@ -23,6 +23,7 @@ #include "jobs/generated/banning.h" #include "jobs/generated/leaving.h" #include "jobs/generated/receipts.h" +#include "jobs/generated/redaction.h" #include "jobs/setroomstatejob.h" #include "events/simplestateevents.h" #include "events/roomavatarevent.h" @@ -671,6 +672,12 @@ void Room::unban(const QString& userId) connection()->callApi(id(), userId); } +void Room::redactEvent(const QString& eventId, const QString& reason) +{ + connection()->callApi( + id(), eventId, connection()->generateTxnId(), reason); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { // Collect all duplicate events at the end of the container diff --git a/room.h b/room.h index bef66b86..989692de 100644 --- a/room.h +++ b/room.h @@ -181,6 +181,8 @@ namespace QMatrixClient 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 = {}); /** Mark all messages in the room as read */ void markAllMessagesAsRead(); -- cgit v1.2.3 From 5a5ae02e1eb9d58b162d8e3c0b3f837a6719d38f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 10 Dec 2017 16:32:24 +0900 Subject: Fixed compilation with older Qt --- examples/qmc-example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 62ae8310..ff7944ce 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -29,7 +29,7 @@ void onNewRoom(Room* r) << r->roomMembername(e->senderId()).toStdString() << endl << "Timestamp:" << e->timestamp().toString().toStdString() << endl - << "JSON:" << endl << e->originalJson().toStdString() << endl; + << "JSON:" << endl << string(e->originalJson()) << endl; } }); } -- cgit v1.2.3 From 71185be83b646c7d5a2d6d3dc0306710e1f6fdd0 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 11 Dec 2017 19:24:38 +0900 Subject: Whitelist origin_server_ts in redaction logic The Spec doesn't mention it but both Synapse and Riot act as if origin_server_ts was whitelisted, and it was also confirmed in #matrix-dev to be reasonable behaviour. --- events/event.cpp | 4 ++-- room.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/events/event.cpp b/events/event.cpp index d779f293..354aed03 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -87,6 +87,8 @@ RoomEvent::RoomEvent(Event::Type type) : Event(type) { } RoomEvent::RoomEvent(Type type, const QJsonObject& rep) : Event(type, rep), _id(rep["event_id"].toString()) , _roomId(rep["room_id"].toString()) + , _serverTimestamp( + QMatrixClient::fromJson(rep["origin_server_ts"])) , _senderId(rep["sender"].toString()) { // if (_id.isEmpty()) @@ -112,8 +114,6 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& rep) return; } - _serverTimestamp = - QMatrixClient::fromJson(rep["origin_server_ts"]); _txnId = unsignedData.value("transactionId").toString(); if (!_txnId.isEmpty()) qCDebug(EVENTS) << "Event transactionId:" << _txnId; diff --git a/room.cpp b/room.cpp index dfca6bde..7f4d7684 100644 --- a/room.cpp +++ b/room.cpp @@ -716,7 +716,7 @@ void Room::Private::processRedaction(const RedactionEvent* redaction) } static const QStringList keepKeys = { "event_id", "type", "room_id", "sender", "state_key", - "prev_content", "content" }; + "prev_content", "content", "origin_server_ts" }; static const std::vector> keepContentKeysMap { { Event::Type::RoomMember, { "membership" } } -- cgit v1.2.3 From bc1b65f872edf5f493f5fcc9792a37f03d6b7794 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Dec 2017 10:16:55 +0900 Subject: Fix a compiler warning --- events/event.cpp | 2 +- events/event.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/events/event.cpp b/events/event.cpp index 354aed03..1e2d89f2 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -87,9 +87,9 @@ RoomEvent::RoomEvent(Event::Type type) : Event(type) { } RoomEvent::RoomEvent(Type type, const QJsonObject& rep) : Event(type, rep), _id(rep["event_id"].toString()) , _roomId(rep["room_id"].toString()) + , _senderId(rep["sender"].toString()) , _serverTimestamp( QMatrixClient::fromJson(rep["origin_server_ts"])) - , _senderId(rep["sender"].toString()) { // if (_id.isEmpty()) // { diff --git a/events/event.h b/events/event.h index 62d9c23b..fa8a58af 100644 --- a/events/event.h +++ b/events/event.h @@ -162,9 +162,9 @@ namespace QMatrixClient private: QString _id; - QDateTime _serverTimestamp; QString _roomId; QString _senderId; + QDateTime _serverTimestamp; QScopedPointer _redactedBecause; QString _txnId; }; -- cgit v1.2.3 From 6d670e1c72eb5deba241c8484fc31665c48cb660 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 12 Dec 2017 10:20:47 +0900 Subject: Deduplicate events within a batch Closes #130. --- room.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/room.cpp b/room.cpp index 7f4d7684..6e4cfbbd 100644 --- a/room.cpp +++ b/room.cpp @@ -460,8 +460,8 @@ void Room::Private::insertEvent(RoomEvent* e, Timeline::iterator where, if (eventsIndex.contains(e->id())) { qCWarning(MAIN) << "Event" << e->id() << "is already in the timeline."; - qCWarning(MAIN) << "Either dropDuplicateEvents() wasn't called or duplicate " - "events within the same batch arrived from the server."; + qCWarning(MAIN) + << "Room::dropDuplicateEvents() wasn't called or has a bug."; return; } timeline.emplace(where, e, index); @@ -680,10 +680,30 @@ void Room::redactEvent(const QString& eventId, const QString& reason) void Room::Private::dropDuplicateEvents(RoomEvents* events) const { + if (events->empty()) + return; + // Collect all duplicate events at the end of the container auto dupsBegin = std::stable_partition(events->begin(), events->end(), [&] (RoomEvent* e) { return !eventsIndex.contains(e->id()); }); + + if (dupsBegin != events->begin()) + { + // Check the batch itself for dups + auto eIt = events->begin(); + for (auto baseId = (*eIt)->id(); ++eIt < dupsBegin; baseId = (*eIt)->id()) + { + dupsBegin = + std::stable_partition(eIt, dupsBegin, + [&] (const RoomEvent* e) { return e->id() != baseId; }); + } + } + if (dupsBegin == events->end()) + return; + + qCDebug(EVENTS) << "Dropping" << distance(dupsBegin, events->end()) + << "duplicate event(s)"; // Dispose of those dups std::for_each(dupsBegin, events->end(), [] (Event* e) { delete e; }); events->erase(dupsBegin, events->end()); -- cgit v1.2.3 From 8137e2e4e84878a330d20d0545476fae4e60dfe5 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 13 Dec 2017 11:09:20 +0900 Subject: Refactoring around Room::Private::insertEvent(s) --- room.cpp | 58 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/room.cpp b/room.cpp index 6e4cfbbd..e8d9f5f9 100644 --- a/room.cpp +++ b/room.cpp @@ -45,6 +45,8 @@ using namespace QMatrixClient; +enum EventsPlacement : int { Older = -1, Newer = 1 }; + class Room::Private { public: @@ -106,16 +108,7 @@ class Room::Private e->type() == EventType::RoomMessage; } - void appendEvent(RoomEvent* e) - { - insertEvent(e, timeline.end(), - timeline.empty() ? 0 : q->maxTimelineIndex() + 1); - } - void prependEvent(RoomEvent* e) - { - insertEvent(e, timeline.begin(), - timeline.empty() ? 0 : q->minTimelineIndex() - 1); - } + void insertEvents(RoomEventsView events, EventsPlacement placement); /** * Removes events from the passed container that are already in the timeline @@ -138,8 +131,6 @@ class Room::Private void insertMemberIntoMap(User* u); void removeMemberFromMap(const QString& username, User* u); - void insertEvent(RoomEvent* e, Timeline::iterator where, - TimelineItem::index_t index); bool isLocalUser(const User* u) const { return u == connection->user(); @@ -449,24 +440,30 @@ inline QByteArray makeErrorStr(const Event* e, QByteArray msg) return msg.append("; event dump follows:\n").append(e->originalJson()); } -void Room::Private::insertEvent(RoomEvent* e, Timeline::iterator where, - TimelineItem::index_t index) +void Room::Private::insertEvents(RoomEventsView events, EventsPlacement placement) { - Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); - Q_ASSERT_X(!e->id().isEmpty(), __FUNCTION__, - makeErrorStr(e, "Event with empty id cannot be in the timeline")); - Q_ASSERT_X(where == timeline.end() || where == timeline.begin(), __FUNCTION__, - "Events can only be appended or prepended to the timeline"); - if (eventsIndex.contains(e->id())) + // Historical messages arrive in newest-to-oldest order, so the process for + // them is symmetric to the one for new messages. + auto index = timeline.empty() ? -int(placement) : + placement == Older ? timeline.front().index() : + timeline.back().index(); + auto baseIndex = index; + for (const auto e: events) { - qCWarning(MAIN) << "Event" << e->id() << "is already in the timeline."; - qCWarning(MAIN) - << "Room::dropDuplicateEvents() wasn't called or has a bug."; - return; + Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); + Q_ASSERT_X(!e->id().isEmpty(), __FUNCTION__, + makeErrorStr(e, "Event with empty id cannot be in the timeline")); + Q_ASSERT_X(!eventsIndex.contains(e->id()), __FUNCTION__, + makeErrorStr(e, "Event is already in the timeline; " + "incoming events were not properly deduplicated")); + if (placement == Older) + timeline.emplace_front(e, --index); + else + timeline.emplace_back(e, ++index); + eventsIndex.insert(e->id(), index); + Q_ASSERT(q->findInTimeline(e->id())->event() == e); } - timeline.emplace(where, e, index); - eventsIndex.insert(e->id(), index); - Q_ASSERT(q->findInTimeline(e->id())->event() == e); + Q_ASSERT(int(events.size()) == (index - baseIndex) * int(placement)); } void Room::Private::addMember(User *u) @@ -832,8 +829,7 @@ void Room::addNewMessageEvents(RoomEvents events) void Room::doAddNewMessageEvents(RoomEventsView events) { Q_ASSERT(!events.empty()); - for (auto e: events) - d->appendEvent(e); + d->insertEvents(events, Newer); qCDebug(MAIN) << "Room" << displayName() << "received" << events.size() << "new events; the last event is now" << d->timeline.back(); @@ -889,9 +885,7 @@ void Room::doAddHistoricalMessageEvents(RoomEventsView events) Q_ASSERT(!events.empty()); const bool thereWasNoReadMarker = readMarker() == timelineEdge(); - // Historical messages arrive in newest-to-oldest order - for (auto e: events) - d->prependEvent(e); + d->insertEvents(events, Older); // Catch a special case when the last read event id refers to an event // that was outside the loaded timeline and has just arrived. Depending on -- cgit v1.2.3 From 3a1f1eced05f5f7ca3244f009e111eed2e809119 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 13 Dec 2017 16:29:02 +0900 Subject: EventsBatch: document the class --- events/event.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/events/event.h b/events/event.h index fa8a58af..bd33bb50 100644 --- a/events/event.h +++ b/events/event.h @@ -83,10 +83,33 @@ namespace QMatrixClient }; using EventType = Event::Type; + /** + * \brief A vector of pointers to events with deserialisation capabilities + * + * This is a simple wrapper over a generic vector type that adds + * a convenience method to deserialise events from QJsonArray. + * Note that this type does not own pointers to events. If owning + * semantics is needed, one should use the Owning<> wrapper around + * the container (e.g. \code Owning> \endcode). + * \tparam EventT base type of all events in the vector + */ template class EventsBatch : public std::vector { public: + /** + * \brief Deserialise events from an array + * + * Given the following JSON construct, creates events from + * the array stored at key "node": + * \code + * "container": { + * "node": [ { "event_id": "!evt1:srv.org", ... }, ... ] + * } + * \endcode + * \param container - the wrapping JSON object + * \param node - the key in container that holds the array of events + */ void fromJson(const QJsonObject& container, const QString& node) { const auto objs = container.value(node).toArray(); -- cgit v1.2.3 From bb14969dfa5e9e8a26a005a7f804f21a62460f68 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 08:27:56 +0900 Subject: Room::markMessagesAsRead(): Move private overload to pimpl --- room.cpp | 18 ++++++++++-------- room.h | 2 -- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/room.cpp b/room.cpp index e8d9f5f9..1234c343 100644 --- a/room.cpp +++ b/room.cpp @@ -120,6 +120,8 @@ class Room::Private rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker, bool force = false); + void markMessagesAsRead(rev_iter_t upToMarker); + void processRedaction(const RedactionEvent* redaction); QJsonObject toJson() const; @@ -267,21 +269,21 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker, return { prevMarker, newMarker }; } -void Room::markMessagesAsRead(Room::rev_iter_t upToMarker) +void Room::Private::markMessagesAsRead(Room::rev_iter_t upToMarker) { - Private::rev_iter_pair_t markers = d->promoteReadMarker(localUser(), upToMarker); + rev_iter_pair_t markers = promoteReadMarker(q->localUser(), upToMarker); if (markers.first != markers.second) - qCDebug(MAIN) << "Marked messages as read until" << *readMarker(); + qCDebug(MAIN) << "Marked messages as read until" << *q->readMarker(); // We shouldn't send read receipts for the local user's own messages - so // search earlier messages for the latest message not from the local user // until the previous last-read message, whichever comes first. for (; markers.second < markers.first; ++markers.second) { - if ((*markers.second)->senderId() != localUser()->id()) + if ((*markers.second)->senderId() != q->localUser()->id()) { - connection()->callApi( - id(), "m.read", (*markers.second)->id()); + connection->callApi( + id, "m.read", (*markers.second)->id()); break; } } @@ -289,13 +291,13 @@ void Room::markMessagesAsRead(Room::rev_iter_t upToMarker) void Room::markMessagesAsRead(QString uptoEventId) { - markMessagesAsRead(findInTimeline(uptoEventId)); + d->markMessagesAsRead(findInTimeline(uptoEventId)); } void Room::markAllMessagesAsRead() { if (!d->timeline.empty()) - markMessagesAsRead(d->timeline.crbegin()); + d->markMessagesAsRead(d->timeline.crbegin()); } bool Room::hasUnreadMessages() diff --git a/room.h b/room.h index 989692de..c0e041f6 100644 --- a/room.h +++ b/room.h @@ -227,8 +227,6 @@ namespace QMatrixClient void addNewMessageEvents(RoomEvents events); void addHistoricalMessageEvents(RoomEvents events); - - void markMessagesAsRead(rev_iter_t upToMarker); }; class MemberSorter -- cgit v1.2.3 From c5f480355c7d4c000a4ee73fd7f8107a9a9340c2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 18:41:55 +0900 Subject: Move all internal event pointers to std::unique_ptr<> This causes the following changes along the way: - Owning<> template is decommissioned. - event.h has been rearranged, and Event/RoomEvent::fromJson static methods have been replaced with an external makeEvent<> function template. A side effect of that is that one cannot use a factory with a type other than the one it's defined for (i.e. you cannot call makeEvent) but that feature has been out of use for long anyway. - Room::doAddNewMessageEvents() and Room::doAddHistoricalMessageEvents() have been removed, giving place to Room::onAddNewTimelineEvents() and Room::onAddHistoricalTimelineEvents(). The most important difference is that all code that must be executed now resides in addNewMessageEvents() (it moved from Room to Room::Private) and classes inheriting from Room are not obliged to call the overridden function from the overriding function (they can do it but those functions have empty bodies in Room). This was a long overdue change, and owning pointers simply mandated it. Room::onAddNewTimelineEvents/onAddHistoricalTimelineEvents should not do anything with the passed range in terms of ownership, it's just a way to allow the derived class to update his data in due course. - Room::Private::dropDuplicateEvents() and Room::Private::insertEvents(), notably, have been updated to work with owning pointers. insertEvents() move()s pointers to the timeline, while dropDuplicateEvents uses remove_if instead of stable_partition and doesn't explicitly delete event objects. Also, a bugfix: Event accidentally had not virtual destructor for quite a long time. According to the standard, deleting an object through a pointer to a base class without a virtual destructor leads to UB. So the fact that libqmatrixclient clients even worked all these months is mere coincidence and compiler authors good will :-D --- events/event.cpp | 23 +++-- events/event.h | 54 +++++++---- examples/qmc-example.cpp | 12 +-- jobs/roommessagesjob.cpp | 6 +- jobs/roommessagesjob.h | 2 +- jobs/syncjob.h | 2 +- room.cpp | 244 +++++++++++++++++++++++++---------------------- room.h | 36 +++---- util.h | 47 --------- 9 files changed, 205 insertions(+), 221 deletions(-) diff --git a/events/event.cpp b/events/event.cpp index 1e2d89f2..01f473ce 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -42,6 +42,8 @@ Event::Event(Type type, const QJsonObject& rep) } } +Event::~Event() = default; + QByteArray Event::originalJson() const { return QJsonDocument(_originalJson).toJson(); @@ -72,14 +74,15 @@ inline BaseEventT* makeIfMatches(const QJsonObject& o, const QString& selector) return makeIfMatches(o, selector); } -Event* Event::fromJson(const QJsonObject& obj) +template <> +EventPtr QMatrixClient::makeEvent(const QJsonObject& obj) { // Check more specific event types first - if (auto e = RoomEvent::fromJson(obj)) - return e; + if (auto e = makeEvent(obj)) + return EventPtr(move(e)); - return makeIfMatches(obj, obj["type"].toString()); + return EventPtr { makeIfMatches(obj, obj["type"].toString()) }; } RoomEvent::RoomEvent(Event::Type type) : Event(type) { } @@ -119,8 +122,7 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& rep) qCDebug(EVENTS) << "Event transactionId:" << _txnId; } -RoomEvent::~RoomEvent() -{ /* Let QScopedPointer do its job */ } +RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job QString RoomEvent::redactionReason() const { @@ -133,11 +135,12 @@ void RoomEvent::addId(const QString& id) _id = id; } -RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) +template <> +RoomEventPtr QMatrixClient::makeEvent(const QJsonObject& obj) { - return makeIfMatches - (obj, obj["type"].toString()); + (obj, obj["type"].toString()) }; } diff --git a/events/event.h b/events/event.h index bd33bb50..2b18bb46 100644 --- a/events/event.h +++ b/events/event.h @@ -25,8 +25,21 @@ #include "util.h" +#include + namespace QMatrixClient { + template + using event_ptr_tt = std::unique_ptr; + + /** 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 + event_ptr_tt makeEvent(const QJsonObject& obj); + class Event { Q_GADGET @@ -64,12 +77,6 @@ namespace QMatrixClient // (and in most cases it will be a combination of other fields // instead of "content" field). - /** Create an event with proper type from a JSON object - * Use this factory to detect the type from the JSON object contents - * and create an event object of that type. - */ - static Event* fromJson(const QJsonObject& obj); - protected: const QJsonObject contentJson() const; @@ -82,6 +89,10 @@ namespace QMatrixClient Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) }; using EventType = Event::Type; + using EventPtr = event_ptr_tt; + + template <> + EventPtr makeEvent(const QJsonObject& obj); /** * \brief A vector of pointers to events with deserialisation capabilities @@ -94,7 +105,7 @@ namespace QMatrixClient * \tparam EventT base type of all events in the vector */ template - class EventsBatch : public std::vector + class EventsBatch : public std::vector> { public: /** @@ -120,8 +131,10 @@ namespace QMatrixClient for (auto objValue: objs) { const auto o = objValue.toObject(); - auto e = EventT::fromJson(o); - this->push_back(e ? e : new EventT(EventType::Unknown, o)); + auto e { makeEvent(o) }; + if (!e) + e.reset(new EventT(EventType::Unknown, o)); + this->emplace_back(std::move(e)); } } }; @@ -151,10 +164,10 @@ namespace QMatrixClient const QDateTime& timestamp() const { return _serverTimestamp; } const QString& roomId() const { return _roomId; } const QString& senderId() const { return _senderId; } - bool isRedacted() const { return redactedBecause(); } - RedactionEvent* redactedBecause() const + bool isRedacted() const { return bool(_redactedBecause); } + const RedactionEvent* redactedBecause() const { - return _redactedBecause.data(); + return _redactedBecause.get(); } QString redactionReason() const; const QString& transactionId() const { return _txnId; } @@ -180,18 +193,19 @@ namespace QMatrixClient */ void addId(const QString& id); - // "Static override" of the one in Event - static RoomEvent* fromJson(const QJsonObject& obj); - private: QString _id; QString _roomId; QString _senderId; QDateTime _serverTimestamp; - QScopedPointer _redactedBecause; + event_ptr_tt _redactedBecause; QString _txnId; }; using RoomEvents = EventsBatch; + using RoomEventPtr = event_ptr_tt; + + template <> + RoomEventPtr makeEvent(const QJsonObject& obj); /** * Conceptually similar to QStringView (but much more primitive), it's a @@ -199,10 +213,10 @@ namespace QMatrixClient * referring to the beginning and the end of a range in a RoomEvents * container. */ - struct RoomEventsView + struct RoomEventsRange { - RoomEvents::const_iterator from; - RoomEvents::const_iterator to; + RoomEvents::iterator from; + RoomEvents::iterator to; RoomEvents::size_type size() const { @@ -212,6 +226,8 @@ namespace QMatrixClient bool empty() const { return from == to; } RoomEvents::const_iterator begin() const { return from; } RoomEvents::const_iterator end() const { return to; } + RoomEvents::iterator begin() { return from; } + RoomEvents::iterator end() { return to; } }; template diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index ff7944ce..dbb9912b 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -20,16 +20,16 @@ void onNewRoom(Room* r) << " Canonical alias: " << r->canonicalAlias().toStdString() << endl << endl; }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsView events) { - cout << events.size() << " new event(s) in room " + QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsRange timeline) { + cout << timeline.size() << " new event(s) in room " << r->id().toStdString() << ":" << endl; - for (auto e: events) + for (const auto& item: timeline) { cout << "From: " - << r->roomMembername(e->senderId()).toStdString() + << r->roomMembername(item->senderId()).toStdString() << endl << "Timestamp:" - << e->timestamp().toString().toStdString() << endl - << "JSON:" << endl << string(e->originalJson()) << endl; + << item->timestamp().toString().toStdString() << endl + << "JSON:" << endl << string(item->originalJson()) << endl; } }); } diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp index 9af1b3a6..e5568f17 100644 --- a/jobs/roommessagesjob.cpp +++ b/jobs/roommessagesjob.cpp @@ -23,7 +23,7 @@ using namespace QMatrixClient; class RoomMessagesJob::Private { public: - Owning events; + RoomEvents events; QString end; }; @@ -46,9 +46,9 @@ RoomMessagesJob::~RoomMessagesJob() delete d; } -RoomEvents RoomMessagesJob::releaseEvents() +RoomEvents&& RoomMessagesJob::releaseEvents() { - return d->events.release(); + return move(d->events); } QString RoomMessagesJob::end() const diff --git a/jobs/roommessagesjob.h b/jobs/roommessagesjob.h index 9680d52c..7b3fd9c9 100644 --- a/jobs/roommessagesjob.h +++ b/jobs/roommessagesjob.h @@ -34,7 +34,7 @@ namespace QMatrixClient FetchDirection dir = FetchDirection::Backward); virtual ~RoomMessagesJob(); - RoomEvents releaseEvents(); + RoomEvents&& releaseEvents(); QString end() const; protected: diff --git a/jobs/syncjob.h b/jobs/syncjob.h index 08bd773e..e9288486 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -30,7 +30,7 @@ namespace QMatrixClient { public: template - class Batch : public Owning> + class Batch : public EventsBatch { public: explicit Batch(QString k) : jsonKey(std::move(k)) { } diff --git a/room.cpp b/room.cpp index 1234c343..6c426de2 100644 --- a/room.cpp +++ b/room.cpp @@ -44,6 +44,7 @@ #include using namespace QMatrixClient; +using namespace std::placeholders; enum EventsPlacement : int { Older = -1, Newer = 1 }; @@ -101,20 +102,34 @@ class Room::Private void getPreviousContent(int limit = 10); - bool isEventNotable(const RoomEvent* e) const + bool isEventNotable(const TimelineItem& ti) const { - return !e->isRedacted() && - e->senderId() != connection->userId() && - e->type() == EventType::RoomMessage; + return !ti->isRedacted() && + ti->senderId() != connection->userId() && + ti->type() == EventType::RoomMessage; } - void insertEvents(RoomEventsView events, EventsPlacement placement); + void addNewMessageEvents(RoomEvents&& events); + void addHistoricalMessageEvents(RoomEvents&& events); + + /** + * @brief Move events into the timeline + * + * Insert events into the timeline, either new or historical. + * Pointers in the original container become empty, the ownership + * is passed to the timeline container. + * @param events - the range of events to be inserted + * @param placement - position and direction of insertion: Older for + * historical messages, Newer for new ones + */ + Timeline::size_type insertEvents(RoomEventsRange&& events, + EventsPlacement placement); /** * Removes events from the passed container that are already in the timeline */ void dropDuplicateEvents(RoomEvents* events) const; - void checkUnreadMessages(RoomEventsView events); + void checkUnreadMessages(timeline_iter_t from); void setLastReadEvent(User* u, const QString& eventId); rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker, @@ -122,7 +137,13 @@ class Room::Private void markMessagesAsRead(rev_iter_t upToMarker); - void processRedaction(const RedactionEvent* redaction); + /** + * @brief Apply redaction to the timeline + * + * Tries to find an event in the timeline and redact it; deletes the + * redaction event whether the redacted event was found or not. + */ + void processRedaction(RoomEventPtr redactionEvent); QJsonObject toJson() const; @@ -250,9 +271,8 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker, setLastReadEvent(u, (*(eagerMarker - 1))->id()); if (isLocalUser(u) && unreadMessages) { - auto stillUnreadMessagesCount = - count_if(eagerMarker, timeline.cend(), - [=](const TimelineItem& ti) { return isEventNotable(ti.event()); }); + auto stillUnreadMessagesCount = count_if(eagerMarker, timeline.cend(), + bind(&Room::Private::isEventNotable, this, _1)); if (stillUnreadMessagesCount == 0) { @@ -437,12 +457,13 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) emit q->memberRenamed(formerNamesakes[0]); } -inline QByteArray makeErrorStr(const Event* e, QByteArray msg) +inline QByteArray makeErrorStr(const Event& e, QByteArray msg) { - return msg.append("; event dump follows:\n").append(e->originalJson()); + return msg.append("; event dump follows:\n").append(e.originalJson()); } -void Room::Private::insertEvents(RoomEventsView events, EventsPlacement placement) +Room::Timeline::size_type Room::Private::insertEvents(RoomEventsRange&& events, + EventsPlacement placement) { // Historical messages arrive in newest-to-oldest order, so the process for // them is symmetric to the one for new messages. @@ -450,22 +471,26 @@ void Room::Private::insertEvents(RoomEventsView events, EventsPlacement placemen placement == Older ? timeline.front().index() : timeline.back().index(); auto baseIndex = index; - for (const auto e: events) + for (auto&& e: events) { + const auto eId = e->id(); Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); - Q_ASSERT_X(!e->id().isEmpty(), __FUNCTION__, - makeErrorStr(e, "Event with empty id cannot be in the timeline")); - Q_ASSERT_X(!eventsIndex.contains(e->id()), __FUNCTION__, - makeErrorStr(e, "Event is already in the timeline; " + Q_ASSERT_X(!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; " "incoming events were not properly deduplicated")); if (placement == Older) - timeline.emplace_front(e, --index); + timeline.emplace_front(move(e), --index); else - timeline.emplace_back(e, ++index); - eventsIndex.insert(e->id(), index); - Q_ASSERT(q->findInTimeline(e->id())->event() == e); + timeline.emplace_back(move(e), ++index); + eventsIndex.insert(eId, index); + Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId); } + // Pointers in "events" are empty now, but events.size() didn't change Q_ASSERT(int(events.size()) == (index - baseIndex) * int(placement)); + return events.size(); } void Room::Private::addMember(User *u) @@ -578,15 +603,15 @@ void Room::updateData(SyncRoomData&& data) << et.elapsed() << "ms," << data.timeline.size() << "events"; et.restart(); - addNewMessageEvents(data.timeline.release()); + d->addNewMessageEvents(move(data.timeline)); qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et.elapsed() << "ms"; } if (!data.ephemeral.empty()) { et.restart(); - for( auto ephemeralEvent: data.ephemeral ) - processEphemeralEvent(ephemeralEvent); + for( auto&& ephemeralEvent: data.ephemeral ) + processEphemeralEvent(move(ephemeralEvent)); qCDebug(PROFILER) << "*** Room::processEphemeralEvents():" << et.elapsed() << "ms"; } @@ -638,7 +663,7 @@ void Room::Private::getPreviousContent(int limit) connect( roomMessagesJob, &RoomMessagesJob::result, [=]() { if( !roomMessagesJob->error() ) { - q->addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); + addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); prevBatch = roomMessagesJob->end(); } roomMessagesJob = nullptr; @@ -682,35 +707,35 @@ void Room::Private::dropDuplicateEvents(RoomEvents* events) const if (events->empty()) return; - // Collect all duplicate events at the end of the container - auto dupsBegin = - std::stable_partition(events->begin(), events->end(), - [&] (RoomEvent* e) { return !eventsIndex.contains(e->id()); }); - - if (dupsBegin != events->begin()) - { - // Check the batch itself for dups - auto eIt = events->begin(); - for (auto baseId = (*eIt)->id(); ++eIt < dupsBegin; baseId = (*eIt)->id()) - { - dupsBegin = - std::stable_partition(eIt, dupsBegin, - [&] (const RoomEvent* e) { return e->id() != baseId; }); - } - } + // 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()); }); + + // 2. Check for duplicates within the batch if there are still events. + for (auto eIt = events->begin(); distance(eIt, dupsBegin) > 1; ++eIt) + dupsBegin = remove_if(eIt + 1, dupsBegin, + [&] (const RoomEventPtr& e) + { return e->id() == (*eIt)->id(); }); if (dupsBegin == events->end()) return; qCDebug(EVENTS) << "Dropping" << distance(dupsBegin, events->end()) << "duplicate event(s)"; - // Dispose of those dups - std::for_each(dupsBegin, events->end(), [] (Event* e) { delete e; }); events->erase(dupsBegin, events->end()); } -void Room::Private::processRedaction(const RedactionEvent* redaction) +inline bool isRedaction(const RoomEventPtr& e) { - Q_ASSERT(redaction && redaction->type() == EventType::Redaction); + return e->type() == EventType::Redaction; +} + +void Room::Private::processRedaction(RoomEventPtr redactionEvent) +{ + Q_ASSERT(redactionEvent && isRedaction(redactionEvent)); + const auto& redaction = + static_cast(redactionEvent.get()); const auto pIdx = eventsIndex.find(redaction->redactedEvent()); if (pIdx == eventsIndex.end()) @@ -778,11 +803,10 @@ void Room::Private::processRedaction(const RedactionEvent* redaction) // Make a new event from the redacted JSON, exchange events, // notify everyone and delete the old event - auto oldEvent = ti.replaceEvent(RoomEvent::fromJson(originalJson)); - q->onRedaction(oldEvent, ti); + auto oldEvent { ti.replaceEvent(makeEvent(originalJson)) }; + q->onRedaction(oldEvent.get(), ti.event()); qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction->id(); - emit q->replacedEvent(oldEvent, ti.event()); - delete oldEvent; + emit q->replacedEvent(ti.event(), oldEvent.get()); } Connection* Room::connection() const @@ -796,62 +820,55 @@ User* Room::localUser() const return connection()->user(); } -inline bool isRedaction(Event* e) +void Room::Private::addNewMessageEvents(RoomEvents&& events) { - return e->type() == EventType::Redaction; -} - -void Room::addNewMessageEvents(RoomEvents events) -{ - auto timelineSize = d->timeline.size(); + auto timelineSize = timeline.size(); - d->dropDuplicateEvents(&events); + dropDuplicateEvents(&events); // We want to process redactions in the order of arrival (covering the // case of one redaction superseding another one), hence stable partition. const auto normalsBegin = - std::stable_partition(events.begin(), events.end(), isRedaction); - RoomEventsView redactions { events.begin(), normalsBegin }, + stable_partition(events.begin(), events.end(), isRedaction); + RoomEventsRange redactions { events.begin(), normalsBegin }, normalEvents { normalsBegin, events.end() }; + if (!normalEvents.empty()) + emit q->aboutToAddNewMessages(normalEvents); + const auto insertedSize = insertEvents(std::move(normalEvents), Newer); + if (insertedSize > 0) { - emit aboutToAddNewMessages(normalEvents); - doAddNewMessageEvents(normalEvents); + qCDebug(MAIN) + << "Room" << displayname << "received" << insertedSize + << "new events; the last event is now" << timeline.back(); + q->onAddNewTimelineEvents(timeline.cend() - insertedSize); } - for (auto* r: redactions) - d->processRedaction(static_cast(r)); - if (!normalEvents.empty()) + for (auto&& r: redactions) + processRedaction(move(r)); + if (insertedSize > 0) { - d->checkUnreadMessages(normalEvents); - emit addedMessages(); + checkUnreadMessages(timeline.cend() - insertedSize); + emit q->addedMessages(); } - Q_ASSERT(d->timeline.size() == timelineSize + normalEvents.size()); -} - -void Room::doAddNewMessageEvents(RoomEventsView events) -{ - Q_ASSERT(!events.empty()); - d->insertEvents(events, Newer); - qCDebug(MAIN) - << "Room" << displayName() << "received" << events.size() - << "new events; the last event is now" << d->timeline.back(); + Q_ASSERT(timeline.size() == timelineSize + insertedSize); } -void Room::Private::checkUnreadMessages(RoomEventsView events) +void Room::Private::checkUnreadMessages(timeline_iter_t from) { - auto newUnreadMessages = - count_if(events.from, events.to, - [=] (const RoomEvent* e) { return isEventNotable(e); }); + Q_ASSERT(from < timeline.cend()); + const auto newUnreadMessages = count_if(from, timeline.cend(), + bind(&Room::Private::isEventNotable, this, _1)); - // The first event in the batch defines whose read marker can possibly be - // promoted any further over the same author's events newly arrived. - // Others will need explicit read receipts from the server (or, for - // the local user, markMessagesAsRead() invocation) to promote their - // read markers over the new message events. - User* firstWriter = connection->user((*events.from)->senderId()); + // The first event in the just-added batch (referred to by upTo.base()) + // defines whose read marker can possibly be promoted any further over + // the same author's events newly arrived. Others will need explicit + // read receipts from the server (or, for the local user, + // markMessagesAsRead() invocation) to promote their read markers over + // the new message events. + auto firstWriter = connection->user((*from)->senderId()); if (q->readMarker(firstWriter) != timeline.crend()) { - promoteReadMarker(firstWriter, q->findInTimeline((*events.from)->id())); + promoteReadMarker(firstWriter, q->findInTimeline((*from)->id())); qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() << "to" << *q->readMarker(firstWriter); } @@ -864,50 +881,45 @@ void Room::Private::checkUnreadMessages(RoomEventsView events) } } -void Room::addHistoricalMessageEvents(RoomEvents events) +void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) { - auto timelineSize = d->timeline.size(); + const auto timelineSize = timeline.size(); - d->dropDuplicateEvents(&events); - auto redactionsBegin = - std::remove_if(events.begin(), events.end(), isRedaction); - RoomEventsView normalEvents { events.begin(), redactionsBegin }; + dropDuplicateEvents(&events); + const auto redactionsBegin = + remove_if(events.begin(), events.end(), isRedaction); + RoomEventsRange normalEvents { events.begin(), redactionsBegin }; if (normalEvents.empty()) return; - emit aboutToAddHistoricalMessages(normalEvents); - doAddHistoricalMessageEvents(normalEvents); - emit addedMessages(); - - Q_ASSERT(d->timeline.size() == timelineSize + normalEvents.size()); -} - -void Room::doAddHistoricalMessageEvents(RoomEventsView events) -{ - Q_ASSERT(!events.empty()); - - const bool thereWasNoReadMarker = readMarker() == timelineEdge(); - d->insertEvents(events, Older); + emit q->aboutToAddHistoricalMessages(normalEvents); + const bool thereWasNoReadMarker = q->readMarker() == timeline.crend(); + const auto insertedSize = insertEvents(std::move(normalEvents), Older); // Catch a special case when the last read event id refers to an event // that was outside the loaded timeline and has just arrived. Depending on // other messages next to the last read one, we might need to promote // the read marker and update unreadMessages flag. - const auto curReadMarker = readMarker(); - if (thereWasNoReadMarker && curReadMarker != timelineEdge()) + const auto curReadMarker = q->readMarker(); + if (thereWasNoReadMarker && curReadMarker != timeline.crend()) { qCDebug(MAIN) << "Discovered last read event in a historical batch"; - d->promoteReadMarker(localUser(), curReadMarker, true); + promoteReadMarker(q->localUser(), curReadMarker, true); } - qCDebug(MAIN) << "Room" << displayName() << "received" << events.size() - << "past events; the oldest event is now" << d->timeline.front(); + qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize + << "past events; the oldest event is now" << timeline.front(); + q->onAddHistoricalTimelineEvents(timeline.crend() - insertedSize); + emit q->addedMessages(); + + Q_ASSERT(timeline.size() == timelineSize + insertedSize); } void Room::processStateEvents(const RoomEvents& events) { bool emitNamesChanged = false; - for (auto event: events) + for (const auto& e: events) { + auto* event = e.get(); switch (event->type()) { case EventType::RoomName: { @@ -973,12 +985,12 @@ void Room::processStateEvents(const RoomEvents& events) d->updateDisplayname(); } -void Room::processEphemeralEvent(Event* event) +void Room::processEphemeralEvent(EventPtr event) { switch (event->type()) { case EventType::Typing: { - auto typingEvent = static_cast(event); + auto typingEvent = static_cast(event.get()); d->usersTyping.clear(); for( const QString& userId: typingEvent->users() ) { @@ -989,7 +1001,7 @@ void Room::processEphemeralEvent(Event* event) break; } case EventType::Receipt: { - auto receiptEvent = static_cast(event); + auto receiptEvent = static_cast(event.get()); for( const auto &p: receiptEvent->eventsWithReceipts() ) { { diff --git a/room.h b/room.h index c0e041f6..08327917 100644 --- a/room.h +++ b/room.h @@ -45,25 +45,25 @@ namespace QMatrixClient { public: // For compatibility with Qt containers, even though we use - // a std:: container now + // a std:: container now for the room timeline using index_t = int; - TimelineItem(RoomEvent* e, index_t number) : evt(e), idx(number) { } + TimelineItem(RoomEventPtr&& e, index_t number) + : evt(move(e)), idx(number) { } RoomEvent* event() const { return evt.get(); } - RoomEvent* operator->() const { return event(); } //< Synonym for event()-> + RoomEvent* operator->() const { return evt.operator->(); } index_t index() const { return idx; } // Used for event redaction - RoomEvent* replaceEvent(RoomEvent* other) + RoomEventPtr replaceEvent(RoomEventPtr&& other) { - auto* old = evt.release(); - evt.reset(other); - return old; + evt.swap(other); + return move(other); } private: - std::unique_ptr evt; + RoomEventPtr evt; index_t idx; }; inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) @@ -88,6 +88,7 @@ namespace QMatrixClient public: using Timeline = std::deque; using rev_iter_t = Timeline::const_reverse_iterator; + using timeline_iter_t = Timeline::const_iterator; Room(Connection* connection, QString id, JoinState initialJoinState); ~Room() override; @@ -188,8 +189,8 @@ namespace QMatrixClient void markAllMessagesAsRead(); signals: - void aboutToAddHistoricalMessages(RoomEventsView events); - void aboutToAddNewMessages(RoomEventsView events); + void aboutToAddHistoricalMessages(RoomEventsRange events); + void aboutToAddNewMessages(RoomEventsRange events); void addedMessages(); /** @@ -212,21 +213,20 @@ namespace QMatrixClient void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); - void replacedEvent(RoomEvent* before, RoomEvent* after); + void replacedEvent(const RoomEvent* newEvent, + const RoomEvent* oldEvent); protected: - virtual void doAddNewMessageEvents(RoomEventsView events); - virtual void doAddHistoricalMessageEvents(RoomEventsView events); virtual void processStateEvents(const RoomEvents& events); - virtual void processEphemeralEvent(Event* event); - virtual void onRedaction(RoomEvent*, TimelineItem&) { } + virtual void processEphemeralEvent(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) { } private: class Private; Private* d; - - void addNewMessageEvents(RoomEvents events); - void addHistoricalMessageEvents(RoomEvents events); }; class MemberSorter diff --git a/util.h b/util.h index 09c58087..65de0610 100644 --- a/util.h +++ b/util.h @@ -25,53 +25,6 @@ namespace QMatrixClient { - /** - * @brief A crude wrapper around a container of pointers that owns pointers - * to contained objects - * - * Similar to vector>, upon deletion, this wrapper - * will delete all events contained in it. This wrapper can be used - * over Qt containers, which are incompatible with unique_ptr and even - * with QScopedPointer (which is the reason of its creation). - */ - template - class Owning : public ContainerT - { - public: - Owning() = default; - Owning(const Owning&) = delete; - Owning(Owning&&) = default; - Owning& operator=(Owning&& other) - { - assign(other.release()); - return *this; - } - - ~Owning() { cleanup(); } - - void assign(ContainerT&& other) - { - if (&other == this) - return; - cleanup(); - ContainerT::operator=(other); - } - - /** - * @brief returns the underlying container and releases the ownership - * - * Acts similar to unique_ptr::release. - */ - ContainerT release() - { - ContainerT c; - ContainerT::swap(c); - return c; - } - private: - void cleanup() { for (auto e: *this) delete e; } - }; - /** * @brief Lookup a value by a key in a varargs list * -- cgit v1.2.3 From 95797cf0ea78779ac98348fa1110abc09f5a344f Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 19:05:06 +0900 Subject: That virtual ~Event() mentioned in the previous commit message --- events/event.h | 1 + 1 file changed, 1 insertion(+) diff --git a/events/event.h b/events/event.h index 2b18bb46..6a7f6ece 100644 --- a/events/event.h +++ b/events/event.h @@ -62,6 +62,7 @@ namespace QMatrixClient explicit Event(Type type) : _type(type) { } Event(Type type, const QJsonObject& rep); Event(const Event&) = delete; + virtual ~Event(); Type type() const { return _type; } bool isStateEvent() const -- cgit v1.2.3 From 5c624bfdcb0fae5ea08466507e557b5acfb463b8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 19:05:30 +0900 Subject: Fixed building with older compilers --- room.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 6c426de2..296f783a 100644 --- a/room.cpp +++ b/room.cpp @@ -803,7 +803,8 @@ void Room::Private::processRedaction(RoomEventPtr redactionEvent) // Make a new event from the redacted JSON, exchange events, // notify everyone and delete the old event - auto oldEvent { ti.replaceEvent(makeEvent(originalJson)) }; + RoomEventPtr oldEvent + { ti.replaceEvent(makeEvent(originalJson)) }; q->onRedaction(oldEvent.get(), ti.event()); qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction->id(); emit q->replacedEvent(ti.event(), oldEvent.get()); -- cgit v1.2.3 From 468a73a804924ed6e63cd4aa58d9bcbc0ebc79d9 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 19:18:15 +0900 Subject: One more fix for older compilers --- events/event.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/event.h b/events/event.h index 6a7f6ece..a5730f14 100644 --- a/events/event.h +++ b/events/event.h @@ -132,7 +132,7 @@ namespace QMatrixClient for (auto objValue: objs) { const auto o = objValue.toObject(); - auto e { makeEvent(o) }; + auto&& e = makeEvent(o); if (!e) e.reset(new EventT(EventType::Unknown, o)); this->emplace_back(std::move(e)); -- cgit v1.2.3 From cca9a575e64e2824d36680d67de84c5cb1653165 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 19:38:19 +0900 Subject: Declare SyncRoomData as C++-movable This fixes compilation with older compilers that try to instantiate a copy constructor (and fail because unique_ptr) but actually is more proper in general. Also: do not advertise the class as Q_MOVABLE_TYPE: this was useful for QList/QVector when SyncRoomData was copyable; now it isn't, and Qt containers can't deal with non-copyable classes at all. --- jobs/syncjob.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jobs/syncjob.h b/jobs/syncjob.h index e9288486..aed36e0b 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -58,12 +58,9 @@ namespace QMatrixClient SyncRoomData(const QString& roomId, JoinState joinState_, const QJsonObject& room_); + SyncRoomData(SyncRoomData&&) = default; + SyncRoomData& operator=(SyncRoomData&&) = default; }; -} // namespace QMatrixClient -Q_DECLARE_TYPEINFO(QMatrixClient::SyncRoomData, Q_MOVABLE_TYPE); - -namespace QMatrixClient -{ // QVector cannot work with non-copiable objects, std::vector can. using SyncDataList = std::vector; -- cgit v1.2.3 From f96592d21940f5deccfed4173b54a7d9ba35fd1c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 19:53:33 +0900 Subject: Add a missing header to fixing building on OSX --- room.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/room.cpp b/room.cpp index 296f783a..0a52c7c8 100644 --- a/room.cpp +++ b/room.cpp @@ -42,6 +42,7 @@ #include #include +#include using namespace QMatrixClient; using namespace std::placeholders; -- cgit v1.2.3 From 9d3ea273e291a40e34f86b4beeaa6b59bc3260dd Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 14 Dec 2017 20:20:33 +0900 Subject: Another fix for CI on OSX --- room.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/room.cpp b/room.cpp index 0a52c7c8..d39e6d32 100644 --- a/room.cpp +++ b/room.cpp @@ -42,7 +42,6 @@ #include #include -#include using namespace QMatrixClient; using namespace std::placeholders; @@ -273,7 +272,7 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker, if (isLocalUser(u) && unreadMessages) { auto stillUnreadMessagesCount = count_if(eagerMarker, timeline.cend(), - bind(&Room::Private::isEventNotable, this, _1)); + std::bind(&Room::Private::isEventNotable, this, _1)); if (stillUnreadMessagesCount == 0) { @@ -859,7 +858,7 @@ void Room::Private::checkUnreadMessages(timeline_iter_t from) { Q_ASSERT(from < timeline.cend()); const auto newUnreadMessages = count_if(from, timeline.cend(), - bind(&Room::Private::isEventNotable, this, _1)); + std::bind(&Room::Private::isEventNotable, this, _1)); // The first event in the just-added batch (referred to by upTo.base()) // defines whose read marker can possibly be promoted any further over -- cgit v1.2.3 From 643aa92da416ab7b25c8b406a90007e4e7ebbb41 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Dec 2017 19:33:49 +0900 Subject: Fix an assertion failure when redacting an unknown event Closes #135. --- events/event.cpp | 6 +++--- events/event.h | 48 ++++++++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/events/event.cpp b/events/event.cpp index 01f473ce..e74bc114 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -75,10 +75,10 @@ inline BaseEventT* makeIfMatches(const QJsonObject& o, const QString& selector) } template <> -EventPtr QMatrixClient::makeEvent(const QJsonObject& obj) +EventPtr _impl::doMakeEvent(const QJsonObject& obj) { // Check more specific event types first - if (auto e = makeEvent(obj)) + if (auto e = doMakeEvent(obj)) return EventPtr(move(e)); return EventPtr { makeIfMatches -RoomEventPtr QMatrixClient::makeEvent(const QJsonObject& obj) +RoomEventPtr _impl::doMakeEvent(const QJsonObject& obj) { return RoomEventPtr { makeIfMatches using event_ptr_tt = std::unique_ptr; - /** 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 - event_ptr_tt makeEvent(const QJsonObject& obj); + namespace _impl + { + template + event_ptr_tt doMakeEvent(const QJsonObject& obj); + } class Event { @@ -92,8 +90,25 @@ namespace QMatrixClient using EventType = Event::Type; using EventPtr = event_ptr_tt; - template <> - EventPtr makeEvent(const QJsonObject& obj); + /** 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 + event_ptr_tt makeEvent(const QJsonObject& obj) + { + auto e = _impl::doMakeEvent(obj); + if (!e) + e.reset(new EventT(EventType::Unknown, obj)); + return e; + } + + namespace _impl + { + template <> + EventPtr doMakeEvent(const QJsonObject& obj); + } /** * \brief A vector of pointers to events with deserialisation capabilities @@ -130,13 +145,7 @@ namespace QMatrixClient // STL and Qt containers. this->reserve(static_cast(objs.size())); for (auto objValue: objs) - { - const auto o = objValue.toObject(); - auto&& e = makeEvent(o); - if (!e) - e.reset(new EventT(EventType::Unknown, o)); - this->emplace_back(std::move(e)); - } + this->emplace_back(makeEvent(objValue.toObject())); } }; using Events = EventsBatch; @@ -205,8 +214,11 @@ namespace QMatrixClient using RoomEvents = EventsBatch; using RoomEventPtr = event_ptr_tt; - template <> - RoomEventPtr makeEvent(const QJsonObject& obj); + namespace _impl + { + template <> + RoomEventPtr doMakeEvent(const QJsonObject& obj); + } /** * Conceptually similar to QStringView (but much more primitive), it's a -- cgit v1.2.3 From c641563dda852880fb9f8189b83d02a59aabe17a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Dec 2017 19:34:54 +0900 Subject: CMakeLists.txt: Make -Wreturn-type an error Because a missing return in a non-void function is always an error. --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cf871a3..e33c4e0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,10 @@ CHECK_CXX_COMPILER_FLAG("-Wpedantic" PEDANTIC_FLAG_SUPPORTED) if ( PEDANTIC_FLAG_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )pedantic($| )") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic") endif ( ) +CHECK_CXX_COMPILER_FLAG("-Werror=return-type" WERROR_FLAG_SUPPORTED) +if ( WERROR_FLAG_SUPPORTED ) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=return-type") +endif ( ) if ( CMAKE_VERSION VERSION_LESS "3.1" ) CHECK_CXX_COMPILER_FLAG("-std=c++11" STD_FLAG_SUPPORTED) -- cgit v1.2.3 From 575f859bb1a16b5d680789876f156aaf1e4f6c52 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 16 Dec 2017 19:35:56 +0900 Subject: Connection: proactively create a Room object upon joining a room So that clients could start using this object without waiting for the next sync. --- connection.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index 4fb661f3..06c4dcee 100644 --- a/connection.cpp +++ b/connection.cpp @@ -286,7 +286,10 @@ PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const JoinRoomJob* Connection::joinRoom(const QString& roomAlias) { - return callApi(roomAlias); + auto job = callApi(roomAlias); + connect(job, &JoinRoomJob::success, + this, [=] { provideRoom(job->roomId(), JoinState::Join); }); + return job; } void Connection::leaveRoom(Room* room) -- cgit v1.2.3 From a76c6faede34b95e4bc5c823cbbe51b3f32556ea Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Dec 2017 13:55:20 +0900 Subject: Don't use LoginJob::homeServer() anymore Because the respective parameter in /login result is deprecated. Closes #137. --- connection.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index 06c4dcee..9169480e 100644 --- a/connection.cpp +++ b/connection.cpp @@ -164,8 +164,6 @@ void Connection::doConnectToServer(const QString& user, const QString& password, deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [=] { - setHomeserver(QUrl::fromUserInput( - homeserver().scheme() + "://" + loginJob->homeServer())); d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); }); -- cgit v1.2.3 From a0eba40e90b030e8033ff3c59de4df0c5fc38479 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 20 Dec 2017 19:42:37 +0900 Subject: Clean away legacy settings when possible Otherwise they stick around when deleting actual settings (e.g. at logout). --- settings.cpp | 9 ++++++++- settings.h | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/settings.cpp b/settings.cpp index 68914642..ac9c091c 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1,7 +1,8 @@ #include "settings.h" +#include "logging.h" + #include -#include using namespace QMatrixClient; @@ -19,6 +20,8 @@ void Settings::setValue(const QString& key, const QVariant& value) { // qCDebug() << "Setting" << key << "to" << value; QSettings::setValue(key, value); + if (legacySettings.contains(key)) + legacySettings.remove(key); } QVariant Settings::value(const QString& key, const QVariant& defaultValue) const @@ -133,10 +136,14 @@ QString AccountSettings::accessToken() const 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); } void AccountSettings::clearAccessToken() { + legacySettings.remove("access_token"); + legacySettings.remove("device_id"); // Force the server to re-issue it remove("access_token"); } diff --git a/settings.h b/settings.h index ab3aae8b..36e29cf1 100644 --- a/settings.h +++ b/settings.h @@ -59,8 +59,8 @@ namespace QMatrixClient static QString legacyApplicationName; protected: - const QSettings legacySettings { legacyOrganizationName, - legacyApplicationName }; + QSettings legacySettings { legacyOrganizationName, + legacyApplicationName }; }; class SettingsGroup: public Settings @@ -94,6 +94,7 @@ namespace QMatrixClient Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName) Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver) Q_PROPERTY(bool keepLoggedIn READ keepLoggedIn WRITE setKeepLoggedIn) + /** \deprecated \sa setToken */ Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken) public: template @@ -115,7 +116,10 @@ namespace QMatrixClient bool keepLoggedIn() const; void setKeepLoggedIn(bool newSetting); + /** \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(); }; -- cgit v1.2.3 From a39ebe7af964a9c311bf213ba84a1aa5ce43d381 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Wed, 20 Dec 2017 19:43:17 +0900 Subject: Connection::accessToken - return QByteArray instead of QString Because that's what it really is. --- connection.cpp | 2 +- connection.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.cpp b/connection.cpp index 9169480e..d72e10ed 100644 --- a/connection.cpp +++ b/connection.cpp @@ -385,7 +385,7 @@ QString Connection::token() const return accessToken(); } -QString Connection::accessToken() const +QByteArray Connection::accessToken() const { return d->data->accessToken(); } diff --git a/connection.h b/connection.h index ecebb2e7..eccde170 100644 --- a/connection.h +++ b/connection.h @@ -86,7 +86,7 @@ namespace QMatrixClient Q_INVOKABLE QString deviceId() const; /** @deprecated Use accessToken() instead. */ Q_INVOKABLE QString token() const; - Q_INVOKABLE QString accessToken() const; + Q_INVOKABLE QByteArray accessToken() const; Q_INVOKABLE SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; -- cgit v1.2.3 From 2bdfc1a657175c3c13d086c780f2907d141fc749 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 21 Dec 2017 15:41:34 +0900 Subject: Connection::logout: supply receiver to connect(job,...) --- connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connection.cpp b/connection.cpp index d72e10ed..b6239762 100644 --- a/connection.cpp +++ b/connection.cpp @@ -223,7 +223,7 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { auto job = callApi(); - connect( job, &LogoutJob::success, [=] { + connect( job, &LogoutJob::success, this, [=] { stopSync(); emit loggedOut(); }); -- cgit v1.2.3 From 00cc69489ce5153a39d91599acdda95a2c20b698 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Dec 2017 08:21:09 +0900 Subject: Qt 5.6 or newer is required from now on --- .travis.yml | 52 +++++++++++++++++++++++++++++++--------------------- README.md | 20 +++++++++++--------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 313d1408..f033f3dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,36 @@ language: cpp + +os: [ linux, osx ] + +compiler: [ gcc, clang ] + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt563-trusty' + packages: + - g++-5 + - qt56base + matrix: - include: - - os: linux - dist: trusty - compiler: gcc - - os: linux - dist: trusty - compiler: clang - - os: osx + exclude: [ { os: osx, compiler: gcc } ] + install: - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; else sudo apt-get update -qq; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; else sudo apt-get install -y qt5-default; fi - - mkdir build && cd build - - cmake .. +- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; fi +- if [ "$TRAVIS_OS_NAME" = "linux" ]; then . /opt/qt56/bin/qt56-env.sh; fi + script: - - cmake --build . --target all - - cd .. - - qmake qmc-example.pro && make all +- mkdir build && cd build +- cmake .. +- cmake --build . --target all +- cd .. +- qmake qmc-example.pro && make all + notifications: - webhooks: - urls: - - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtpdHN1bmUlM0FtYXRyaXgub3JnLyUyMVBDelV0eHRPalV5U3hTZWxvZiUzQW1hdHJpeC5vcmc" - on_success: change # always|never|change - on_failure: always - on_start: never + webhooks: + urls: + - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtpdHN1bmUlM0FtYXRyaXgub3JnLyUyMVBDelV0eHRPalV5U3hTZWxvZiUzQW1hdHJpeC5vcmc" + on_success: change # always|never|change + on_failure: always + on_start: never diff --git a/README.md b/README.md index 731b0e72..370380c5 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,21 @@ You can find authors of libqmatrixclient in the Matrix room: [#qmatrixclient:mat You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. ## Building and usage -So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (both as a static and as a dynamic library). There is no specific installation sequence outside of other projects but since it's a CMake-based project, your mileage should be fairly short (in case it's not, issues and PRs are most welcome). +So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (either as a static or as a dynamic library). There is no installation sequence outside of other projects so far (PRs are most welcome). -The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for packagers. +The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for one-off building. If you want to hack on the library as a part of another project (e.g. Quaternion), you're advised to make a recursive check out of that project and update the library submodule to its master branch. There are very few tags so far; there will be more, as new versions are released. ## Pre-requisites - a Linux, MacOS or Windows system (desktop versions tried; mobile Linux/Windows might work too) -- a Git client (to check out this repo) -- Qt 5 (either Open Source or Commercial), version 5.2.1 or higher as of this writing (check `CMakeLists.txt` for most up-to-date information) -- qmake (from the Qt 5 installation) or CMake (from your package management system or [the official website](https://cmake.org/download/)). +- a Linux, OSX or Windows system (desktop versions tried; mobile Linux/Windows might work too) + - For Ubuntu flavours - zesty or later (or a derivative) is good enough out of the box; older ones will need PPAs at least for a newer Qt; in particular, if you have xenial you're advised to add Kubuntu Backports PPA for it +- a Git client to check out this repo +- CMake (from your package management system or [the official website](https://cmake.org/download/)) +- Qt 5 (either Open Source or Commercial), version 5.6 or higher - a C++ toolchain supported by your version of Qt (see a link for your platform at [the Qt's platform requirements page](http://doc.qt.io/qt-5/gettingstarted.html#platform-requirements)) - - GCC 4.8, Clang 3.5.0, Visual C++ 2015 are the oldest officially supported as of this writing + - GCC 5 (Windows, Linux, OSX), Clang 5 (Linux), Apple Clang 8.1 (OSX) and Visual C++ 2015 (Windows) are the oldest officially supported + - any build system that works with CMake should be fine: GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. #### Linux Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries libqmatrixclient uses (or blindly run cmake and look at error messages). The library is entirely offscreen (Qt::Core and Qt::Network are essential) but it also depends on Qt::Gui in order to operate with avatar thumbnails. @@ -34,7 +36,7 @@ Just install things from the list above using your preferred package manager. If `brew install qt5` should get you Qt5. If you plan to use CMake, you may need to tell it about the path to Qt by passing `-DCMAKE_PREFIX_PATH=` #### Windows -1. Install Qt5, using their official installer. If for some reason you need to use Qt 5.2.1, select its Add-ons component in the installer as well; for later versions, no extras are needed. If you don't have a toolchain and/or IDE, you can easily get one by selecting Qt Creator and at least one toolchain under Qt Creator. Qt 5.3 is recommended on Windows; `windeployqt` in Qt 5.2.1 is not functional enough to generate a proper list of files for installing. +1. Install Qt5, using their official installer. 1. If you plan to build with CMake, install CMake; if you're ok with qmake, you don't need to install anything on top of Qt. The commands in further sections imply that cmake/qmake is in your PATH - otherwise you have to prepend those commands with actual paths. As an option, it's a good idea to run a `qtenv2.bat` script that can be found in `C:\Qt\\\bin` (assuming you installed Qt to `C:\Qt`); the only thing it does is adding necessary paths to PATH. You might not want to run that script on system startup but it's very handy to setup the environment before building. For CMake, setting `CMAKE_PREFIX_PATH` in the same way as for OS X (see above), also helps. There are no official MinGW-based 64-bit packages for Qt. If you're determined to build a 64-bit library, either use a Visual Studio toolchain or build Qt5 yourself as described in Qt documentation. @@ -74,7 +76,7 @@ libqmatrixclient uses Qt's logging categories to make switching certain types of libqmatrixclient..= ``` where -- `` is something like `main`, `jobs`, or `events` (the full list is in the file `debug.cpp`) +- `` is one of: `main`, `jobs`, `jobs.sync`, `events`, `events.ephemeral`, and `profiler` (you can always find the full list in the file `logging.cpp`) - `` is one of `debug` and `warning` - `` is either `true` or `false`. -- cgit v1.2.3 From 00a3b28c8e561c35ad33499819d5aaced49b50da Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Dec 2017 08:37:07 +0900 Subject: Code cleanup --- events/event.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/events/event.h b/events/event.h index 71f2f4b2..d4923f1f 100644 --- a/events/event.h +++ b/events/event.h @@ -115,9 +115,6 @@ namespace QMatrixClient * * This is a simple wrapper over a generic vector type that adds * a convenience method to deserialise events from QJsonArray. - * Note that this type does not own pointers to events. If owning - * semantics is needed, one should use the Owning<> wrapper around - * the container (e.g. \code Owning> \endcode). * \tparam EventT base type of all events in the vector */ template @@ -140,7 +137,7 @@ namespace QMatrixClient void fromJson(const QJsonObject& container, const QString& node) { const auto objs = container.value(node).toArray(); - using size_type = typename std::vector::size_type; + using size_type = typename std::vector>::size_type; // The below line accommodates the difference in size types of // STL and Qt containers. this->reserve(static_cast(objs.size())); -- cgit v1.2.3 From 7b65cec153970c3e7525f24e0d167e0aa7ba4de4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Dec 2017 09:13:25 +0900 Subject: Use pimpl for Avatars --- avatar.cpp | 39 ++++++++++++++++++++++++++++++++++++--- avatar.h | 20 +++++--------------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/avatar.cpp b/avatar.cpp index f5101ddb..b2f50a67 100644 --- a/avatar.cpp +++ b/avatar.cpp @@ -24,7 +24,37 @@ using namespace QMatrixClient; +class Avatar::Private +{ + public: + Private(Connection* c, QIcon di) : _connection(c), _defaultIcon(di) { } + QPixmap get(int width, int height, Avatar::notifier_t notifier); + + Connection* _connection; + const QIcon _defaultIcon; + QUrl _url; + QPixmap _originalPixmap; + + std::vector> _scaledPixmaps; + + QSize _requestedSize; + bool _valid = false; + MediaThumbnailJob* _ongoingRequest = nullptr; + std::vector notifiers; +}; + +Avatar::Avatar(Connection* connection, QIcon defaultIcon) + : d(new Private { connection, std::move(defaultIcon) }) +{ } + +Avatar::~Avatar() = default; + QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) +{ + return d->get(width, height, notifier); +} + +QPixmap Avatar::Private::get(int width, int height, Avatar::notifier_t notifier) { QSize size(width, height); @@ -73,12 +103,15 @@ QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) return pixmap; } +QUrl Avatar::url() const { return d->_url; } + bool Avatar::updateUrl(const QUrl& newUrl) { - if (newUrl == _url) + if (newUrl == d->_url) return false; - _url = newUrl; - _valid = false; + d->_url = newUrl; + d->_valid = false; return true; } + diff --git a/avatar.h b/avatar.h index 60cf3779..e71fecd7 100644 --- a/avatar.h +++ b/avatar.h @@ -31,28 +31,18 @@ namespace QMatrixClient class Avatar { public: - explicit Avatar(Connection* connection, QIcon defaultIcon = {}) - : _defaultIcon(std::move(defaultIcon)), _connection(connection) - { } + explicit Avatar(Connection* connection, QIcon defaultIcon = {}); + ~Avatar(); using notifier_t = std::function; QPixmap get(int w, int h, notifier_t notifier); - QUrl url() const { return _url; } + QUrl url() const; bool updateUrl(const QUrl& newUrl); private: - QUrl _url; - QPixmap _originalPixmap; - QIcon _defaultIcon; - - std::vector> _scaledPixmaps; - - QSize _requestedSize; - bool _valid = false; - Connection* _connection; - MediaThumbnailJob* _ongoingRequest = nullptr; - std::vector notifiers; + class Private; + QScopedPointer d; }; } // namespace QMatrixClient -- cgit v1.2.3 From 997a1c879f853606deb47c75c4735779a2f37189 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Dec 2017 09:16:25 +0900 Subject: CONTRIBUTING.md refreshed and slightly extended --- CONTRIBUTING.md | 78 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81c9d819..e67fabed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,14 +96,48 @@ Any components proposed for reuse should have a license that permits releasing a If you find a significant vulnerability, or evidence of one, use either of the following contacts: -* send an email to Kitsune Ral -* reach out in Matrix to #kitsune:matrix.org (with encryption switched on) +* send an email to Kitsune Ral [Kitsune-Ral@users.sf.net](mailto:Kitsune-Ral@users.sf.net) +* reach out in Matrix to #kitsune:matrix.org (if you can, switch encryption **on**) In any of these two options, _indicate that you have such information_ (do not share the information yet), and we'll tell you the next steps. We will gladly give credit to anyone who reports a vulnerability so that we can fix it. If you want to remain anonymous or pseudonymous instead, please let us know that; we will gladly respect your wishes. +## Code changes + +The code should strive to be DRY (don't repeat yourself), clear, and obviously correct. Some technical debt is inevitable, just don't bankrupt us with it. Refactoring is welcome. + +### Qt-flavoured C++ + +This is our primary language. We don't have a particular code style _as of yet_ but some rules-of-thumb are below: +* 4-space indents, no tabs, no trailing spaces, no last empty lines. If you spot the code abusing these - we'll thank you for fixing it. +* Lines within 80 characters are preferred. +* Braces after if's, while's, do's, function signatures etc. take a separate line. +* A historical deviation from the usual Qt code format conventions is an extra indent inside _classes_ (access specifiers go at +4 spaces to the base, members at +8 spaces) but not _structs_ (members at +4 spaces). This may change in the future for something more conventional. +* Please don't make hypocritical structs with protected or private members. Just make them classes instead. +* Qt containers are generally preferred to STL containers; however, there are notable exceptions, and libqmatrixclient already uses them: + * `std::array` and `std::deque` have no direct counterparts in Qt. + * Because of COW semantics, Qt containers cannot hold uncopyable classes. Classes without a default constructor are a problem too. An example of that is `SyncRoomData` and `EventsBatch<>`. Use standard containers for those but see the next point and also consider if you can supply a reasonable copy/default constructor. + * Standard containers can be used in code internal to a translation unit (i.e., in a certain .cpp file) _as long as it's not exposed in the interface_. It's ok to use, e.g., `std::vector` instead of `QVector` in tighter code, or when dealing with uncopyable (see the previous point) elements. However, exposing standard containers in the API that might be used by QML will not work at all and will never be accepted. +* Use `QVector` instead of `QList` where possible - see a [great article of Marc Mutz on Qt containers](https://marcmutz.wordpress.com/effective-qt/containers/) for details. + +### Automated tests + +There's no testing framework as of now; either Catch or QTest or both will be used eventually (PRs welcome, just don't expect a quick merge of one - we'll hunt you down to actually write some tests first :-D ). + +### Security, privacy, and performance + +Pay attention to security, and work *with* (not against) the usual security hardening mechanisms (however few in C++). `char *` and similar unchecked C-style read/write arrays are forbidden - use Qt containers or at the very least `std::array<>` instead. + +Exercise the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) where reasonable and appropriate. Prefer less-coupled cohesive code. + +Protect private information, in particular passwords and email addresses. Do not forget about local access to data (in particular, be very careful when storing something in temporary files, let alone permanent configuration or state). Avoid mechanisms that could be used for tracking where possible (we do need to verify people are logged in but that's pretty much it), and ensure that third parties can't use interactions for tracking. + +We want the software to have decent performance for typical users. At the same time we keep libqmatrixclient single-threaded as much as possible, to keep the code simple. That means being cautious about operation complexity (read about big-O notation if you need a kickstart on the topic). This especially refers to operations on the whole timeline - it can easily be several thousands elements long so even operations with linear complexity, if heavy enough, can produce noticeable GUI freezing. + +Having said that, there's always a trade-off between various attributes; in particular, readability and maintainability of the code is more important than squeezing every bit out of that clumsy algorithm. + ## Documentation changes Most of the documentation is in "markdown" format. @@ -116,51 +150,19 @@ In practice we use the version of Markdown implemented by GitHub when it renders This version of markdown is sometimes called [GitHub-flavored markdown](https://help.github.com/articles/github-flavored-markdown/). In particular, blank lines separate paragraphs; newlines inside a paragraph do *not* force a line break. -Beware - this is *not* the same markdown algorithm used by GitHub when it renders issue or pull comments; in those cases [newlines in paragraph-like content are considered as real line breaks](https://help.github.com/articles/writing-on-github/); unfortunately this other algorithm is *also* called GitHub rendered markdown. (Yes, it'd be better if there were standard different names -for different things.) +Beware - this is *not* the same markdown algorithm used by GitHub when it renders issue or pull comments; in those cases [newlines in paragraph-like content are considered as real line breaks](https://help.github.com/articles/writing-on-github/); unfortunately this other algorithm is *also* called GitHub rendered markdown. (Yes, it'd be better if there were standard different names for different things.) In your markdown, please don't use tab characters and avoid "bare" URLs (in a hypertext link, the link text and URL should be on the same line). We do not care about the line length in markdown texts (and more often than not put the whole paragraph into one line). This is actually negotiable, and absolutely not enforceable. If you want to fit in a 70-character limit, go ahead, just don't reformat the whole text along the way. Take care to not break URLs when breaking lines. Do not use trailing two spaces for line breaks, since these cannot be seen and may be silently removed by some tools. Instead, use <br /> (an HTML break). -## Code changes - -The code should strive to be DRY (don't repeat yourself), clear, and obviously correct. -Some technical debt is inevitable, just don't bankrupt us with it. -Improved refactorizations are welcome. - -Below are guidelines for specific languages. - -### Qt-flavoured C++ - -This is our primary language. We don't have a particular code style _as of yet_ but some rules-of-thumb are below: -* 4-space indents, no tabs, no trailing spaces, no last empty lines. If you spot abusing code in the existing code base - we'll thank you for fixing it. -* A notable deviation from the usual Qt code format conventions is an extra indent inside _classes_ (access specifiers go at +4 spaces to the base, members at +8 spaces) but not _structs_ (members at +4 spaces). -* While we are at it - please don't make hypocritical structs with protected or private members. Just do them classes instead. -* - -### Automated tests - -There's no testing framework as of now; either Catch or QTest will be used eventually (PRs welcome, just don't expect a quick merge of one - we'll hunt you down to actually write some tests first :-D ). - -### Security, privacy, and performance - -Pay attention to security, and work *with* (not against) the usual security hardening mechanisms (however few in C++). `char *` and similar unchecked C-style read/write arrays are forbidden - use Qt containers or at the very least std::array<> instead. -Protect private information, in particular passwords and email addresses. Do not forget about local access to data (in particular, be very careful when storing something in temporary files, let alone permanent configuration or state). -Avoid mechanisms that could be used for tracking where possible -(we do need to verify people are logged in for some operations), -and ensure that third parties can't use interactions for tracking. - -We want the software to have decent performance for typical users. And the same time we keep libqmatrixclient single-threaded as much as possible, to keep the code simple. That means being cautious about operation complexity (read about big-O notation if you need a kickstart on the topic). This especially refers to operations on the whole timeline - it can easily be several thousands elements long so even operations with linear complexity, if heavy enough, can produce noticeable GUI freezing. -Having said that, there's always a trade-off between various attributes; in particular, readability and maintainability of the code is more important than squeezing every bit out of that clumsy algorithm. - ## How to check proposed changes before submitting them Checking the code on at least one configuration is essential; if you only have a hasty fix that doesn't even compile, better make an issue and put a link to your commit into it (with an explanation what it is about and why). ### Standard checks --Wall -pedantic is used with GCC and Clang. We don't turn those warnings to errors but please treat them as such. +`-Wall -pedantic` is used with GCC and Clang. We don't turn those warnings to errors but please treat them as such. ### Continuous Integration @@ -192,9 +194,9 @@ Regardless of the above paragraph (and as mentioned earlier in the text), we're Some cases need additional explanation: * Before rolling out your own super-optimised container or algorithm written from scratch, take a good long look through documentation on Qt and C++ standard library (including the experimental/future sections). Please try to reuse the existing facilities as much as possible. -* You should have a very good reason (or better several ones) to add a component from KDE Frameworks. We don't rule this out and there's no prejudice against KDE; it just so happened that KDE Frameworks is one of most obvious reuse candidates for us but so far none of these components survived as libqmatrixclient deps. +* You should have a good reason (or better several ones) to add a component from KDE Frameworks. We don't rule this out and there's no prejudice against KDE; it just so happened that KDE Frameworks is one of most obvious reuse candidates for us but so far none of these components survived as libqmatrixclient deps. * Never forget that libqmatrixclient is aimed to be a non-visual library; QtGui in dependencies is only driven by (entirely offscreen) dealing with QPixmaps. While there's a bunch of visual code (in C++ and QML) shared between libqmatrixclient-enabled _applications_, this is likely to end up in a separate (libqmatrixclient-enabled) library, rather than libqmatrixclient. ## Attribution -This text is largely based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors. The text itself is licensed under CC-BY-4.0. +This text is largely based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors (many thanks!). The text itself is licensed under CC-BY-4.0. -- cgit v1.2.3 From a4a1129385731c3999a6d5986a24fc069938245c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 25 Dec 2017 09:17:39 +0900 Subject: ReceiptEvent: use QVector instead of std::vector Because we should practice what we preach in CONTRIBUTING.md. --- events/receiptevent.cpp | 6 +++--- events/receiptevent.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/events/receiptevent.cpp b/events/receiptevent.cpp index b36ddb23..e30fe4e4 100644 --- a/events/receiptevent.cpp +++ b/events/receiptevent.cpp @@ -56,15 +56,15 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) continue; } const QJsonObject reads = eventIt.value().toObject().value("m.read").toObject(); - std::vector receipts; - receipts.reserve(static_cast(reads.size())); + QVector 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(), QMatrixClient::fromJson(user["ts"])}); } - _eventsWithReceipts.push_back({eventIt.key(), receipts}); + _eventsWithReceipts.push_back({eventIt.key(), std::move(receipts)}); } static const auto UnreadMsgsKey = QStringLiteral("x-qmatrixclient.unread_messages"); diff --git a/events/receiptevent.h b/events/receiptevent.h index 15fdf946..9494c7c6 100644 --- a/events/receiptevent.h +++ b/events/receiptevent.h @@ -30,9 +30,9 @@ namespace QMatrixClient struct ReceiptsForEvent { QString evtId; - std::vector receipts; + QVector receipts; }; - using EventsWithReceipts = std::vector; + using EventsWithReceipts = QVector; class ReceiptEvent: public Event { -- cgit v1.2.3