From 13509c820e241afdd1dadf747858312068e0423c Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 12 Oct 2024 12:29:44 +0100 Subject: [PATCH] Don't save the content or relates to structs in the RoomMessageEvent and just generate as required. This also cleans up the code moving all content objefcts to EventContent and additional helpers to grab text, file or loaction content if you know it's what you need are added. --- Quotient/events/eventcontent.h | 103 +++++++++++++++++- Quotient/events/roommessageevent.cpp | 76 +++++++++++-- Quotient/events/roommessageevent.h | 157 ++++++--------------------- Quotient/room.cpp | 2 +- gtad/gtad | 2 +- 5 files changed, 205 insertions(+), 135 deletions(-) diff --git a/Quotient/events/eventcontent.h b/Quotient/events/eventcontent.h index 98d85ebc8..1bdae98a7 100644 --- a/Quotient/events/eventcontent.h +++ b/Quotient/events/eventcontent.h @@ -8,8 +8,6 @@ #include "filesourceinfo.h" -#include "../util.h" - #include #include #include @@ -163,6 +161,50 @@ class QUOTIENT_API TypedBase : public Base { using Base::Base; }; +//! \brief Rich text content for m.text, m.emote, m.notice +//! +//! Available fields: mimeType, body. The body can be either rich text +//! or plain text, depending on what mimeType specifies. +class QUOTIENT_API TextContent : public TypedBase { +public: + TextContent(QString text, const QString& contentType); + explicit TextContent(const QJsonObject& json); + + QMimeType type() const override { return mimeType; } + + QMimeType mimeType; + QString body; + +protected: + void fillJson(QJsonObject& json) const override; +}; + +//! \brief Content class for m.location +//! +//! Available fields: +//! - corresponding to the top-level JSON: +//! - geoUri ("geo_uri" in JSON) +//! - corresponding to the "info" subobject: +//! - thumbnail.url ("thumbnail_url" in JSON) +//! - corresponding to the "info/thumbnail_info" subobject: +//! - thumbnail.payloadSize +//! - thumbnail.mimeType +//! - thumbnail.imageSize +class QUOTIENT_API LocationContent : public TypedBase { +public: + LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); + explicit LocationContent(const QJsonObject& json); + + QMimeType type() const override; + +public: + QString geoUri; + Thumbnail thumbnail; + +protected: + void fillJson(QJsonObject& o) const override; +}; + //! \brief A template class for content types with a URL and additional info //! //! Types that derive from this class template take `url` (or, if the file @@ -246,5 +288,62 @@ using ImageContent = UrlBasedContent; //! - thumbnail.mimeType //! - thumbnail.imageSize (QSize for `h` and `w` in JSON) using FileContent = UrlBasedContent; + +//! A base class for info types that include duration: audio and video +template +class PlayableContent : public UrlBasedContent { +public: +using UrlBasedContent::UrlBasedContent; +PlayableContent(const QJsonObject& json) + : UrlBasedContent(json) + , duration(FileInfo::originalInfoJson["duration"_L1].toInt()) +{} + +protected: +void fillInfoJson(QJsonObject& infoJson) const override +{ + infoJson.insert("duration"_L1, duration); +} + +public: +int duration; +}; + +//! \brief Content class for m.video +//! +//! Available fields: +//! - corresponding to the top-level JSON: +//! - url +//! - filename (extension to the CS API spec) +//! - corresponding to the "info" subobject: +//! - payloadSize ("size" in JSON) +//! - mimeType ("mimetype" in JSON) +//! - duration +//! - imageSize (QSize for a combination of "h" and "w" in JSON) +//! - thumbnail.url ("thumbnail_url" in JSON) +//! - corresponding to the "info/thumbnail_info" subobject: contents of +//! thumbnail field, in the same vein as for "info": +//! - payloadSize +//! - mimeType +//! - imageSize +using VideoContent = PlayableContent; + +//! \brief Content class for m.audio +//! +//! Available fields: +//! - corresponding to the top-level JSON: +//! - url +//! - filename (extension to the CS API spec) +//! - corresponding to the "info" subobject: +//! - payloadSize ("size" in JSON) +//! - mimeType ("mimetype" in JSON) +//! - duration +//! - thumbnail.url ("thumbnail_url" in JSON) +//! - corresponding to the "info/thumbnail_info" subobject: contents of +//! thumbnail field, in the same vein as for "info": +//! - payloadSize +//! - mimeType +//! - imageSize +using AudioContent = PlayableContent; } // namespace Quotient::EventContent Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*) diff --git a/Quotient/events/roommessageevent.cpp b/Quotient/events/roommessageevent.cpp index 47c872152..4dfa05740 100644 --- a/Quotient/events/roommessageevent.cpp +++ b/Quotient/events/roommessageevent.cpp @@ -7,11 +7,13 @@ #include "../logging_categories_p.h" #include "eventrelation.h" +#include #include #include #include #include +#include using namespace Quotient; using namespace EventContent; @@ -121,8 +123,6 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, std::optional relatesTo) : RoomEvent( basicJson(TypeId, assembleContentJson(plainBody, jsonMsgType, content, relatesTo))) - , _content(content) - , _relatesTo(relatesTo) {} RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, @@ -131,7 +131,7 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, MsgType msgType, {} RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(obj), _content(nullptr) + : RoomEvent(obj) { if (isRedacted()) return; @@ -141,7 +141,6 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) bool msgTypeFound = false; for (const auto& mt : msgTypes) { if (mt.matrixType == msgtype) { - _content = mt.maker(content); msgTypeFound = true; } } @@ -152,8 +151,6 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) qCWarning(EVENTS) << formatJson << content; return; } - - fromJson(content[RelatesToKey], _relatesTo); } else { qCWarning(EVENTS) << "No body or msgtype in room message event"; qCWarning(EVENTS) << formatJson << obj; @@ -179,7 +176,34 @@ QMimeType RoomMessageEvent::mimeType() const { static const auto PlainTextMimeType = QMimeDatabase().mimeTypeForName("text/plain"_L1); - return _content ? _content->type() : PlainTextMimeType; + return content() ? content()->type() : PlainTextMimeType; +} + +std::unique_ptr RoomMessageEvent::content() const +{ + const QJsonObject content = contentJson(); + if (content.contains(MsgTypeKey) && content.contains(BodyKey)) { + auto msgtype = content[MsgTypeKey].toString(); + bool msgTypeFound = false; + std::unique_ptr eventContent = nullptr; + for (const auto& mt : msgTypes) { + if (mt.matrixType == msgtype) { + eventContent = mt.maker(content); + msgTypeFound = true; + } + } + + if (!msgTypeFound) { + qCWarning(EVENTS) << "RoomMessageEvent: unknown msg_type," + << " full content dump follows"; + qCWarning(EVENTS) << formatJson << content; + } + return eventContent; + } else { + qCWarning(EVENTS) << "No body or msgtype in room message event"; + qCWarning(EVENTS) << formatJson << fullJson(); + return nullptr; + } } bool RoomMessageEvent::hasTextContent() const @@ -189,19 +213,53 @@ bool RoomMessageEvent::hasTextContent() const || msgtype() == MsgType::Notice); } +std::optional RoomMessageEvent::textContent() const +{ + if (!hasTextContent() || !content()) { + return std::nullopt; + } + + return TextContent(contentJson()); +} + bool RoomMessageEvent::hasFileContent() const { return content() && content()->fileInfo(); } +std::optional RoomMessageEvent::fileContent() const +{ + if (!hasFileContent()) { + return std::nullopt; + } + + return FileContent(contentJson()); +} + bool RoomMessageEvent::hasThumbnail() const { return content() && content()->thumbnailInfo(); } +bool RoomMessageEvent::hasLocationContent() const +{ + return content() && msgtype() == MsgType::Location; +} + +std::optional RoomMessageEvent::locationContent() const +{ + if (!hasLocationContent()) { + return std::nullopt; + } + + return LocationContent(contentJson()); +} + std::optional RoomMessageEvent::relatesTo() const { - return _relatesTo; + std::optional relatesTo; + fromJson(contentJson()[RelatesToKey], relatesTo); + return relatesTo; } QString RoomMessageEvent::upstreamEventId() const @@ -215,7 +273,7 @@ QString RoomMessageEvent::replacedEvent() const if (!content() || !hasTextContent()) return {}; - return isReplacement(_relatesTo) ? _relatesTo->eventId : QString(); + return isReplacement(relatesTo()) ? relatesTo()->eventId : QString(); } bool RoomMessageEvent::isReplaced() const diff --git a/Quotient/events/roommessageevent.h b/Quotient/events/roommessageevent.h index 97c56013b..8cadc0379 100644 --- a/Quotient/events/roommessageevent.h +++ b/Quotient/events/roommessageevent.h @@ -46,24 +46,45 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { MsgType msgtype() const; QString rawMsgtype() const; QString plainBody() const; - const EventContent::TypedBase* content() const { return _content.get(); } + + //! \brief The EventContent for this event. + //! + //! \warning The result must be checked for nullptr as an event with just a plainBody + //! will not have a content object. + //! + //! \return an EventContent object if the event has content, nullptr otherwise. + std::unique_ptr content() const; + void editContent(auto visitor) { - visitor(*_content); - editJson()[ContentKey] = assembleContentJson(plainBody(), rawMsgtype(), _content.get(), _relatesTo); + visitor(*content()); + editJson()[ContentKey] = assembleContentJson(plainBody(), rawMsgtype(), content().get(), relatesTo()); } 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 Get the TextContent object for the event + //! + //! \return A TextContent object if the message has one; std::nullopt otherwise. + std::optional textContent() 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 Get the FileContent object for the event + //! + //! \return A FileContent object if the message has one; std::nullopt otherwise. + std::optional fileContent() const; + //! \brief Determine whether the message has a thumbnail //! //! \return true, if the message has a data structure corresponding to @@ -72,6 +93,17 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { //! false otherwise bool hasThumbnail() const; + //! \brief Determine whether the message has a location + //! + //! \return true, if the message has a data structure corresponding to + //! a location; false otherwise + bool hasLocationContent() const; + + //! \brief Get the LocationContent object for the event + //! + //! \return A LocationContent object if the message has one; std::nullopt otherwise. + std::optional locationContent() const; + //! \brief The EventRelation for this event. //! //! \return an EventRelation object which can be checked for type if it exists, @@ -142,9 +174,6 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { static QString rawMsgTypeForFile(const QFileInfo& fi); private: - std::unique_ptr _content; - std::optional _relatesTo; - // FIXME: should it really be static? static QJsonObject assembleContentJson(const QString& plainBody, const QString& jsonMsgType, @@ -155,120 +184,4 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { }; using MessageEventType = RoomMessageEvent::MsgType; - -namespace EventContent { - - // Additional event content types - - /** - * Rich text content for m.text, m.emote, m.notice - * - * Available fields: mimeType, body. The body can be either rich text - * or plain text, depending on what mimeType specifies. - */ - class QUOTIENT_API TextContent : public TypedBase { - public: - TextContent(QString text, const QString& contentType); - explicit TextContent(const QJsonObject& json); - - QMimeType type() const override { return mimeType; } - - QMimeType mimeType; - QString body; - - protected: - void fillJson(QJsonObject& json) const override; - }; - - /** - * Content class for m.location - * - * Available fields: - * - corresponding to the top-level JSON: - * - geoUri ("geo_uri" in JSON) - * - corresponding to the "info" subobject: - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: - * - thumbnail.payloadSize - * - thumbnail.mimeType - * - thumbnail.imageSize - */ - class QUOTIENT_API LocationContent : public TypedBase { - public: - LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); - explicit LocationContent(const QJsonObject& json); - - QMimeType type() const override; - - public: - QString geoUri; - Thumbnail thumbnail; - - protected: - void fillJson(QJsonObject& o) const override; - }; - - /** - * A base class for info types that include duration: audio and video - */ - template - class PlayableContent : public UrlBasedContent { - public: - using UrlBasedContent::UrlBasedContent; - PlayableContent(const QJsonObject& json) - : UrlBasedContent(json) - , duration(FileInfo::originalInfoJson["duration"_L1].toInt()) - {} - - protected: - void fillInfoJson(QJsonObject& infoJson) const override - { - infoJson.insert("duration"_L1, duration); - } - - public: - int duration; - }; - - /** - * Content class for m.video - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the CS API spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - duration - * - imageSize (QSize for a combination of "h" and "w" in JSON) - * - thumbnail.url ("thumbnail_url" in JSON) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field, in the same vein as for "info": - * - payloadSize - * - mimeType - * - imageSize - */ - using VideoContent = PlayableContent; - - /** - * Content class for m.audio - * - * Available fields: - * - corresponding to the top-level JSON: - * - url - * - filename (extension to the CS API spec) - * - corresponding to the "info" subobject: - * - payloadSize ("size" in JSON) - * - mimeType ("mimetype" in JSON) - * - duration - * - thumbnail.url ("thumbnail_url" in JSON - extension to the spec) - * - corresponding to the "info/thumbnail_info" subobject: contents of - * thumbnail field (extension to the spec): - * - payloadSize - * - mimeType - * - imageSize - */ - using AudioContent = PlayableContent; -} // namespace EventContent } // namespace Quotient diff --git a/Quotient/room.cpp b/Quotient/room.cpp index d80234476..9ba403d21 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -1743,7 +1743,7 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events, eventsIndex.insert(eId, index); if (usesEncryption) if (auto* const rme = ti.viewAs()) - if (auto* const content = rme->content()) + if (const auto content = rme->content()) if (auto* const fileInfo = content->fileInfo()) if (auto* const efm = std::get_if( &fileInfo->source)) diff --git a/gtad/gtad b/gtad/gtad index 4d62000b3..3ca859e1a 160000 --- a/gtad/gtad +++ b/gtad/gtad @@ -1 +1 @@ -Subproject commit 4d62000b3db86a8eba15a107d915cb477060b85b +Subproject commit 3ca859e1aae53b77d1e1f3a9b363cd40fdef54ff