From 1edfe9d82ea9d9a50645d419c736db45bf940978 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 27 Feb 2018 20:14:47 +0900 Subject: jobs/generated: SetAccountDataJob, SetAccountDataPerRoomJob --- jobs/generated/account-data.cpp | 28 ++++++++++++++++++++++++++++ jobs/generated/account-data.h | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 jobs/generated/account-data.cpp create mode 100644 jobs/generated/account-data.h diff --git a/jobs/generated/account-data.cpp b/jobs/generated/account-data.cpp new file mode 100644 index 00000000..35ee94c0 --- /dev/null +++ b/jobs/generated/account-data.cpp @@ -0,0 +1,28 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#include "account-data.h" + +#include "converters.h" + +#include + +using namespace QMatrixClient; + +static const auto basePath = QStringLiteral("/_matrix/client/r0"); + +SetAccountDataJob::SetAccountDataJob(const QString& userId, const QString& type, const QJsonObject& content) + : BaseJob(HttpVerb::Put, "SetAccountDataJob", + basePath % "/user/" % userId % "/account_data/" % type) +{ + setRequestData(Data(content)); +} + +SetAccountDataPerRoomJob::SetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type, const QJsonObject& content) + : BaseJob(HttpVerb::Put, "SetAccountDataPerRoomJob", + basePath % "/user/" % userId % "/rooms/" % roomId % "/account_data/" % type) +{ + setRequestData(Data(content)); +} + diff --git a/jobs/generated/account-data.h b/jobs/generated/account-data.h new file mode 100644 index 00000000..69ad9fb4 --- /dev/null +++ b/jobs/generated/account-data.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * THIS FILE IS GENERATED - ANY EDITS WILL BE OVERWRITTEN + */ + +#pragma once + +#include "../basejob.h" + +#include + + +namespace QMatrixClient +{ + // Operations + + class SetAccountDataJob : public BaseJob + { + public: + explicit SetAccountDataJob(const QString& userId, const QString& type, const QJsonObject& content = {}); + }; + + class SetAccountDataPerRoomJob : public BaseJob + { + public: + explicit SetAccountDataPerRoomJob(const QString& userId, const QString& roomId, const QString& type, const QJsonObject& content = {}); + }; +} // namespace QMatrixClient -- cgit v1.2.3 From bc08637eaaf25fb83b685e48e86553d3edacc09a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 4 Mar 2018 18:00:05 +0900 Subject: converters.h: pass QJsonValue by reference; add support of QHash --- converters.h | 56 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/converters.h b/converters.h index 00d1d339..96efe5f8 100644 --- a/converters.h +++ b/converters.h @@ -46,17 +46,22 @@ namespace QMatrixClient inline QJsonValue toJson(const QByteArray& bytes) { -#if QT_VERSION < QT_VERSION_CHECK(5, 3, 0) - return QJsonValue(QLatin1String(bytes.constData())); -#else return QJsonValue(bytes.constData()); -#endif + } + + template + inline QJsonValue toJson(const QHash& hashMap) + { + QJsonObject json; + for (auto it = hashMap.begin(); it != hashMap.end(); ++it) + json.insert(it.key(), toJson(it.value())); + return json; } template struct FromJson { - T operator()(QJsonValue jv) const { return static_cast(jv); } + T operator()(const QJsonValue& jv) const { return static_cast(jv); } }; template @@ -67,32 +72,32 @@ namespace QMatrixClient template <> struct FromJson { - bool operator()(QJsonValue jv) const { return jv.toBool(); } + bool operator()(const QJsonValue& jv) const { return jv.toBool(); } }; template <> struct FromJson { - int operator()(QJsonValue jv) const { return jv.toInt(); } + int operator()(const QJsonValue& jv) const { return jv.toInt(); } }; template <> struct FromJson { - double operator()(QJsonValue jv) const { return jv.toDouble(); } + double operator()(const QJsonValue& jv) const { return jv.toDouble(); } }; template <> struct FromJson { - qint64 operator()(QJsonValue jv) const { return qint64(jv.toDouble()); } + qint64 operator()(const QJsonValue& jv) const { return qint64(jv.toDouble()); } }; template <> struct FromJson { - QString operator()(QJsonValue jv) const { return jv.toString(); } + QString operator()(const QJsonValue& jv) const { return jv.toString(); } }; template <> struct FromJson { - QDateTime operator()(QJsonValue jv) const + QDateTime operator()(const QJsonValue& jv) const { return QDateTime::fromMSecsSinceEpoch(fromJson(jv), Qt::UTC); } @@ -100,7 +105,7 @@ namespace QMatrixClient template <> struct FromJson { - QDate operator()(QJsonValue jv) const + QDate operator()(const QJsonValue& jv) const { return fromJson(jv).date(); } @@ -108,17 +113,23 @@ namespace QMatrixClient template <> struct FromJson { - QJsonObject operator()(QJsonValue jv) const { return jv.toObject(); } + QJsonObject operator()(const QJsonValue& jv) const + { + return jv.toObject(); + } }; template <> struct FromJson { - QJsonArray operator()(QJsonValue jv) const { return jv.toArray(); } + QJsonArray operator()(const QJsonValue& jv) const + { + return jv.toArray(); + } }; template struct FromJson> { - QVector operator()(QJsonValue jv) const + QVector operator()(const QJsonValue& jv) const { const auto jsonArray = jv.toArray(); QVector vect; vect.resize(jsonArray.size()); @@ -130,7 +141,7 @@ namespace QMatrixClient template struct FromJson> { - QList operator()(QJsonValue jv) const + QList operator()(const QJsonValue& jv) const { const auto jsonArray = jv.toArray(); QList sl; sl.reserve(jsonArray.size()); @@ -144,10 +155,21 @@ namespace QMatrixClient template <> struct FromJson { - QByteArray operator()(QJsonValue jv) const + inline QByteArray operator()(const QJsonValue& jv) const { return fromJson(jv).toLatin1(); } }; + template struct FromJson> + { + QHash operator()(const QJsonValue& jv) const + { + const auto json = jv.toObject(); + QHash h; h.reserve(json.size()); + for (auto it = json.begin(); it != json.end(); ++it) + h.insert(it.key(), fromJson(it.value())); + return h; + } + }; } // namespace QMatrixClient -- cgit v1.2.3 From 0be3571d7c96e3df7ec523217e02d58850c7fe73 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 10:08:30 +0900 Subject: Support saving account data on the server Closes #152. Saving of specific event types should be added separately. --- room.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/room.cpp b/room.cpp index cfa705bb..e03a2b5b 100644 --- a/room.cpp +++ b/room.cpp @@ -24,6 +24,7 @@ #include "jobs/generated/leaving.h" #include "jobs/generated/receipts.h" #include "jobs/generated/redaction.h" +#include "jobs/generated/account-data.h" #include "jobs/setroomstatejob.h" #include "events/simplestateevents.h" #include "events/roomavatarevent.h" @@ -202,6 +203,13 @@ class Room::Private */ void processRedaction(RoomEventPtr redactionEvent); + template + SetAccountDataPerRoomJob* setAccountData(const EvT& event) + { + return connection->callApi( + connection->userId(), id, EvT::typeId(), event.toJson()); + } + QJsonObject toJson() const; private: -- cgit v1.2.3 From 9057fc02b06bdd3e38e9cf39e68287e02d58596b Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 13:17:27 +0900 Subject: Use constants instead of hardcoded strings --- connection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connection.cpp b/connection.cpp index a57bd8b4..80685dd1 100644 --- a/connection.cpp +++ b/connection.cpp @@ -539,12 +539,12 @@ QHash> Connection::tagsToRooms() const QStringList Connection::tagNames() const { - QStringList tags ({"m.favourite"}); + QStringList tags ({FavouriteTag}); for (auto* r: d->roomMap) for (const auto& tag: r->tagNames()) - if (tag != "m.lowpriority" && !tags.contains(tag)) + if (tag != LowPriorityTag && !tags.contains(tag)) tags.push_back(tag); - tags.push_back("m.lowpriority"); + tags.push_back(LowPriorityTag); return tags; } -- cgit v1.2.3 From 6ea1fb621488910de055bd3af4d00343a763541a Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 10:16:20 +0900 Subject: ReadMarkerEvent; TagEvent remade with less boilerplate code tagevent.h -> accountdataevents.h now has a macro to define more simplistic events along the lines of simplestateevents.h but inheriting from Event instead. TagEvent and ReadMarkerEvent(m.fully_read) are defined using this macro. ReadMarkerEvent is also wired through event.* (but not further yet). --- CMakeLists.txt | 1 - events/accountdataevents.h | 78 ++++++++++++++++++++++++++++++++++++++++++++++ events/event.cpp | 5 +-- events/event.h | 2 +- events/tagevent.cpp | 71 ----------------------------------------- events/tagevent.h | 74 ------------------------------------------- libqmatrixclient.pri | 3 +- room.cpp | 36 ++++++++++++++------- room.h | 10 +++--- 9 files changed, 112 insertions(+), 168 deletions(-) create mode 100644 events/accountdataevents.h delete mode 100644 events/tagevent.cpp delete mode 100644 events/tagevent.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d7762e17..e95c72d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,6 @@ set(libqmatrixclient_SRCS events/roomavatarevent.cpp events/typingevent.cpp events/receiptevent.cpp - events/tagevent.cpp jobs/requestdata.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp diff --git a/events/accountdataevents.h b/events/accountdataevents.h new file mode 100644 index 00000000..78cf9c46 --- /dev/null +++ b/events/accountdataevents.h @@ -0,0 +1,78 @@ +#include + +/****************************************************************************** + * Copyright (C) 2018 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" +#include "eventcontent.h" + +namespace QMatrixClient +{ + static constexpr const char* FavouriteTag = "m.favourite"; + static constexpr const char* LowPriorityTag = "m.lowpriority"; + + struct TagRecord + { + TagRecord (QString order) : order(std::move(order)) { } + explicit TagRecord(const QJsonValue& jv = {}) + : order(jv.toObject().value("order").toString()) + { } + + QString order; + + bool operator==(const TagRecord& other) const + { return order == other.order; } + bool operator!=(const TagRecord& other) const + { return !operator==(other); } + }; + + inline QJsonValue toJson(const TagRecord& rec) + { + return QJsonObject {{ QStringLiteral("order"), rec.order }}; + } + + using TagsMap = QHash; + +#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _EnumType, _ContentType, _ContentKey) \ + class _Name : public Event \ + { \ + public: \ + static constexpr const char* TypeId = _TypeId; \ + static const char* typeId() { return TypeId; } \ + explicit _Name(const QJsonObject& obj) \ + : Event((_EnumType), obj) \ + , _content(contentJson(), QStringLiteral(#_ContentKey)) \ + { } \ + template \ + explicit _Name(Ts&&... contentArgs) \ + : Event(_EnumType) \ + , _content(QStringLiteral(#_ContentKey), \ + std::forward(contentArgs)...) \ + { } \ + const _ContentType& _ContentKey() const { return _content.value; } \ + QJsonObject toJson() const { return _content.toJson(); } \ + protected: \ + EventContent::SimpleContent<_ContentType> _content; \ + }; + + DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", EventType::Tag, TagsMap, tags) + DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", EventType::ReadMarker, + QString, event_id) +} diff --git a/events/event.cpp b/events/event.cpp index 74a2c3d7..f3e965e2 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -24,7 +24,7 @@ #include "roomavatarevent.h" #include "typingevent.h" #include "receiptevent.h" -#include "tagevent.h" +#include "accountdataevents.h" #include "redactionevent.h" #include "logging.h" @@ -88,7 +88,8 @@ EventPtr _impl::doMakeEvent(const QJsonObject& obj) return EventPtr(move(e)); return EventPtr { makeIfMatches(obj, obj["type"].toString()) }; + TypingEvent, ReceiptEvent, TagEvent, ReadMarkerEvent>( + obj, obj["type"].toString()) }; } RoomEvent::RoomEvent(Event::Type type) : Event(type) { } diff --git a/events/event.h b/events/event.h index f0ca2d15..eccfec41 100644 --- a/events/event.h +++ b/events/event.h @@ -45,7 +45,7 @@ namespace QMatrixClient enum class Type : quint16 { Unknown = 0, - Typing, Receipt, Tag, DirectChat, + Typing, Receipt, Tag, DirectChat, ReadMarker, RoomEventBase = 0x1000, RoomMessage = RoomEventBase + 1, RoomEncryptedMessage, Redaction, diff --git a/events/tagevent.cpp b/events/tagevent.cpp deleted file mode 100644 index c643ac62..00000000 --- a/events/tagevent.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2018 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 - */ - -#include "tagevent.h" - -using namespace QMatrixClient; - -TagRecord::TagRecord(const QJsonObject& json) - : order(json.value("order").toString()) -{ } - -TagEvent::TagEvent() - : Event(Type::Tag) -{ - // TODO: Support getting a list of tags and saving it -} - -TagEvent::TagEvent(const QJsonObject& obj) - : Event(Type::Tag, obj) -{ - Q_ASSERT(obj["type"].toString() == TypeId); -} - -QStringList TagEvent::tagNames() const -{ - return tagsObject().keys(); -} - -QHash TagEvent::tags() const -{ - QHash result; - auto allTags = tagsObject(); - for (auto it = allTags.begin(); it != allTags.end(); ++ it) - result.insert(it.key(), TagRecord(it.value().toObject())); - return result; -} - -bool TagEvent::empty() const -{ - return tagsObject().empty(); -} - -bool TagEvent::contains(const QString& name) const -{ - return tagsObject().contains(name); -} - -TagRecord TagEvent::recordForTag(const QString& name) const -{ - return TagRecord(tagsObject().value(name).toObject()); -} - -QJsonObject TagEvent::tagsObject() const -{ - return contentJson().value("tags").toObject(); -} diff --git a/events/tagevent.h b/events/tagevent.h deleted file mode 100644 index 26fe8788..00000000 --- a/events/tagevent.h +++ /dev/null @@ -1,74 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2018 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 -{ - static constexpr const char* FavouriteTag = "m.favourite"; - static constexpr const char* LowPriorityTag = "m.lowpriority"; - - struct TagRecord - { - explicit TagRecord(const QJsonObject& json = {}); - - QString order; - }; - - class TagEvent : public Event - { - public: - TagEvent(); - explicit TagEvent(const QJsonObject& obj); - - /** Get the list of tag names */ - QStringList tagNames() const; - - /** Get the list of tags along with information on each */ - QHash tags() const; - - /** Check if the event lists no tags */ - bool empty() const; - - /** Check whether the tags list contains the specified name */ - bool contains(const QString& name) const; - - /** Get the record for the given tag name */ - TagRecord recordForTag(const QString& name) const; - - /** Get the whole tags content as a JSON object - * It's NOT recommended to use this method directly from client code. - * Use other convenience methods provided by the class. - */ - QJsonObject tagsObject() const; - - static constexpr const char * TypeId = "m.tag"; - }; - - using TagEventPtr = event_ptr_tt; - - inline QJsonValue toJson(const TagEventPtr& tagEvent) - { - return QJsonObject {{ "type", "m.tag" }, - // TODO: Replace tagsObject() with a genuine list of tags - // (or make the needed JSON upon TagEvent creation) - { "content", QJsonObject {{ "tags", tagEvent->tagsObject() }} }}; - } -} diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 7cfa94a1..c7b95617 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -24,7 +24,7 @@ HEADERS += \ $$PWD/events/roomavatarevent.h \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ - $$PWD/events/tagevent.h \ + $$PWD/events/accountdataevents.h \ $$PWD/events/redactionevent.h \ $$PWD/jobs/requestdata.h \ $$PWD/jobs/basejob.h \ @@ -56,7 +56,6 @@ SOURCES += \ $$PWD/events/roommemberevent.cpp \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ - $$PWD/events/tagevent.cpp \ $$PWD/jobs/requestdata.cpp \ $$PWD/jobs/basejob.cpp \ $$PWD/jobs/checkauthmethods.cpp \ diff --git a/room.cpp b/room.cpp index e03a2b5b..971e4121 100644 --- a/room.cpp +++ b/room.cpp @@ -65,7 +65,6 @@ enum EventsPlacement : int { Older = -1, Newer = 1 }; # define WORKAROUND_EXTENDED_INITIALIZER_LIST #endif - class Room::Private { public: @@ -106,7 +105,7 @@ class Room::Private QString firstDisplayedEventId; QString lastDisplayedEventId; QHash lastReadEventIds; - TagEventPtr tags = std::make_unique(); + TagsMap tags; QHash accountData; QString prevBatch; QPointer roomMessagesJob; @@ -574,27 +573,36 @@ void Room::resetHighlightCount() QStringList Room::tagNames() const { - return d->tags->tagNames(); + return d->tags.keys(); } -QHash Room::tags() const +TagsMap Room::tags() const { - return d->tags->tags(); + return d->tags; } TagRecord Room::tag(const QString& name) const { - return d->tags->recordForTag(name); + return d->tags.value(name); +} + +void Room::setTags(const TagsMap& newTags) +{ + if (newTags == d->tags) + return; + d->tags = newTags; + d->setAccountData(TagEvent(d->tags)); + emit tagsChanged(); } bool Room::isFavourite() const { - return d->tags->contains(FavouriteTag); + return d->tags.contains(FavouriteTag); } bool Room::isLowPriority() const { - return d->tags->contains(LowPriorityTag); + return d->tags.contains(LowPriorityTag); } const RoomMessageEvent* @@ -1485,13 +1493,19 @@ void Room::processAccountDataEvent(EventPtr event) switch (event->type()) { case EventType::Tag: - d->tags.reset(static_cast(event.release())); + { + auto newTags = static_cast(event.get())->tags(); + if (newTags == d->tags) + break; + d->tags = newTags; qCDebug(MAIN) << "Room" << id() << "is tagged with: " << tagNames().join(", "); emit tagsChanged(); break; + } default: - d->accountData[event->jsonType()] = event->contentJson().toVariantHash(); + d->accountData[event->jsonType()] = + event->contentJson().toVariantHash(); } } @@ -1644,7 +1658,7 @@ QJsonObject Room::Private::toJson() const } QJsonArray accountDataEvents; - if (!tags->empty()) + if (!tags.empty()) accountDataEvents.append(QMatrixClient::toJson(tags)); if (!accountData.empty()) diff --git a/room.h b/room.h index 71d5c433..bdd11452 100644 --- a/room.h +++ b/room.h @@ -20,13 +20,9 @@ #include "jobs/syncjob.h" #include "events/roommessageevent.h" -#include "events/tagevent.h" +#include "events/accountdataevents.h" #include "joinstate.h" -#include -#include -#include -#include #include #include @@ -241,9 +237,11 @@ namespace QMatrixClient Q_INVOKABLE void resetHighlightCount(); QStringList tagNames() const; - QHash tags() const; + TagsMap tags() const; TagRecord tag(const QString& name) const; + void setTags(const TagsMap& newTags); + /** Check whether the list of tags has m.favourite */ bool isFavourite() const; /** Check whether the list of tags has m.lowpriority */ -- cgit v1.2.3 From fcf335e202a49c62be29566daf233866cd2f3584 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 13:18:37 +0900 Subject: Room::toJson(): Fix caching of tags --- room.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/room.cpp b/room.cpp index 971e4121..86d1e6cd 100644 --- a/room.cpp +++ b/room.cpp @@ -1659,7 +1659,10 @@ QJsonObject Room::Private::toJson() const QJsonArray accountDataEvents; if (!tags.empty()) - accountDataEvents.append(QMatrixClient::toJson(tags)); + accountDataEvents.append(QJsonObject( + { { QStringLiteral("type"), QStringLiteral("m.tag") } + , { QStringLiteral("content"), TagEvent(tags).toJson() } + })); if (!accountData.empty()) { -- cgit v1.2.3 From f9cd6410623b7ddebf97e248584d1a8e838b4da8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 11:44:00 +0900 Subject: Room: addTag() and removeTag() Slightly changed TagRecord constructors to match. --- events/accountdataevents.h | 4 ++-- room.cpp | 20 ++++++++++++++++++++ room.h | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/events/accountdataevents.h b/events/accountdataevents.h index 78cf9c46..f3ba27bb 100644 --- a/events/accountdataevents.h +++ b/events/accountdataevents.h @@ -30,8 +30,8 @@ namespace QMatrixClient struct TagRecord { - TagRecord (QString order) : order(std::move(order)) { } - explicit TagRecord(const QJsonValue& jv = {}) + TagRecord (QString order = {}) : order(std::move(order)) { } + explicit TagRecord(const QJsonValue& jv) : order(jv.toObject().value("order").toString()) { } diff --git a/room.cpp b/room.cpp index 86d1e6cd..cb94ddb6 100644 --- a/room.cpp +++ b/room.cpp @@ -586,6 +586,26 @@ TagRecord Room::tag(const QString& name) const return d->tags.value(name); } +void Room::addTag(const QString& name, const TagRecord& record) +{ + if (d->tags.contains(name)) + return; + + d->tags.insert(name, record); + d->setAccountData(TagEvent(d->tags)); + emit tagsChanged(); +} + +void Room::removeTag(const QString& name) +{ + if (!d->tags.contains(name)) + return; + + d->tags.remove(name); + d->setAccountData(TagEvent(d->tags)); + emit tagsChanged(); +} + void Room::setTags(const TagsMap& newTags) { if (newTags == d->tags) diff --git a/room.h b/room.h index bdd11452..0eb5ecc3 100644 --- a/room.h +++ b/room.h @@ -240,6 +240,25 @@ namespace QMatrixClient TagsMap tags() const; TagRecord tag(const QString& name) const; + /** Add a new tag to this room + * If this room already has this tag, nothing happens. If it's a new + * tag for the room, the respective tag record is added to the set + * of tags and the new set is sent to the server to update other + * clients. + */ + void addTag(const QString& name, const TagRecord& record = {}); + + /** Remove a tag from the room */ + void removeTag(const QString& name); + + /** Overwrite the room's tags + * This completely replaces the existing room's tags with a set + * of new ones and updates the new set on the server. Unlike + * most other methods in Room, this one sends a signal about changes + * immediately, not waiting for confirmation from the server + * (because tags are saved in account data rather than in shared + * room state). + */ void setTags(const TagsMap& newTags); /** Check whether the list of tags has m.favourite */ -- cgit v1.2.3 From 2e7627528308da7629f1293757de2fb4bb22a7ad Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 5 Mar 2018 16:12:47 +0900 Subject: qmc-example: tests for redaction and tagging; send origin in test messages --- .travis.yml | 2 +- examples/qmc-example.cpp | 108 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 001ba11f..c9002c13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ script: - cd .. - qmake qmc-example.pro "CONFIG += debug" "CONFIG -= app_bundle" "QMAKE_CC = $CC" "QMAKE_CXX = $CXX" - make all -- $VALGRIND ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" '#qmc-test:matrix.org' +- $VALGRIND ./qmc-example "$QMC_TEST_USER" "$QMC_TEST_PWD" '#qmc-test:matrix.org' "Travis CI job $TRAVIS_JOB_NUMBER" notifications: webhooks: diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index e0aabca9..f63b32a2 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -2,18 +2,68 @@ #include "connection.h" #include "room.h" #include "user.h" +#include "jobs/sendeventjob.h" #include #include +#include #include using namespace QMatrixClient; using std::cout; using std::endl; -using std::bind; using namespace std::placeholders; -void onNewRoom(Room* r, const char* targetRoomName) +static int semaphor = 0; +static Room* targetRoom = nullptr; + +#define QMC_CHECK(origin, description, condition) \ + cout << (description) \ + << (!!(condition) ? " successul" : " FAILED") << endl; \ + targetRoom->postMessage(QString(origin) % ": " % QStringLiteral(description) % \ + (!!(condition) ? QStringLiteral(" successful") : \ + QStringLiteral(" FAILED")), MessageEventType::Notice) + +void addAndRemoveTag(const char* origin) +{ + ++semaphor; + static const auto TestTag = QStringLiteral("org.qmatrixclient.test"); + QObject::connect(targetRoom, &Room::tagsChanged, targetRoom, [=] { + cout << "Room " << targetRoom->id().toStdString() + << ", tag(s) changed:" << endl + << " " << targetRoom->tagNames().join(", ").toStdString() << endl; + if (targetRoom->tags().contains(TestTag)) + { + targetRoom->removeTag(TestTag); + QMC_CHECK(origin, "Tagging test", + !targetRoom->tags().contains(TestTag)); + --semaphor; + QObject::disconnect(targetRoom, &Room::tagsChanged, nullptr, nullptr); + } + }); + // The reverse order because tagsChanged is emitted synchronously. + targetRoom->addTag(TestTag); +} + +void sendAndRedact(const char* origin) +{ + ++semaphor; + auto* job = targetRoom->connection()->callApi(targetRoom->id(), + RoomMessageEvent("Message to redact")); + QObject::connect(job, &BaseJob::success, targetRoom, [job] { + targetRoom->redactEvent(job->eventId(), "qmc-example"); + }); + QObject::connect(targetRoom, &Room::replacedEvent, targetRoom, + [=] (const RoomEvent* newEvent) { + QMC_CHECK(origin, "Redaction", newEvent->isRedacted() && + newEvent->redactionReason() == "qmc-example"); + --semaphor; + QObject::disconnect(targetRoom, &Room::replacedEvent, + nullptr, nullptr); + }); +} + +void onNewRoom(Room* r, const char* targetRoomName, const char* origin) { cout << "New room: " << r->id().toStdString() << endl; QObject::connect(r, &Room::namesChanged, [=] { @@ -24,11 +74,15 @@ void onNewRoom(Room* r, const char* targetRoomName) if (targetRoomName && (r->name() == targetRoomName || r->canonicalAlias() == targetRoomName)) { - r->postMessage( - "This is a test message from an example application\n" - "The current user is " % r->localUser()->fullName(r) % "\n" % - QStringLiteral("This room has %1 member(s)") - .arg(r->memberCount()) % "\n" % + cout << "Found the target room, proceeding for tests" << endl; + targetRoom = r; + addAndRemoveTag(origin); + sendAndRedact(origin); + targetRoom->postMessage( + "This is a test notice from an example application\n" + "Origin: " % QString(origin) % "\n" + "The current user is " % + targetRoom->localUser()->fullName(targetRoom) % "\n" % // "The room is " % // (r->isDirectChat() ? "" : "not ") % "a direct chat\n" % "Have a good day", @@ -36,11 +90,7 @@ void onNewRoom(Room* r, const char* targetRoomName) ); } }); - QObject::connect(r, &Room::tagsChanged, [=] { - cout << "Room " << r->id().toStdString() << ", tag(s) changed:" << endl - << " " << r->tagNames().join(", ").toStdString() << endl << endl; - }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsRange timeline) { + QObject::connect(r, &Room::aboutToAddNewMessages, [r] (RoomEventsRange timeline) { cout << timeline.size() << " new event(s) in room " << r->id().toStdString() << endl; // for (const auto& item: timeline) @@ -56,13 +106,15 @@ void onNewRoom(Room* r, const char* targetRoomName) void finalize(Connection* conn) { + if (semaphor) + cout << "One or more tests FAILED" << endl; cout << "Logging out" << endl; conn->logout(); QObject::connect(conn, &Connection::loggedOut, QCoreApplication::instance(), [conn] { conn->deleteLater(); - QCoreApplication::instance()->processEvents(); - QCoreApplication::instance()->quit(); + QCoreApplication::processEvents(); + QCoreApplication::exit(semaphor); }); } @@ -83,10 +135,30 @@ int main(int argc, char* argv[]) }); const char* targetRoomName = argc >= 4 ? argv[3] : nullptr; if (targetRoomName) - cout << "Target room name: " << targetRoomName; + cout << "Target room name: " << targetRoomName << endl; + const char* origin = argc >= 5 ? argv[4] : nullptr; + if (origin) + cout << "Origin for the test message: " << origin << endl; QObject::connect(conn, &Connection::newRoom, - bind(onNewRoom, _1, targetRoomName)); - QObject::connect(conn, &Connection::syncDone, - bind(finalize, conn)); + [=](Room* room) { onNewRoom(room, targetRoomName, origin); }); + QObject::connect(conn, &Connection::syncDone, conn, [conn] { + cout << "Sync complete, " << semaphor << " tests in the air" << endl; + if (semaphor) + conn->sync(10000); + else + { + if (targetRoom) + { + auto j = conn->callApi(targetRoom->id(), + RoomMessageEvent("All tests finished")); + QObject::connect(j, &BaseJob::finished, + conn, [conn] { finalize(conn); }); + } + else + finalize(conn); + } + }); + // Big red countdown + QTimer::singleShot(180000, conn, [conn] { finalize(conn); }); return app.exec(); } -- cgit v1.2.3