diff options
Diffstat (limited to 'lib/events')
36 files changed, 596 insertions, 460 deletions
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 8cea0ec8..9cf77be3 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -4,7 +4,6 @@ #pragma once #include "event.h" -#include "eventcontent.h" namespace Quotient { constexpr const char* FavouriteTag = "m.favourite"; @@ -16,12 +15,12 @@ struct TagRecord { order_type order; - TagRecord(order_type order = none) : order(std::move(order)) {} + TagRecord(order_type order = none) : order(order) {} bool operator<(const TagRecord& other) const { // Per The Spec, rooms with no order should be after those with order, - // against optional<>::operator<() convention. + // against std::optional<>::operator<() convention. return order && (!other.order || *order < *other.order); } }; @@ -55,15 +54,15 @@ using TagsMap = QHash<QString, TagRecord>; public: \ using content_type = _ContentType; \ DEFINE_EVENT_TYPEID(_TypeId, _Name) \ - explicit _Name(QJsonObject obj) : Event(typeId(), std::move(obj)) {} \ - explicit _Name(_ContentType content) \ + explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \ + explicit _Name(const content_type& content) \ : Event(typeId(), matrixTypeId(), \ - QJsonObject { { QStringLiteral(#_ContentKey), \ - toJson(std::move(content)) } }) \ + QJsonObject { \ + { QStringLiteral(#_ContentKey), toJson(content) } }) \ {} \ auto _ContentKey() const \ { \ - return content<content_type>(#_ContentKey##_ls); \ + return contentPart<content_type>(#_ContentKey##_ls); \ } \ }; \ REGISTER_EVENT_TYPE(_Name) \ diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h index 6132cb44..4c01c941 100644 --- a/lib/events/callanswerevent.h +++ b/lib/events/callanswerevent.h @@ -19,11 +19,11 @@ public: int lifetime() const { - return content<int>("lifetime"_ls); + return contentPart<int>("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["answer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart<QJsonObject>("answer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp deleted file mode 100644 index b87c8e9b..00000000 --- a/lib/events/callcandidatesevent.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Marius Gripsgard <marius@ubports.com> -// SPDX-FileCopyrightText: 2018 Josip Delic <delijati@googlemail.com> -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "callcandidatesevent.h" - -/* -m.call.candidates -{ - "age": 242352, - "content": { - "call_id": "12345", - "candidates": [ - { - "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 -43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio" - } - ], - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.candidates" -} -*/ diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h index c2ccac3b..74c38f2c 100644 --- a/lib/events/callcandidatesevent.h +++ b/lib/events/callcandidatesevent.h @@ -25,17 +25,17 @@ public: QJsonArray candidates() const { - return content<QJsonArray>("candidates"_ls); + return contentPart<QJsonArray>("candidates"_ls); } QString callId() const { - return content<QString>("call_id"); + return contentPart<QString>("call_id"); } int version() const { - return content<int>("version"); + return contentPart<int>("version"); } }; diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp deleted file mode 100644 index 43bc4db0..00000000 --- a/lib/events/callhangupevent.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/****************************************************************************** - * SPDX-FileCopyrightText: 2017 Marius Gripsgard <marius@ubports.com> - * SPDX-FileCopyrightText: 2018 Josip Delic <delijati@googlemail.com> - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "callhangupevent.h" - -/* -m.call.hangup -{ - "age": 242352, - "content": { - "call_id": "12345", - "version": 0 - }, - "event_id": "$WLGTSEFSEF:localhost", - "origin_server_ts": 1431961217939, - "room_id": "!Cuyf34gef24t:localhost", - "sender": "@example:localhost", - "type": "m.call.hangup" -} -*/ - -using namespace Quotient; - -CallHangupEvent::CallHangupEvent(const QJsonObject& obj) - : CallEventBase(typeId(), obj) -{ - qCDebug(EVENTS) << "Call Hangup event"; -} - -CallHangupEvent::CallHangupEvent(const QString& callId) - : CallEventBase(typeId(), matrixTypeId(), callId, 0) -{} diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h index 24382ac2..f3f82833 100644 --- a/lib/events/callhangupevent.h +++ b/lib/events/callhangupevent.h @@ -11,8 +11,12 @@ class CallHangupEvent : public CallEventBase { public: DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent) - explicit CallHangupEvent(const QJsonObject& obj); - explicit CallHangupEvent(const QString& callId); + explicit CallHangupEvent(const QJsonObject& obj) + : CallEventBase(typeId(), obj) + {} + explicit CallHangupEvent(const QString& callId) + : CallEventBase(typeId(), matrixTypeId(), callId, 0) + {} }; REGISTER_EVENT_TYPE(CallHangupEvent) diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h index d3454c4f..80b7d651 100644 --- a/lib/events/callinviteevent.h +++ b/lib/events/callinviteevent.h @@ -18,11 +18,11 @@ public: int lifetime() const { - return content<int>("lifetime"_ls); + return contentPart<int>("lifetime"_ls); } // FIXME: Omittable<>? QString sdp() const { - return contentJson()["offer"_ls].toObject().value("sdp"_ls).toString(); + return contentPart<QJsonObject>("offer"_ls).value("sdp"_ls).toString(); } }; diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index eb7123eb..de89a7c6 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -7,7 +7,6 @@ #include "roomevent.h" namespace Quotient { -class Room; /* * While the specification states: * @@ -27,7 +26,6 @@ class Room; * one and doesn't add new restrictions, just provides additional features. */ class EncryptedEvent : public RoomEvent { - Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) @@ -43,7 +41,7 @@ public: QString algorithm() const { - QString algo = content<QString>(AlgorithmKeyL); + QString algo = contentPart<QString>(AlgorithmKeyL); if (!SupportedAlgorithms.contains(algo)) { qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo << "is not supported"; @@ -52,17 +50,17 @@ public: } QByteArray ciphertext() const { - return content<QString>(CiphertextKeyL).toLatin1(); + return contentPart<QString>(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return content<QJsonObject>(CiphertextKeyL).value(identityKey).toObject(); + return contentPart<QJsonObject>(CiphertextKeyL).value(identityKey).toObject(); } - QString senderKey() const { return content<QString>(SenderKeyKeyL); } + QString senderKey() const { return contentPart<QString>(SenderKeyKeyL); } /* device_id and session_id are required with Megolm */ - QString deviceId() const { return content<QString>(DeviceIdKeyL); } - QString sessionId() const { return content<QString>(SessionIdKeyL); } + QString deviceId() const { return contentPart<QString>(DeviceIdKeyL); } + QString sessionId() const { return contentPart<QString>(SessionIdKeyL); } }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h new file mode 100644 index 00000000..24ac9de1 --- /dev/null +++ b/lib/events/encryptedfile.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPl-2.1-or-later + +#pragma once + +#include "converters.h" + +namespace Quotient { +/** + * JSON Web Key object as specified in + * https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes + * The only currently relevant member is `k`, the rest needs to be set to the defaults specified in the spec. + */ +struct JWK +{ + Q_GADGET + Q_PROPERTY(QString kty MEMBER kty CONSTANT) + Q_PROPERTY(QStringList keyOps MEMBER keyOps CONSTANT) + Q_PROPERTY(QString alg MEMBER alg CONSTANT) + Q_PROPERTY(QString k MEMBER k CONSTANT) + Q_PROPERTY(bool ext MEMBER ext CONSTANT) + +public: + QString kty; + QStringList keyOps; + QString alg; + QString k; + bool ext; +}; + +struct EncryptedFile +{ + Q_GADGET + Q_PROPERTY(QUrl url MEMBER url CONSTANT) + Q_PROPERTY(JWK key MEMBER key CONSTANT) + Q_PROPERTY(QString iv MEMBER iv CONSTANT) + Q_PROPERTY(QHash<QString, QString> hashes MEMBER hashes CONSTANT) + Q_PROPERTY(QString v MEMBER v CONSTANT) + +public: + QUrl url; + JWK key; + QString iv; + QHash<QString, QString> hashes; + QString v; +}; + +template <> +struct JsonObjectConverter<EncryptedFile> { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) + { + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); + } + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) + { + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); + } +}; + +template <> +struct JsonObjectConverter<JWK> { + static void dumpTo(QJsonObject& jo, const JWK& pod) + { + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); + } + static void fillFrom(const QJsonObject& jo, JWK& pod) + { + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); + } +}; +} // namespace Quotient diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index 490a5e8a..aa05a96e 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -39,6 +39,14 @@ EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) {} +EncryptionEventContent::EncryptionEventContent(EncryptionType et) + : encryption(et) +{ + if(encryption != Undefined) { + algorithm = encryptionStrings[encryption]; + } +} + void EncryptionEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h index f9bbab12..14439fcc 100644 --- a/lib/events/encryptionevent.h +++ b/lib/events/encryptionevent.h @@ -12,9 +12,7 @@ class EncryptionEventContent : public EventContent::Base { public: enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined }; - explicit EncryptionEventContent(EncryptionType et = Undefined) - : encryption(et) - {} + explicit EncryptionEventContent(EncryptionType et = Undefined); explicit EncryptionEventContent(const QJsonObject& json); EncryptionType encryption; @@ -40,6 +38,7 @@ public: // default value : StateEvent(typeId(), obj) {} + EncryptionEvent(EncryptionEvent&&) = delete; template <typename... ArgTs> EncryptionEvent(ArgTs&&... contentArgs) : StateEvent(typeId(), matrixTypeId(), QString(), diff --git a/lib/events/event.cpp b/lib/events/event.cpp index 3d66ab55..96be717c 100644 --- a/lib/events/event.cpp +++ b/lib/events/event.cpp @@ -46,14 +46,11 @@ QString Event::matrixType() const { return fullJson()[TypeKeyL].toString(); } QByteArray Event::originalJson() const { return QJsonDocument(_json).toJson(); } -// On const below: this is to catch accidental attempts to change event JSON -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::contentJson() const { return fullJson()[ContentKeyL].toObject(); } -// NOLINTNEXTLINE(readability-const-return-type) const QJsonObject Event::unsignedJson() const { return fullJson()[UnsignedKeyL].toObject(); diff --git a/lib/events/event.h b/lib/events/event.h index f8f8311d..8f62872d 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -5,6 +5,7 @@ #include "converters.h" #include "logging.h" +#include "function_traits.h" namespace Quotient { // === event_ptr_tt<> and type casting facilities === @@ -28,31 +29,30 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr) // === Standard Matrix key names and basicEventJson() === -static const auto TypeKey = QStringLiteral("type"); -static const auto BodyKey = QStringLiteral("body"); -static const auto ContentKey = QStringLiteral("content"); -static const auto EventIdKey = QStringLiteral("event_id"); -static const auto SenderKey = QStringLiteral("sender"); -static const auto RoomIdKey = QStringLiteral("room_id"); -static const auto UnsignedKey = QStringLiteral("unsigned"); -static const auto StateKeyKey = QStringLiteral("state_key"); -static const auto TypeKeyL = "type"_ls; -static const auto BodyKeyL = "body"_ls; -static const auto ContentKeyL = "content"_ls; -static const auto EventIdKeyL = "event_id"_ls; -static const auto SenderKeyL = "sender"_ls; -static const auto RoomIdKeyL = "room_id"_ls; -static const auto UnsignedKeyL = "unsigned"_ls; -static const auto RedactedCauseKeyL = "redacted_because"_ls; -static const auto PrevContentKeyL = "prev_content"_ls; -static const auto StateKeyKeyL = "state_key"_ls; +constexpr auto TypeKeyL = "type"_ls; +constexpr auto BodyKeyL = "body"_ls; +constexpr auto ContentKeyL = "content"_ls; +constexpr auto EventIdKeyL = "event_id"_ls; +constexpr auto SenderKeyL = "sender"_ls; +constexpr auto RoomIdKeyL = "room_id"_ls; +constexpr auto UnsignedKeyL = "unsigned"_ls; +constexpr auto RedactedCauseKeyL = "redacted_because"_ls; +constexpr auto PrevContentKeyL = "prev_content"_ls; +constexpr auto StateKeyKeyL = "state_key"_ls; +const QString TypeKey { TypeKeyL }; +const QString BodyKey { BodyKeyL }; +const QString ContentKey { ContentKeyL }; +const QString EventIdKey { EventIdKeyL }; +const QString SenderKey { SenderKeyL }; +const QString RoomIdKey { RoomIdKeyL }; +const QString UnsignedKey { UnsignedKeyL }; +const QString StateKeyKey { StateKeyKeyL }; /// Make a minimal correct Matrix event JSON -template <typename StrT> -inline QJsonObject basicEventJson(StrT matrixType, const QJsonObject& content) +inline QJsonObject basicEventJson(const QString& matrixType, + const QJsonObject& content) { - return { { TypeKey, std::forward<StrT>(matrixType) }, - { ContentKey, content } }; + return { { TypeKey, matrixType }, { ContentKey, content } }; } // === Event types and event types registry === @@ -76,8 +76,7 @@ public: private: EventTypeRegistry() = default; - Q_DISABLE_COPY(EventTypeRegistry) - DISABLE_MOVE(EventTypeRegistry) + Q_DISABLE_COPY_MOVE(EventTypeRegistry) static EventTypeRegistry& get() { @@ -111,97 +110,90 @@ inline event_type_t typeId() inline event_type_t unknownEventTypeId() { return typeId<void>(); } -// === EventFactory === +// === Event creation facilities === -/** Create an event of arbitrary type from its arguments */ +//! Create an event of arbitrary type from its arguments template <typename EventT, typename... ArgTs> inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args) { return std::make_unique<EventT>(std::forward<ArgTs>(args)...); } -template <typename BaseEventT> -class EventFactory { -public: - template <typename FnT> - static auto addMethod(FnT&& method) - { - factories().emplace_back(std::forward<FnT>(method)); - return 0; - } - - /** Chain two type factories - * Adds the factory class of EventT2 (EventT2::factory_t) to - * the list in factory class of EventT1 (EventT1::factory_t) so - * that when EventT1::factory_t::make() is invoked, types of - * EventT2 factory are looked through as well. This is used - * to include RoomEvent types into the more general Event factory, - * and state event types into the RoomEvent factory. - */ - template <typename EventT> - static auto chainFactory() - { - return addMethod(&EventT::factory_t::make); - } - - static event_ptr_tt<BaseEventT> make(const QJsonObject& json, - const QString& matrixType) - { - for (const auto& f : factories()) - if (auto e = f(json, matrixType)) - return e; - return nullptr; - } - -private: - static auto& factories() +namespace _impl { + template <class EventT, class BaseEventT> + event_ptr_tt<BaseEventT> makeIfMatches(const QJsonObject& json, + const QString& matrixType) { - using inner_factory_tt = std::function<event_ptr_tt<BaseEventT>( - const QJsonObject&, const QString&)>; - static std::vector<inner_factory_tt> _factories {}; - return _factories; + return QLatin1String(EventT::matrixTypeId()) == matrixType + ? makeEvent<EventT>(json) + : nullptr; } -}; -/** Add a type to its default factory - * Adds a standard factory method (via makeEvent<>) for a given - * type to EventT::factory_t factory class so that it can be - * created dynamically from loadEvent<>(). - * - * \tparam EventT the type to enable dynamic creation of - * \return the registered type id - * \sa loadEvent, Event::type - */ -template <typename EventT> -inline auto setupFactory() -{ - qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId(); - return EventT::factory_t::addMethod([](const QJsonObject& json, - const QString& jsonMatrixType) { - return EventT::matrixTypeId() == jsonMatrixType ? makeEvent<EventT>(json) - : nullptr; - }); -} - -template <typename EventT> -inline auto registerEventType() -{ - // Initialise exactly once, even if this function is called twice for - // the same type (for whatever reason - you never know the ways of - // static initialisation is done). - static const auto _ = setupFactory<EventT>(); - return _; // Only to facilitate usage in static initialisation -} + //! \brief A family of event factories to create events from CS API responses + //! + //! Each of these factories, as instantiated by event base types (Event, + //! RoomEvent etc.) is capable of producing an event object derived from + //! \p BaseEventT, using the JSON payload and the event type passed to its + //! make() method. Don't use these directly to make events; use loadEvent() + //! overloads as the frontend for these. Never instantiate new factories + //! outside of base event classes. + //! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory, + //! StateEventBase::factory + template <typename BaseEventT> + class EventFactory + : private std::vector<event_ptr_tt<BaseEventT> (*)(const QJsonObject&, + const QString&)> { + // Actual makeIfMatches specialisations will differ in the first + // template parameter but that doesn't affect the function type + public: + explicit EventFactory(const char* name = "") + : name(name) + { + static auto yetToBeConstructed = true; + Q_ASSERT(yetToBeConstructed); + if (!yetToBeConstructed) // For Release builds that pass Q_ASSERT + qCritical(EVENTS) + << "Another EventFactory for the same base type is being " + "created - event creation logic will be splintered"; + yetToBeConstructed = false; + } + EventFactory(const EventFactory&) = delete; + + //! \brief Add a method to create events of a given type + //! + //! Adds a standard factory method (makeIfMatches) for \p EventT so that + //! event objects of this type can be created dynamically by loadEvent. + //! The caller is responsible for ensuring this method is called only + //! once per type. + //! \sa makeIfMatches, loadEvent, Quotient::loadEvent + template <class EventT> + bool addMethod() + { + this->emplace_back(&makeIfMatches<EventT, BaseEventT>); + qDebug(EVENTS) << "Added factory method for" + << EventT::matrixTypeId() << "events;" << this->size() + << "methods in the" << name << "chain by now"; + return true; + } + + auto loadEvent(const QJsonObject& json, const QString& matrixType) + { + for (const auto& f : *this) + if (auto e = f(json, matrixType)) + return e; + return makeEvent<BaseEventT>(unknownEventTypeId(), json); + } + + const char* const name; + }; +} // namespace _impl // === Event === class Event { - Q_GADGET - Q_PROPERTY(Type type READ type CONSTANT) - Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) public: using Type = event_type_t; - using factory_t = EventFactory<Event>; + static inline _impl::EventFactory<Event> factory { "Event" }; explicit Event(Type type, const QJsonObject& json); explicit Event(Type type, event_mtype_t matrixType, @@ -213,7 +205,10 @@ public: Type type() const { return _type; } QString matrixType() const; + [[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() " + "or by other means")]] QByteArray originalJson() const; + [[deprecated("Use fullJson() instead")]] // QJsonObject originalJsonObject() const { return fullJson(); } const QJsonObject& fullJson() const { return _json; } @@ -222,19 +217,30 @@ public: // a "content" object; but since its structure is different for // different types, we're implementing it per-event type. + // NB: const return types below are meant to catch accidental attempts + // to change event JSON (e.g., consider contentJson()["inexistentKey"]). + const QJsonObject contentJson() const; - const QJsonObject unsignedJson() const; + + template <typename T = QJsonValue, typename KeyT> + const T contentPart(KeyT&& key) const + { + return fromJson<T>(contentJson()[std::forward<KeyT>(key)]); + } template <typename T> + [[deprecated("Use contentPart() to get a part of the event content")]] // T content(const QString& key) const { - return fromJson<T>(contentJson()[key]); + return contentPart<T>(key); } - template <typename T> - T content(QLatin1String key) const + const QJsonObject unsignedJson() const; + + template <typename T = QJsonValue, typename KeyT> + const T unsignedPart(KeyT&& key) const { - return fromJson<T>(contentJson()[key]); + return fromJson<T>(unsignedJson()[std::forward<KeyT>(key)]); } friend QDebug operator<<(QDebug dbg, const Event& e) @@ -262,7 +268,7 @@ template <typename EventT> using EventsArray = std::vector<event_ptr_tt<EventT>>; using Events = EventsArray<Event>; -// === Macros used with event class definitions === +// === Facilities for event class definitions === // This macro should be used in a public section of an event class to // provide matrixTypeId() and typeId(). @@ -274,14 +280,28 @@ using Events = EventsArray<Event>; // This macro should be put after an event class definition (in .h or .cpp) // to enable its deserialisation from a /sync and other // polymorphic event arrays -#define REGISTER_EVENT_TYPE(_Type) \ - namespace { \ - [[maybe_unused]] static const auto _factoryAdded##_Type = \ - registerEventType<_Type>(); \ - } \ +#define REGISTER_EVENT_TYPE(_Type) \ + [[maybe_unused]] inline const auto _factoryAdded##_Type = \ + _Type::factory.addMethod<_Type>(); \ // End of macro -// === is<>(), eventCast<>() and visit<>() === +// === Event loading === +// (see also event_loader.h) + +//! \brief Point of customisation to dynamically load events +//! +//! The default specialisation of this calls BaseEventT::factory and if that +//! fails (i.e. returns nullptr) creates an unknown event of BaseEventT. +//! Other specialisations may reuse other factories, add validations common to +//! BaseEventT, and so on +template <class BaseEventT> +event_ptr_tt<BaseEventT> doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + return BaseEventT::factory.loadEvent(json, matrixType); +} + +// === is<>(), eventCast<>() and switchOnType<>() === template <class EventT> inline bool is(const Event& e) @@ -303,12 +323,12 @@ inline auto eventCast(const BasePtrT& eptr) : nullptr; } -// A single generic catch-all visitor +// A trivial generic catch-all "switch" template <class BaseEventT, typename FnT> -inline auto visit(const BaseEventT& event, FnT&& visitor) - -> decltype(visitor(event)) +inline auto switchOnType(const BaseEventT& event, FnT&& fn) + -> decltype(fn(event)) { - return visitor(event); + return fn(event); } namespace _impl { @@ -319,52 +339,60 @@ namespace _impl { && !std::is_same_v<BaseT, std::decay_t<fn_arg_t<FnT>>>; } -// A single type-specific void visitor +// A trivial type-specific "switch" for a void function template <class BaseT, typename FnT> -inline auto visit(const BaseT& event, FnT&& visitor) +inline auto switchOnType(const BaseT& event, FnT&& fn) -> std::enable_if_t<_impl::needs_downcast<BaseT, FnT> && std::is_void_v<fn_return_t<FnT>>> { using event_type = fn_arg_t<FnT>; if (is<std::decay_t<event_type>>(event)) - visitor(static_cast<event_type>(event)); + fn(static_cast<event_type>(event)); } -// A single type-specific non-void visitor with an optional default value -// non-voidness is guarded by defaultValue type +// A trivial type-specific "switch" for non-void functions with an optional +// default value; non-voidness is guarded by defaultValue type template <class BaseT, typename FnT> -inline auto visit(const BaseT& event, FnT&& visitor, - fn_return_t<FnT>&& defaultValue = {}) +inline auto switchOnType(const BaseT& event, FnT&& fn, + fn_return_t<FnT>&& defaultValue = {}) -> std::enable_if_t<_impl::needs_downcast<BaseT, FnT>, fn_return_t<FnT>> { using event_type = fn_arg_t<FnT>; if (is<std::decay_t<event_type>>(event)) - return visitor(static_cast<event_type>(event)); - return std::forward<fn_return_t<FnT>>(defaultValue); + return fn(static_cast<event_type>(event)); + return std::move(defaultValue); } -// A chain of 2 or more visitors +// A switch for a chain of 2 or more functions template <class BaseT, typename FnT1, typename FnT2, typename... FnTs> -inline std::common_type_t<fn_return_t<FnT1>, fn_return_t<FnT2>> visit( - const BaseT& event, FnT1&& visitor1, FnT2&& visitor2, - FnTs&&... visitors) +inline std::common_type_t<fn_return_t<FnT1>, fn_return_t<FnT2>> +switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns) { using event_type1 = fn_arg_t<FnT1>; if (is<std::decay_t<event_type1>>(event)) - return visitor1(static_cast<event_type1&>(event)); - return visit(event, std::forward<FnT2>(visitor2), - std::forward<FnTs>(visitors)...); + return fn1(static_cast<event_type1&>(event)); + return switchOnType(event, std::forward<FnT2>(fn2), + std::forward<FnTs>(fns)...); +} + +template <class BaseT, typename... FnTs> +[[deprecated("The new name for visit() is switchOnType()")]] // +inline std::common_type_t<fn_return_t<FnTs>...> +visit(const BaseT& event, FnTs&&... fns) +{ + return switchOnType(event, std::forward<FnTs>(fns)...); } -// A facility overload that calls void-returning visit() on each event + // A facility overload that calls void-returning switchOnType() on each event // over a range of event pointers +// TODO: replace with ranges::for_each once all standard libraries have it template <typename RangeT, typename... FnTs> -inline auto visitEach(RangeT&& events, FnTs&&... visitors) +inline auto visitEach(RangeT&& events, FnTs&&... fns) -> std::enable_if_t<std::is_void_v< - decltype(visit(**begin(events), std::forward<FnTs>(visitors)...))>> + decltype(switchOnType(**begin(events), std::forward<FnTs>(fns)...))>> { for (auto&& evtPtr: events) - visit(*evtPtr, std::forward<FnTs>(visitors)...); + switchOnType(*evtPtr, std::forward<FnTs>(fns)...); } } // namespace Quotient Q_DECLARE_METATYPE(Quotient::Event*) diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index b249b160..22878d4c 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -5,10 +5,13 @@ #include "converters.h" #include "util.h" +#include "logging.h" #include <QtCore/QMimeDatabase> +#include <QtCore/QFileInfo> using namespace Quotient::EventContent; +using std::move; QJsonObject Base::toJson() const { @@ -17,23 +20,44 @@ QJsonObject Base::toJson() const return o; } -FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType, - const QString& originalFilename) +FileInfo::FileInfo(const QFileInfo &fi) + : mimeType(QMimeDatabase().mimeTypeForFile(fi)) + , url(QUrl::fromLocalFile(fi.filePath())) + , payloadSize(fi.size()) + , originalName(fi.fileName()) +{ + Q_ASSERT(fi.isFile()); +} + +FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType, + Omittable<EncryptedFile> file, QString originalFilename) : mimeType(mimeType) - , url(u) + , url(move(u)) , payloadSize(payloadSize) - , originalName(originalFilename) -{} + , originalName(move(originalFilename)) + , file(file) +{ + if (!isValid()) + qCWarning(MESSAGES) + << "To client developers: using FileInfo(QUrl, qint64, ...) " + "constructor for non-mxc resources is deprecated since Quotient " + "0.7; for local resources, use FileInfo(QFileInfo) instead"; +} -FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) +FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable<EncryptedFile> &file, + QString originalFilename) : originalInfoJson(infoJson) , mimeType( QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString())) - , url(u) + , url(move(mxcUrl)) , payloadSize(fromJson<qint64>(infoJson["size"_ls])) - , originalName(originalFilename) + , originalName(move(originalFilename)) + , file(file) { + if(url.isEmpty() && file.has_value()) { + url = file->url; + } if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } @@ -53,14 +77,20 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); } -ImageInfo::ImageInfo(const QUrl& u, qint64 fileSize, QMimeType mimeType, - const QSize& imageSize, const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize) +ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) + : FileInfo(fi), imageSize(imageSize) +{} + +ImageInfo::ImageInfo(const QUrl& mxcUrl, qint64 fileSize, const QMimeType& type, + QSize imageSize, const Omittable<EncryptedFile> &file, const QString& originalFilename) + : FileInfo(mxcUrl, fileSize, type, file, originalFilename) + , imageSize(imageSize) {} -ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, +ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable<EncryptedFile> &file, const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename) + : FileInfo(mxcUrl, infoJson, file, originalFilename) , imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt()) {} @@ -73,9 +103,10 @@ void ImageInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("h"), imageSize.height()); } -Thumbnail::Thumbnail(const QJsonObject& infoJson) +Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file) : ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()), - infoJson["thumbnail_info"_ls].toObject()) + infoJson["thumbnail_info"_ls].toObject(), + file) {} void Thumbnail::fillInfoJson(QJsonObject* infoJson) const diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h index 60d1f7b7..f609a603 100644 --- a/lib/events/eventcontent.h +++ b/lib/events/eventcontent.h @@ -12,6 +12,10 @@ #include <QtCore/QUrl> #include <QtCore/QMetaType> +#include "encryptedfile.h" + +class QFileInfo; + namespace Quotient { namespace EventContent { /** @@ -47,13 +51,14 @@ namespace EventContent { // but specific aggregation structure is altered. See doc comments to // each type for the list of available attributes. - // A quick classes inheritance structure follows: + // A quick classes inheritance structure follows (the definitions are + // spread across eventcontent.h and roommessageevent.h): // FileInfo - // FileContent : UrlBasedContent<FileInfo, Thumbnail> - // AudioContent : UrlBasedContent<FileInfo, Duration> + // FileContent : UrlWithThumbnailContent<FileInfo> + // AudioContent : PlayableContent<UrlBasedContent<FileInfo>> // ImageInfo : FileInfo + imageSize attribute - // ImageContent : UrlBasedContent<ImageInfo, Thumbnail> - // VideoContent : UrlBasedContent<ImageInfo, Thumbnail, Duration> + // ImageContent : UrlWithThumbnailContent<ImageInfo> + // VideoContent : PlayableContent<UrlWithThumbnailContent<ImageInfo>> /** * A base/mixin class for structures representing an "info" object for @@ -73,11 +78,15 @@ namespace EventContent { */ class FileInfo { public: - explicit FileInfo(const QUrl& u, qint64 payloadSize = -1, + FileInfo() = default; + explicit FileInfo(const QFileInfo& fi); + explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1, const QMimeType& mimeType = {}, - const QString& originalFilename = {}); - FileInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + Omittable<EncryptedFile> file = none, + QString originalFilename = {}); + FileInfo(QUrl mxcUrl, const QJsonObject& infoJson, + const Omittable<EncryptedFile> &file, + QString originalFilename = {}); bool isValid() const; @@ -98,6 +107,7 @@ namespace EventContent { QUrl url; qint64 payloadSize; QString originalName; + Omittable<EncryptedFile> file = none; }; template <typename InfoT> @@ -113,10 +123,14 @@ namespace EventContent { */ class ImageInfo : public FileInfo { public: - explicit ImageInfo(const QUrl& u, qint64 fileSize = -1, - QMimeType mimeType = {}, const QSize& imageSize = {}, + ImageInfo() = default; + explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {}); + explicit ImageInfo(const QUrl& mxcUrl, qint64 fileSize = -1, + const QMimeType& type = {}, QSize imageSize = {}, + const Omittable<EncryptedFile> &file = none, const QString& originalFilename = {}); - ImageInfo(const QUrl& u, const QJsonObject& infoJson, + ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson, + const Omittable<EncryptedFile> &encryptedFile, const QString& originalFilename = {}); void fillInfoJson(QJsonObject* infoJson) const; @@ -134,8 +148,8 @@ namespace EventContent { */ class Thumbnail : public ImageInfo { public: - Thumbnail() : ImageInfo(QUrl()) {} // To allow empty thumbnails - Thumbnail(const QJsonObject& infoJson); + Thumbnail() = default; // Allow empty thumbnails + Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file = none); Thumbnail(const ImageInfo& info) : ImageInfo(info) {} using ImageInfo::ImageInfo; @@ -148,13 +162,13 @@ namespace EventContent { class TypedBase : public Base { public: - explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} virtual QMimeType type() const = 0; virtual const FileInfo* fileInfo() const { return nullptr; } virtual FileInfo* fileInfo() { return nullptr; } virtual const Thumbnail* thumbnailInfo() const { return nullptr; } protected: + explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {} using Base::Base; }; @@ -175,7 +189,7 @@ namespace EventContent { explicit UrlBasedContent(const QJsonObject& json) : TypedBase(json) , InfoT(QUrl(json["url"].toString()), json["info"].toObject(), - json["filename"].toString()) + fromJson<Omittable<EncryptedFile>>(json["file"]), json["filename"].toString()) { // A small hack to facilitate links creation in QML. originalJson.insert("mediaId", InfoT::mediaId()); @@ -189,7 +203,11 @@ namespace EventContent { void fillJson(QJsonObject* json) const override { Q_ASSERT(json); - json->insert("url", InfoT::url.toString()); + if (!InfoT::file.has_value()) { + json->insert("url", InfoT::url.toString()); + } else { + json->insert("file", Quotient::toJson(*InfoT::file)); + } if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); json->insert("info", toInfoJson<InfoT>(*this)); diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h index 978668f2..fe624d70 100644 --- a/lib/events/eventloader.h +++ b/lib/events/eventloader.h @@ -6,16 +6,6 @@ #include "stateevent.h" namespace Quotient { -namespace _impl { - template <typename BaseEventT> - static inline auto loadEvent(const QJsonObject& json, - const QString& matrixType) - { - if (auto e = EventFactory<BaseEventT>::make(json, matrixType)) - return e; - return makeEvent<BaseEventT>(unknownEventTypeId(), json); - } -} // namespace _impl /*! Create an event with proper type from a JSON object * @@ -26,7 +16,7 @@ namespace _impl { template <typename BaseEventT> inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson) { - return _impl::loadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString()); + return doLoadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString()); } /*! Create an event from a type string and content JSON @@ -39,8 +29,8 @@ template <typename BaseEventT> inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType, const QJsonObject& content) { - return _impl::loadEvent<BaseEventT>(basicEventJson(matrixType, content), - matrixType); + return doLoadEvent<BaseEventT>(basicEventJson(matrixType, content), + matrixType); } /*! Create a state event from a type string, content JSON and state key @@ -53,7 +43,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType, const QJsonObject& content, const QString& stateKey = {}) { - return _impl::loadEvent<StateEventBase>( + return doLoadEvent<StateEventBase>( basicStateEventJson(matrixType, content, stateKey), matrixType); } diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h index 777905f2..5a2b98c4 100644 --- a/lib/events/reactionevent.h +++ b/lib/events/reactionevent.h @@ -47,7 +47,7 @@ public: explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {} EventRelation relation() const { - return content<EventRelation>(QStringLiteral("m.relates_to")); + return contentPart<EventRelation>("m.relates_to"_ls); } }; REGISTER_EVENT_TYPE(ReactionEvent) diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp index 4185d92d..72dbf2e3 100644 --- a/lib/events/receiptevent.cpp +++ b/lib/events/receiptevent.cpp @@ -25,6 +25,27 @@ Example of a Receipt Event: using namespace Quotient; +// The library loads the event-ids-to-receipts JSON map into a vector because +// map lookups are not used and vectors are massively faster. Same goes for +// de-/serialization of ReceiptsForEvent::receipts. +// (XXX: would this be generally preferred across CS API JSON maps?..) +QJsonObject toJson(const EventsWithReceipts& ewrs) +{ + QJsonObject json; + for (const auto& e : ewrs) { + QJsonObject receiptsJson; + for (const auto& r : e.receipts) + receiptsJson.insert(r.userId, + QJsonObject { { "ts"_ls, toJson(r.timestamp) } }); + json.insert(e.evtId, QJsonObject { { "m.read"_ls, receiptsJson } }); + } + return json; +} + +ReceiptEvent::ReceiptEvent(const EventsWithReceipts &ewrs) + : Event(typeId(), matrixTypeId(), toJson(ewrs)) +{} + EventsWithReceipts ReceiptEvent::eventsWithReceipts() const { EventsWithReceipts result; @@ -39,14 +60,14 @@ EventsWithReceipts ReceiptEvent::eventsWithReceipts() const } const auto reads = eventIt.value().toObject().value("m.read"_ls).toObject(); - QVector<Receipt> receipts; - receipts.reserve(reads.size()); + QVector<UserTimestamp> usersAtEvent; + usersAtEvent.reserve(reads.size()); for (auto userIt = reads.begin(); userIt != reads.end(); ++userIt) { const auto user = userIt.value().toObject(); - receipts.push_back( + usersAtEvent.push_back( { userIt.key(), fromJson<QDateTime>(user["ts"_ls]) }); } - result.push_back({ eventIt.key(), std::move(receipts) }); + result.push_back({ eventIt.key(), std::move(usersAtEvent) }); } return result; } diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h index 4feec9ea..9683deef 100644 --- a/lib/events/receiptevent.h +++ b/lib/events/receiptevent.h @@ -9,19 +9,20 @@ #include <QtCore/QVector> namespace Quotient { -struct Receipt { +struct UserTimestamp { QString userId; QDateTime timestamp; }; struct ReceiptsForEvent { QString evtId; - QVector<Receipt> receipts; + QVector<UserTimestamp> receipts; }; using EventsWithReceipts = QVector<ReceiptsForEvent>; class ReceiptEvent : public Event { public: DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent) + explicit ReceiptEvent(const EventsWithReceipts& ewrs); explicit ReceiptEvent(const QJsonObject& obj) : Event(typeId(), obj) {} EventsWithReceipts eventsWithReceipts() const; diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h index ed560331..be20bf52 100644 --- a/lib/events/redactionevent.h +++ b/lib/events/redactionevent.h @@ -17,7 +17,7 @@ public: { return fullJson()["redacts"_ls].toString(); } - QString reason() const { return contentJson()["reason"_ls].toString(); } + QString reason() const { return contentPart<QString>("reason"_ls); } }; REGISTER_EVENT_TYPE(RedactionEvent) } // namespace Quotient diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h index a4257895..8618ba31 100644 --- a/lib/events/roomavatarevent.h +++ b/lib/events/roomavatarevent.h @@ -20,12 +20,12 @@ public: : StateEvent(typeId(), matrixTypeId(), QString(), avatar) {} // A replica of EventContent::ImageInfo constructor - explicit RoomAvatarEvent(const QUrl& u, qint64 fileSize = -1, + explicit RoomAvatarEvent(const QUrl& mxcUrl, qint64 fileSize = -1, QMimeType mimeType = {}, const QSize& imageSize = {}, const QString& originalFilename = {}) : RoomAvatarEvent(EventContent::ImageContent { - u, fileSize, mimeType, imageSize, originalFilename }) + mxcUrl, fileSize, mimeType, imageSize, none, originalFilename }) {} QUrl url() const { return content().url; } diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp index 6558bade..bb6de648 100644 --- a/lib/events/roomcreateevent.cpp +++ b/lib/events/roomcreateevent.cpp @@ -5,19 +5,35 @@ using namespace Quotient; +template <> +struct Quotient::JsonConverter<RoomType> { + static RoomType load(const QJsonValue& jv) + { + const auto& roomTypeString = jv.toString(); + for (auto it = RoomTypeStrings.begin(); it != RoomTypeStrings.end(); + ++it) + if (roomTypeString == *it) + return RoomType(it - RoomTypeStrings.begin()); + + if (!roomTypeString.isEmpty()) + qCWarning(EVENTS) << "Unknown Room Type: " << roomTypeString; + return RoomType::Undefined; + } +}; + bool RoomCreateEvent::isFederated() const { - return fromJson<bool>(contentJson()["m.federate"_ls]); + return contentPart<bool>("m.federate"_ls); } QString RoomCreateEvent::version() const { - return fromJson<QString>(contentJson()["room_version"_ls]); + return contentPart<QString>("room_version"_ls); } RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const { - const auto predJson = contentJson()["predecessor"_ls].toObject(); + const auto predJson = contentPart<QJsonObject>("predecessor"_ls); return { fromJson<QString>(predJson[RoomIdKeyL]), fromJson<QString>(predJson[EventIdKeyL]) }; } @@ -26,3 +42,8 @@ bool RoomCreateEvent::isUpgrade() const { return contentJson().contains("predecessor"_ls); } + +RoomType RoomCreateEvent::roomType() const +{ + return contentPart<RoomType>("type"_ls); +} diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h index 05e623ed..b3ad287c 100644 --- a/lib/events/roomcreateevent.h +++ b/lib/events/roomcreateevent.h @@ -4,6 +4,7 @@ #pragma once #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class RoomCreateEvent : public StateEventBase { @@ -24,6 +25,7 @@ public: QString version() const; Predecessor predecessor() const; bool isUpgrade() const; + RoomType roomType() const; }; REGISTER_EVENT_TYPE(RoomCreateEvent) } // namespace Quotient diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 4fec9d2b..b728e0bf 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -9,9 +9,6 @@ using namespace Quotient; -[[maybe_unused]] static auto roomEventTypeInitialised = - Event::factory_t::chainFactory<RoomEvent>(); - RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, const QJsonObject& contentJson) : Event(type, matrixType, contentJson) @@ -19,7 +16,7 @@ RoomEvent::RoomEvent(Type type, event_mtype_t matrixType, RoomEvent::RoomEvent(Type type, const QJsonObject& json) : Event(type, json) { - if (const auto redaction = unsignedJson()[RedactedCauseKeyL]; + if (const auto redaction = unsignedPart(RedactedCauseKeyL); redaction.isObject()) _redactedBecause = makeEvent<RedactionEvent>(redaction.toObject()); } @@ -45,14 +42,14 @@ QString RoomEvent::senderId() const bool RoomEvent::isReplaced() const { - return unsignedJson()["m.relations"_ls].toObject().contains("m.replace"); + return unsignedPart<QJsonObject>("m.relations"_ls).contains("m.replace"); } QString RoomEvent::replacedBy() const { // clang-format off - return unsignedJson()["m.relations"_ls].toObject() - .value("m.replace").toObject() + return unsignedPart<QJsonObject>("m.relations"_ls) + .value("m.replace"_ls).toObject() .value(EventIdKeyL).toString(); // clang-format on } @@ -64,7 +61,7 @@ QString RoomEvent::redactionReason() const QString RoomEvent::transactionId() const { - return unsignedJson()["transaction_id"_ls].toString(); + return unsignedPart<QString>("transaction_id"_ls); } QString RoomEvent::stateKey() const diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index fea509c0..8be58481 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -12,16 +12,8 @@ class RedactionEvent; /** This class corresponds to m.room.* events */ class RoomEvent : public Event { - Q_GADGET - Q_PROPERTY(QString id READ id) - Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString redactionReason READ redactionReason) - Q_PROPERTY(bool isRedacted READ isRedacted) - Q_PROPERTY(QString transactionId READ transactionId WRITE setTransactionId) public: - using factory_t = EventFactory<RoomEvent>; + static inline _impl::EventFactory<RoomEvent> factory { "RoomEvent" }; // RedactionEvent is an incomplete type here so we cannot inline // constructors and destructors and we cannot use 'using'. @@ -32,11 +24,12 @@ public: QString id() const; QDateTime originTimestamp() const; - [[deprecated("Use originTimestamp()")]] QDateTime timestamp() const { - return originTimestamp(); - } QString roomId() const; QString senderId() const; + //! \brief Determine whether the event has been replaced + //! + //! \return true if this event has been overridden by another event + //! with `"rel_type": "m.replace"`; false otherwise bool isReplaced() const; QString replacedBy() const; bool isRedacted() const { return bool(_redactedBecause); } @@ -48,28 +41,23 @@ public: QString transactionId() const; QString stateKey() const; + //! \brief Fill the pending event object with the room id void setRoomId(const QString& roomId); + //! \brief Fill the pending event object with the sender id void setSender(const QString& senderId); - - /** - * Sets the transaction id for locally created events. This should be - * done before the event is exposed to any code using the respective - * Q_PROPERTY. - * - * \param txnId - transaction id, normally obtained from - * Connection::generateTxnId() - */ + //! \brief Fill the pending event object with the transaction id + //! \param txnId - transaction id, normally obtained from + //! Connection::generateTxnId() void setTransactionId(const QString& txnId); - /** - * Sets event id for locally created events - * - * When a new event is created locally, it has no server id yet. - * This function allows to add the id once the confirmation from - * the server is received. There should be no id set previously - * in the event. It's the responsibility of the code calling addId() - * to notify clients that use Q_PROPERTY(id) about its change - */ + //! \brief Add an event id to locally created events after they are sent + //! + //! When a new event is created locally, it has no id; the homeserver + //! assigns it once the event is sent. This function allows to add the id + //! once the confirmation from the server is received. There should be no id + //! set previously in the event. It's the responsibility of the code calling + //! addId() to notify clients about the change; there's no signal or + //! callback for that in RoomEvent. void addId(const QString& newId); protected: @@ -90,8 +78,8 @@ public: ~CallEventBase() override = default; bool isCallEvent() const override { return true; } - QString callId() const { return content<QString>("call_id"_ls); } - int version() const { return content<int>("version"_ls); } + QString callId() const { return contentPart<QString>("call_id"_ls); } + int version() const { return contentPart<int>("version"_ls); } }; } // namespace Quotient Q_DECLARE_METATYPE(Quotient::RoomEvent*) diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h index 14e80324..d021fbec 100644 --- a/lib/events/roomkeyevent.h +++ b/lib/events/roomkeyevent.h @@ -13,10 +13,10 @@ public: explicit RoomKeyEvent(const QJsonObject& obj); - QString algorithm() const { return content<QString>("algorithm"_ls); } - QString roomId() const { return content<QString>(RoomIdKeyL); } - QString sessionId() const { return content<QString>("session_id"_ls); } - QString sessionKey() const { return content<QString>("session_key"_ls); } + QString algorithm() const { return contentPart<QString>("algorithm"_ls); } + QString roomId() const { return contentPart<QString>(RoomIdKeyL); } + QString sessionId() const { return contentPart<QString>("session_id"_ls); } + QString sessionKey() const { return contentPart<QString>("session_key"_ls); } }; REGISTER_EVENT_TYPE(RoomKeyEvent) } // namespace Quotient diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp index 9634ca3a..3141f6b5 100644 --- a/lib/events/roommemberevent.cpp +++ b/lib/events/roommemberevent.cpp @@ -7,27 +7,26 @@ #include "converters.h" #include "logging.h" -#include <array> - -static const std::array<QString, 5> membershipStrings = { - { QStringLiteral("invite"), QStringLiteral("join"), QStringLiteral("knock"), - QStringLiteral("leave"), QStringLiteral("ban") } -}; +#include <QtCore/QtAlgorithms> namespace Quotient { template <> -struct JsonConverter<MembershipType> { - static MembershipType load(const QJsonValue& jv) +struct JsonConverter<Membership> { + static Membership load(const QJsonValue& jv) { - const auto& membershipString = jv.toString(); - for (auto it = membershipStrings.begin(); it != membershipStrings.end(); - ++it) - if (membershipString == *it) - return MembershipType(it - membershipStrings.begin()); - - if (!membershipString.isEmpty()) - qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; - return MembershipType::Undefined; + const auto& ms = jv.toString(); + if (ms.isEmpty()) + { + qCWarning(EVENTS) << "Empty membership state"; + return Membership::Invalid; + } + const auto it = + std::find(MembershipStrings.begin(), MembershipStrings.end(), ms); + if (it != MembershipStrings.end()) + return Membership(1U << (it - MembershipStrings.begin())); + + qCWarning(EVENTS) << "Unknown Membership value: " << ms; + return Membership::Invalid; } }; } // namespace Quotient @@ -35,7 +34,7 @@ struct JsonConverter<MembershipType> { using namespace Quotient; MemberEventContent::MemberEventContent(const QJsonObject& json) - : membership(fromJson<MembershipType>(json["membership"_ls])) + : membership(fromJson<Membership>(json["membership"_ls])) , isDirect(json["is_direct"_ls].toBool()) , displayName(fromJson<Omittable<QString>>(json["displayname"_ls])) , avatarUrl(fromJson<Omittable<QString>>(json["avatar_url"_ls])) @@ -48,10 +47,10 @@ MemberEventContent::MemberEventContent(const QJsonObject& json) void MemberEventContent::fillJson(QJsonObject* o) const { Q_ASSERT(o); - Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__, - "The key 'membership' must be explicit in MemberEventContent"); - if (membership != MembershipType::Undefined) - o->insert(QStringLiteral("membership"), membershipStrings[membership]); + if (membership != Membership::Invalid) + o->insert(QStringLiteral("membership"), + MembershipStrings[qCountTrailingZeroBits( + std::underlying_type_t<Membership>(membership))]); if (displayName) o->insert(QStringLiteral("displayname"), *displayName); if (avatarUrl && avatarUrl->isValid()) @@ -67,51 +66,49 @@ bool RoomMemberEvent::changesMembership() const bool RoomMemberEvent::isInvite() const { - return membership() == MembershipType::Invite && changesMembership(); + return membership() == Membership::Invite && changesMembership(); } bool RoomMemberEvent::isRejectedInvite() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Invite; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Invite; } bool RoomMemberEvent::isJoin() const { - return membership() == MembershipType::Join && changesMembership(); + return membership() == Membership::Join && changesMembership(); } bool RoomMemberEvent::isLeave() const { - return membership() == MembershipType::Leave && prevContent() + return membership() == Membership::Leave && prevContent() && prevContent()->membership != membership() - && prevContent()->membership != MembershipType::Ban - && prevContent()->membership != MembershipType::Invite; + && prevContent()->membership != Membership::Ban + && prevContent()->membership != Membership::Invite; } bool RoomMemberEvent::isBan() const { - return membership() == MembershipType::Ban && changesMembership(); + return membership() == Membership::Ban && changesMembership(); } bool RoomMemberEvent::isUnban() const { - return membership() == MembershipType::Leave && prevContent() - && prevContent()->membership == MembershipType::Ban; + return membership() == Membership::Leave && prevContent() + && prevContent()->membership == Membership::Ban; } bool RoomMemberEvent::isRename() const { - auto prevName = prevContent() && prevContent()->displayName - ? *prevContent()->displayName - : QString(); - return newDisplayName() != prevName; + return prevContent() && prevContent()->displayName + ? newDisplayName() != *prevContent()->displayName + : newDisplayName().has_value(); } bool RoomMemberEvent::isAvatarUpdate() const { - auto prevAvatarUrl = prevContent() && prevContent()->avatarUrl - ? *prevContent()->avatarUrl - : QUrl(); - return newAvatarUrl() != prevAvatarUrl; + return prevContent() && prevContent()->avatarUrl + ? newAvatarUrl() != *prevContent()->avatarUrl + : newAvatarUrl().has_value(); } diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h index f2fbe689..0fb464d4 100644 --- a/lib/events/roommemberevent.h +++ b/lib/events/roommemberevent.h @@ -7,23 +7,21 @@ #include "eventcontent.h" #include "stateevent.h" +#include "quotient_common.h" namespace Quotient { class MemberEventContent : public EventContent::Base { public: - enum MembershipType : unsigned char { - Invite = 0, - Join, - Knock, - Leave, - Ban, - Undefined - }; + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; - explicit MemberEventContent(MembershipType mt = Join) : membership(mt) {} + explicit MemberEventContent(Membership ms = Membership::Join) + : membership(ms) + {} explicit MemberEventContent(const QJsonObject& json); - MembershipType membership; + Membership membership; + /// (Only for invites) Whether the invite is to a direct chat bool isDirect = false; Omittable<QString> displayName; Omittable<QUrl> avatarUrl; @@ -33,15 +31,15 @@ protected: void fillJson(QJsonObject* o) const override; }; -using MembershipType = MemberEventContent::MembershipType; +using MembershipType [[deprecated("Use Membership instead")]] = Membership; class RoomMemberEvent : public StateEvent<MemberEventContent> { Q_GADGET public: DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent) - using MembershipType = MemberEventContent::MembershipType; - Q_ENUM(MembershipType) + using MembershipType + [[deprecated("Use Quotient::Membership instead")]] = Membership; explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj) {} @@ -51,21 +49,20 @@ public: std::forward<ArgTs>(contentArgs)...) {} - /// A special constructor to create unknown RoomMemberEvents - /** - * This is needed in order to use RoomMemberEvent as a "base event - * class" in cases like GetMembersByRoomJob when RoomMemberEvents - * (rather than RoomEvents or StateEvents) are resolved from JSON. - * For such cases loadEvent<> requires an underlying class to be - * constructible with unknownTypeId() instead of its genuine id. - * Don't use it directly. - * \sa GetMembersByRoomJob, loadEvent, unknownTypeId - */ + //! \brief A special constructor to create unknown RoomMemberEvents + //! + //! This is needed in order to use RoomMemberEvent as a "base event class" + //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than + //! RoomEvents or StateEvents) are resolved from JSON. For such cases + //! loadEvent\<> requires an underlying class to have a specialisation of + //! EventFactory\<> and be constructible with unknownTypeId() instead of + //! its genuine id. Don't use directly. + //! \sa EventFactory, loadEvent, GetMembersByRoomJob RoomMemberEvent(Type type, const QJsonObject& fullJson) : StateEvent(type, fullJson) {} - MembershipType membership() const { return content().membership; } + Membership membership() const { return content().membership; } QString userId() const { return stateKey(); } bool isDirect() const { return content().isDirect; } Omittable<QString> newDisplayName() const { return content().displayName; } @@ -91,14 +88,13 @@ public: }; template <> -class EventFactory<RoomMemberEvent> { -public: - static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json, - const QString&) - { +inline event_ptr_tt<RoomMemberEvent> +doLoadEvent<RoomMemberEvent>(const QJsonObject& json, const QString& matrixType) +{ + if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId())) return makeEvent<RoomMemberEvent>(json); - } -}; + return makeEvent<RoomMemberEvent>(unknownEventTypeId(), json); +} REGISTER_EVENT_TYPE(RoomMemberEvent) } // namespace Quotient diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 31c0fd9e..2b7b4166 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -10,7 +10,9 @@ #include <QtCore/QFileInfo> #include <QtCore/QMimeDatabase> #include <QtGui/QImageReader> -#include <QtMultimedia/QMediaResource> +#if QT_VERSION_MAJOR < 6 +# include <QtMultimedia/QMediaResource> +#endif using namespace Quotient; using namespace EventContent; @@ -133,6 +135,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) {} +#if QT_VERSION_MAJOR < 6 TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) { auto filePath = file.absoluteFilePath(); @@ -142,21 +145,21 @@ TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile) auto mimeTypeName = mimeType.name(); if (mimeTypeName.startsWith("image/")) return new ImageContent(localUrl, file.size(), mimeType, - QImageReader(filePath).size(), + QImageReader(filePath).size(), none, file.fileName()); // duration can only be obtained asynchronously and can only be reliably // done by starting to play the file. Left for a future implementation. if (mimeTypeName.startsWith("video/")) return new VideoContent(localUrl, file.size(), mimeType, - QMediaResource(localUrl).resolution(), + QMediaResource(localUrl).resolution(), none, file.fileName()); if (mimeTypeName.startsWith("audio/")) - return new AudioContent(localUrl, file.size(), mimeType, + return new AudioContent(localUrl, file.size(), mimeType, none, file.fileName()); } - return new FileContent(localUrl, file.size(), mimeType, file.fileName()); + return new FileContent(localUrl, file.size(), mimeType, none, file.fileName()); } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, @@ -166,6 +169,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, : rawMsgTypeForFile(file), contentFromFile(file, asGenericFile)) {} +#endif RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj), _content(nullptr) @@ -200,12 +204,12 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const QString RoomMessageEvent::rawMsgtype() const { - return contentJson()[MsgTypeKeyL].toString(); + return contentPart<QString>(MsgTypeKeyL); } QString RoomMessageEvent::plainBody() const { - return contentJson()[BodyKeyL].toString(); + return contentPart<QString>(BodyKeyL); } QMimeType RoomMessageEvent::mimeType() const diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h index 8303ce4e..56597ddc 100644 --- a/lib/events/roommessageevent.h +++ b/lib/events/roommessageevent.h @@ -18,10 +18,6 @@ namespace MessageEventContent = EventContent; // Back-compatibility */ class RoomMessageEvent : public RoomEvent { Q_GADGET - Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT) - Q_PROPERTY(QString plainBody READ plainBody CONSTANT) - Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT) - Q_PROPERTY(const EventContent::TypedBase* content READ content CONSTANT) public: DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent) @@ -42,8 +38,12 @@ public: explicit RoomMessageEvent(const QString& plainBody, MsgType msgType = MsgType::Text, EventContent::TypedBase* content = nullptr); +#if QT_VERSION_MAJOR < 6 + [[deprecated("Create an EventContent object on the client side" + " and pass it to other constructors")]] // explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, bool asGenericFile = false); +#endif explicit RoomMessageEvent(const QJsonObject& obj); MsgType msgtype() const; @@ -58,9 +58,26 @@ public: _content.data()); } QMimeType mimeType() const; + //! \brief Determine whether the message has text content + //! + //! \return true, if the message type is one of m.text, m.notice, m.emote, + //! or the message type is unspecified (in which case plainBody() + //! can still be examined); false otherwise bool hasTextContent() const; + //! \brief Determine whether the message has a file/attachment + //! + //! \return true, if the message has a data structure corresponding to + //! a file (such as m.file or m.audio); false otherwise bool hasFileContent() const; + //! \brief Determine whether the message has a thumbnail + //! + //! \return true, if the message has a data structure corresponding to + //! a thumbnail (the message type may be one for visual content, + //! such as m.image, or generic binary content, i.e. m.file); + //! false otherwise bool hasThumbnail() const; + //! \brief Obtain id of an event replaced by the current one + //! \sa RoomEvent::isReplaced, RoomEvent::replacedBy QString replacedEvent() const; static QString rawMsgTypeForUrl(const QUrl& url); diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp index 080d269c..2c3492d6 100644 --- a/lib/events/roomtombstoneevent.cpp +++ b/lib/events/roomtombstoneevent.cpp @@ -7,10 +7,10 @@ using namespace Quotient; QString RoomTombstoneEvent::serverMessage() const { - return fromJson<QString>(contentJson()["body"_ls]); + return contentPart<QString>("body"_ls); } QString RoomTombstoneEvent::successorRoomId() const { - return fromJson<QString>(contentJson()["replacement_room"_ls]); + return contentPart<QString>("replacement_room"_ls); } diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h index c977cb6e..13597979 100644 --- a/lib/events/simplestateevents.h +++ b/lib/events/simplestateevents.h @@ -8,8 +8,7 @@ namespace Quotient { namespace EventContent { template <typename T> - class SimpleContent { - public: + struct SimpleContent { using value_type = T; // The constructor is templated to enable perfect forwarding @@ -25,11 +24,8 @@ namespace EventContent { return { { key, Quotient::toJson(value) } }; } - public: T value; - - protected: - QString key; + const QString key; }; } // namespace EventContent @@ -57,19 +53,16 @@ DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name) DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic) DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents) -class RoomAliasesEvent - : public StateEvent<EventContent::SimpleContent<QStringList>> { +class [[deprecated( + "m.room.aliases events are deprecated by the Matrix spec; use" + " RoomCanonicalAliasEvent::altAliases() to get non-authoritative aliases")]] // +RoomAliasesEvent : public StateEvent<EventContent::SimpleContent<QStringList>> { public: DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent) explicit RoomAliasesEvent(const QJsonObject& obj) : StateEvent(typeId(), obj, QStringLiteral("aliases")) {} - RoomAliasesEvent(const QString& server, const QStringList& aliases) - : StateEvent(typeId(), matrixTypeId(), server, - QStringLiteral("aliases"), aliases) - {} QString server() const { return stateKey(); } QStringList aliases() const { return content().value; } }; -REGISTER_EVENT_TYPE(RoomAliasesEvent) } // namespace Quotient diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp index 42fc9054..e53d47d4 100644 --- a/lib/events/stateevent.cpp +++ b/lib/events/stateevent.cpp @@ -5,20 +5,13 @@ using namespace Quotient; -// Aside from the normal factory to instantiate StateEventBase inheritors -// StateEventBase itself can be instantiated if there's a state_key JSON key -// but the event type is unknown. -[[maybe_unused]] static auto stateEventTypeInitialised = - RoomEvent::factory_t::addMethod( - [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr { - if (!json.contains(StateKeyKeyL)) - return nullptr; - - if (auto e = StateEventBase::factory_t::make(json, matrixType)) - return e; - - return makeEvent<StateEventBase>(unknownEventTypeId(), json); - }); +StateEventBase::StateEventBase(Type type, const QJsonObject& json) + : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json) +{ + if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL)) + qWarning(EVENTS) << "Attempt to create a state event with no stateKey -" + "forcing the event type to unknown to avoid damage"; +} StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, const QString& stateKey, @@ -28,22 +21,22 @@ StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType, bool StateEventBase::repeatsState() const { - const auto prevContentJson = unsignedJson().value(PrevContentKeyL); + const auto prevContentJson = unsignedPart(PrevContentKeyL); return fullJson().value(ContentKeyL) == prevContentJson; } QString StateEventBase::replacedState() const { - return unsignedJson().value("replaces_state"_ls).toString(); + return unsignedPart<QString>("replaces_state"_ls); } void StateEventBase::dumpTo(QDebug dbg) const { if (!stateKey().isEmpty()) dbg << '<' << stateKey() << "> "; - if (unsignedJson().contains(PrevContentKeyL)) - dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject()) - .toJson(QJsonDocument::Compact) + if (const auto prevContentJson = unsignedPart<QJsonObject>(PrevContentKeyL); + !prevContentJson.isEmpty()) + dbg << QJsonDocument(prevContentJson).toJson(QJsonDocument::Compact) << " -> "; RoomEvent::dumpTo(dbg); } diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h index 1415f709..c37965aa 100644 --- a/lib/events/stateevent.h +++ b/lib/events/stateevent.h @@ -18,13 +18,10 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId, } class StateEventBase : public RoomEvent { - Q_GADGET - Q_PROPERTY(QString stateKey READ stateKey CONSTANT) public: - using factory_t = EventFactory<StateEventBase>; + static inline _impl::EventFactory<StateEventBase> factory { "StateEvent" }; - StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json) - {} + StateEventBase(Type type, const QJsonObject& json); StateEventBase(Type type, event_mtype_t matrixType, const QString& stateKey = {}, const QJsonObject& contentJson = {}); @@ -39,6 +36,22 @@ public: using StateEventPtr = event_ptr_tt<StateEventBase>; using StateEvents = EventsArray<StateEventBase>; +//! \brief Override RoomEvent factory with that from StateEventBase if JSON has +//! stateKey +//! +//! This means in particular that an event with a type known to RoomEvent but +//! having stateKey set (even to an empty value) will be treated as a state +//! event and most likely end up as unknown (consider, e.g., m.room.message +//! that has stateKey set). +template <> +inline RoomEventPtr doLoadEvent(const QJsonObject& json, + const QString& matrixType) +{ + if (json.contains(StateKeyKeyL)) + return StateEventBase::factory.loadEvent(json, matrixType); + return RoomEvent::factory.loadEvent(json, matrixType); +} + template <> inline bool is<StateEventBase>(const Event& e) { @@ -100,10 +113,6 @@ public: visitor(_content); editJson()[ContentKeyL] = _content.toJson(); } - [[deprecated("Use prevContent instead")]] const ContentT* prev_content() const - { - return prevContent(); - } const ContentT* prevContent() const { return _prev ? &_prev->content : nullptr; diff --git a/lib/events/stickerevent.cpp b/lib/events/stickerevent.cpp index ea4dff3f..628fd154 100644 --- a/lib/events/stickerevent.cpp +++ b/lib/events/stickerevent.cpp @@ -12,7 +12,7 @@ StickerEvent::StickerEvent(const QJsonObject &obj) QString StickerEvent::body() const { - return content<QString>("body"_ls); + return contentPart<QString>("body"_ls); } const EventContent::ImageContent &StickerEvent::image() const diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp index e97e978f..7e5d7ee6 100644 --- a/lib/events/typingevent.cpp +++ b/lib/events/typingevent.cpp @@ -7,5 +7,5 @@ using namespace Quotient; QStringList TypingEvent::users() const { - return fromJson<QStringList>(contentJson()["user_ids"_ls]); + return contentPart<QStringList>("user_ids"_ls); } |