From 1b11a6ee708291db37b0c7879eb103d81d70a6b7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Sep 2017 10:10:18 +0900 Subject: Event::originalJsonObject(), RoomEvent validations commented out * Event::originalJsonObject() exposes the original JSON for the event without converting it to QByteArray. This is useful to quickly dump an event into a bigger JSON without reconstructing a JSON object. * Validations in RoomEvent::RoomEvent() do more harm than good. The rest of the library tolerates absence of those attributes pretty well (it wouldn't be able to do much with that anyway); at the same time, dumping JSON to logs turns out to be pretty heavy, and throwing many invalid events at a client is a good way to hit its performance. --- events/event.cpp | 35 ++++++++++++++++++++--------------- events/event.h | 1 + 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'events') diff --git a/events/event.cpp b/events/event.cpp index 8a6de822..d718306d 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -48,6 +48,11 @@ QByteArray Event::originalJson() const return QJsonDocument(_originalJson).toJson(); } +QJsonObject Event::originalJsonObject() const +{ + return _originalJson; +} + QDateTime Event::toTimestamp(const QJsonValue& v) { Q_ASSERT(v.isDouble() || v.isNull() || v.isUndefined()); @@ -97,21 +102,21 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& rep) , _senderId(rep["sender"].toString()) , _txnId(rep["unsigned"].toObject().value("transactionId").toString()) { - if (_id.isEmpty()) - { - qCWarning(EVENTS) << "Can't find event_id in a room event"; - qCWarning(EVENTS) << formatJson << rep; - } - if (!rep.contains("origin_server_ts")) - { - qCWarning(EVENTS) << "Can't find server timestamp in a room event"; - qCWarning(EVENTS) << formatJson << rep; - } - if (_senderId.isEmpty()) - { - qCWarning(EVENTS) << "Can't find sender in a room event"; - qCWarning(EVENTS) << formatJson << rep; - } +// if (_id.isEmpty()) +// { +// qCWarning(EVENTS) << "Can't find event_id in a room event"; +// qCWarning(EVENTS) << formatJson << rep; +// } +// if (!rep.contains("origin_server_ts")) +// { +// qCWarning(EVENTS) << "Can't find server timestamp in a room event"; +// qCWarning(EVENTS) << formatJson << rep; +// } +// if (_senderId.isEmpty()) +// { +// qCWarning(EVENTS) << "Can't find sender in a room event"; +// qCWarning(EVENTS) << formatJson << rep; +// } if (!_txnId.isEmpty()) qCDebug(EVENTS) << "Event transactionId:" << _txnId; } diff --git a/events/event.h b/events/event.h index 8760aa28..7db14100 100644 --- a/events/event.h +++ b/events/event.h @@ -43,6 +43,7 @@ namespace QMatrixClient Type type() const { return _type; } QByteArray originalJson() const; + QJsonObject originalJsonObject() const; // According to the CS API spec, every event also has // a "content" object; but since its structure is different for -- cgit v1.2.3 From c5c26ff4a09eecaa6d8e1507087566ccf0fd96b4 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Tue, 19 Sep 2017 10:54:07 +0900 Subject: Room: cache last read event and unread messages flag with the room state Since there's no such thing as "unread messages flag" in the CS API spec, there's now a non-standard key-value in cached m.read receipts for that. --- events/receiptevent.cpp | 3 +- events/receiptevent.h | 2 + room.cpp | 115 +++++++++++++++++++++++++++--------------------- 3 files changed, 70 insertions(+), 50 deletions(-) (limited to 'events') diff --git a/events/receiptevent.cpp b/events/receiptevent.cpp index e3478cf1..3d6be9f1 100644 --- a/events/receiptevent.cpp +++ b/events/receiptevent.cpp @@ -46,7 +46,7 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) { Q_ASSERT(obj["type"].toString() == jsonType); - const QJsonObject contents = obj["content"].toObject(); + const QJsonObject contents = contentJson(); _eventsWithReceipts.reserve(static_cast(contents.size())); for( auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt ) { @@ -66,5 +66,6 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) } _eventsWithReceipts.push_back({eventIt.key(), receipts}); } + _unreadMessages = obj["x-qmatrixclient.unread_messages"].toBool(); } diff --git a/events/receiptevent.h b/events/receiptevent.h index 1d280822..cbe36b10 100644 --- a/events/receiptevent.h +++ b/events/receiptevent.h @@ -41,9 +41,11 @@ namespace QMatrixClient EventsWithReceipts eventsWithReceipts() const { return _eventsWithReceipts; } + bool unreadMessages() const { return _unreadMessages; } private: EventsWithReceipts _eventsWithReceipts; + bool _unreadMessages; // Spec extension for caching purposes static constexpr const char * jsonType = "m.receipt"; }; diff --git a/room.cpp b/room.cpp index 1393e145..241a885d 100644 --- a/room.cpp +++ b/room.cpp @@ -789,6 +789,8 @@ void Room::processEphemeralEvent(Event* event) d->setLastReadEvent(m, p.evtId); } } + if (receiptEvent->unreadMessages()) + d->unreadMessages = true; break; } default: @@ -877,67 +879,81 @@ void Room::Private::updateDisplayname() emit q->displaynameChanged(q); } -QJsonObject Room::Private::toJson() const { - QJsonValue nowTimestamp { QDateTime::currentMSecsSinceEpoch() }; - QJsonArray stateEvents; - - QJsonObject nameEvent; - nameEvent.insert("type", QStringLiteral("m.room.name")); +QJsonObject stateEventToJson(const QString& type, const QString& name, + const QJsonValue& content) +{ + QJsonObject contentObj; + contentObj.insert(name, content); - QJsonObject nameEventContent; - nameEventContent.insert("name", this->name); + QJsonObject eventObj; + eventObj.insert("type", type); + eventObj.insert("content", contentObj); - nameEvent.insert("content", nameEventContent); - stateEvents.append(nameEvent); + return eventObj; +} - for (const auto &i : this->membersMap) { - QJsonObject content; - content.insert("membership", QStringLiteral("join")); - content.insert("displayname", i->displayname()); - // avatar URL is not available +QJsonObject Room::Private::toJson() const +{ + QJsonObject result; + { + QJsonArray stateEvents; - QJsonObject memberEvent; - memberEvent.insert("type", QStringLiteral("m.room.member")); - memberEvent.insert("sender", i->id()); - memberEvent.insert("state_key", i->id()); - memberEvent.insert("content", content); - memberEvent.insert("membership", QStringLiteral("join")); - memberEvent.insert("origin_server_ts", nowTimestamp); - stateEvents.append(memberEvent); - } + stateEvents.append(stateEventToJson("m.room.name", "name", name)); + stateEvents.append(stateEventToJson("m.room.topic", "topic", topic)); + stateEvents.append(stateEventToJson("m.room.aliases", "aliases", + QJsonArray::fromStringList(aliases))); + stateEvents.append(stateEventToJson("m.room.canonical_alias", "alias", + canonicalAlias)); - { - QJsonArray aliases; - for (const auto &i : this->aliases) { - aliases.append(QJsonValue(i)); + for (const auto &i : membersMap) + { + QJsonObject content; + content.insert("membership", QStringLiteral("join")); + content.insert("displayname", i->displayname()); + // avatar URL is not available + + QJsonObject memberEvent; + memberEvent.insert("type", QStringLiteral("m.room.member")); + memberEvent.insert("state_key", i->id()); + memberEvent.insert("content", content); + stateEvents.append(memberEvent); } - QJsonObject content; - content.insert("aliases", aliases); + QJsonObject roomStateObj; + roomStateObj.insert("events", stateEvents); - QJsonObject aliasEvent; - aliasEvent.insert("type", QStringLiteral("m.room.aliases")); - aliasEvent.insert("origin_server_ts", nowTimestamp); - aliasEvent.insert("content", content); - - stateEvents.append(aliasEvent); + result.insert("state", roomStateObj); } + if (!q->readMarkerEventId().isEmpty()) { - QJsonObject content; - content.insert("alias", this->canonicalAlias); - - QJsonObject canonicalAliasEvent; - canonicalAliasEvent.insert("type", QStringLiteral("m.room.canonical_alias")); - canonicalAliasEvent.insert("origin_server_ts", nowTimestamp); - stateEvents.append(canonicalAliasEvent); - } + QJsonArray ephemeralEvents; + { + // Don't dump the timestamp because it's useless in the cache. + QJsonObject user; + user.insert(connection->userId(), {}); + + QJsonObject receipt; + receipt.insert("m.read", user); + + QJsonObject lastReadEvent; + lastReadEvent.insert(q->readMarkerEventId(), receipt); + + QJsonObject receiptsObj; + receiptsObj.insert("type", QStringLiteral("m.receipt")); + receiptsObj.insert("content", lastReadEvent); + // In extension of the spec we add a hint to the receipt event + // to allow setting the unread indicator without downloading + // and analysing the timeline. + receiptsObj.insert("x-qmatrixclient.unread_messages", unreadMessages); + ephemeralEvents.append(receiptsObj); + } - QJsonObject roomStateObj; - roomStateObj.insert("events", stateEvents); + QJsonObject ephemeralObj; + ephemeralObj.insert("events", ephemeralEvents); - QJsonObject result; - result.insert("state", roomStateObj); + result.insert("ephemeral", ephemeralObj); + } QJsonObject unreadNotificationsObj; unreadNotificationsObj.insert("highlight_count", highlightCount); @@ -947,7 +963,8 @@ QJsonObject Room::Private::toJson() const { return result; } -QJsonObject Room::toJson() const { +QJsonObject Room::toJson() const +{ return d->toJson(); } -- cgit v1.2.3 From d51e7a43736096eb2776acd99e1aab6deeb65667 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 15 Sep 2017 18:43:29 +0900 Subject: jobs: SetRoomStateJob (with or without state key); setting room topic --- CMakeLists.txt | 1 + events/roomtopicevent.h | 12 +++++++++ jobs/setroomstatejob.cpp | 32 ++++++++++++++++++++++++ jobs/setroomstatejob.h | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ room.cpp | 8 +++++- room.h | 1 + 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 jobs/setroomstatejob.cpp create mode 100644 jobs/setroomstatejob.h (limited to 'events') diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e3abce1..e55335ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ set(libqmatrixclient_SRCS jobs/checkauthmethods.cpp jobs/passwordlogin.cpp jobs/sendeventjob.cpp + jobs/setroomstatejob.cpp jobs/postreceiptjob.cpp jobs/joinroomjob.cpp jobs/leaveroomjob.cpp diff --git a/events/roomtopicevent.h b/events/roomtopicevent.h index fb849afe..95ad0e04 100644 --- a/events/roomtopicevent.h +++ b/events/roomtopicevent.h @@ -25,6 +25,9 @@ namespace QMatrixClient class RoomTopicEvent: public RoomEvent { public: + explicit RoomTopicEvent(const QString& topic) + : RoomEvent(Type::RoomTopic), _topic(topic) + { } explicit RoomTopicEvent(const QJsonObject& obj) : RoomEvent(Type::RoomTopic, obj) , _topic(contentJson()["topic"].toString()) @@ -32,6 +35,15 @@ namespace QMatrixClient QString topic() const { return _topic; } + QJsonObject toJson() const + { + QJsonObject obj; + obj.insert("topic", _topic); + return obj; + } + + static constexpr const char* TypeId = "m.room.topic"; + private: QString _topic; }; diff --git a/jobs/setroomstatejob.cpp b/jobs/setroomstatejob.cpp new file mode 100644 index 00000000..c2beb87b --- /dev/null +++ b/jobs/setroomstatejob.cpp @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (C) 2015 Felix Rohrbach + * + * 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 "setroomstatejob.h" + +using namespace QMatrixClient; + +BaseJob::Status SetRoomStateJob::parseJson(const QJsonDocument& data) +{ + _eventId = data.object().value("event_id").toString(); + if (!_eventId.isEmpty()) + return Success; + + qCDebug(JOBS) << data; + return { UserDefinedError, "No event_id in the JSON response" }; +} + diff --git a/jobs/setroomstatejob.h b/jobs/setroomstatejob.h new file mode 100644 index 00000000..1c72f31c --- /dev/null +++ b/jobs/setroomstatejob.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * Copyright (C) 2015 Felix Rohrbach + * + * 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 "basejob.h" + +#include "connectiondata.h" + +namespace QMatrixClient +{ + class SetRoomStateJob: public BaseJob + { + public: + /** + * Constructs a job that sets a state using an arbitrary room event + * with a state key. + */ + template + SetRoomStateJob(const ConnectionData* connection, const QString& roomId, + const EvT* event, const QString& stateKey) + : BaseJob(connection, HttpVerb::Put, "SetRoomStateJob", + QStringLiteral("_matrix/client/r0/rooms/%1/state/%2/%3") + .arg(roomId, EvT::TypeId, stateKey), + Query(), + Data(event->toJson())) + { } + /** + * Constructs a job that sets a state using an arbitrary room event + * without a state key. + */ + template + SetRoomStateJob(const ConnectionData* connection, const QString& roomId, + const EvT* event) + : BaseJob(connection, HttpVerb::Put, "SetRoomStateJob", + QStringLiteral("_matrix/client/r0/rooms/%1/state/%2") + .arg(roomId, EvT::TypeId), + Query(), + Data(event->toJson())) + { } + + QString eventId() const { return _eventId; } + + protected: + Status parseJson(const QJsonDocument& data) override; + + private: + QString _eventId; + }; +} // namespace QMatrixClient diff --git a/room.cpp b/room.cpp index ae8c7e63..2ba3766a 100644 --- a/room.cpp +++ b/room.cpp @@ -26,6 +26,7 @@ #include #include // for efficient string concats (operator%) #include +#include #include "connection.h" #include "state.h" @@ -595,6 +596,12 @@ void Room::postMessage(RoomMessageEvent* event) connection()->callApi(id(), event); } +void Room::setTopic(const QString& newTopic) +{ + RoomTopicEvent evt(newTopic); + connection()->callApi(id(), &evt); +} + void Room::getPreviousContent(int limit) { d->getPreviousContent(limit); @@ -1032,4 +1039,3 @@ bool MemberSorter::operator()(User *u1, User *u2) const n2.remove(0, 1); return n1.localeAwareCompare(n2) < 0; } - diff --git a/room.h b/room.h index 5ea89418..393dced3 100644 --- a/room.h +++ b/room.h @@ -153,6 +153,7 @@ namespace QMatrixClient void postMessage(RoomMessageEvent* event); /** @deprecated */ void postMessage(const QString& type, const QString& plainText); + void setTopic(const QString& newTopic); void getPreviousContent(int limit = 10); -- cgit v1.2.3