aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Rusakov <Kitsune-Ral@users.sf.net>2022-05-08 19:08:57 +0200
committerGitHub <noreply@github.com>2022-05-08 19:08:57 +0200
commit3458fdf7b416fe64b82ec9b99553c7b2aa299e4c (patch)
treeb3b1bce3ebf0631597aa6d8451e62d198a42c2be
parent272cb01b05529971ea38e09bf75d8d8f194a9dd8 (diff)
parentc42d268db0b40cdba06381fc64a6966a72c90709 (diff)
downloadlibquotient-3458fdf7b416fe64b82ec9b99553c7b2aa299e4c.tar.gz
libquotient-3458fdf7b416fe64b82ec9b99553c7b2aa299e4c.zip
Merge #548: Streamline usage of event types, part 1
-rw-r--r--CMakeLists.txt1
-rw-r--r--lib/connection.cpp4
-rw-r--r--lib/converters.h16
-rw-r--r--lib/events/accountdataevents.h31
-rw-r--r--lib/events/callanswerevent.h6
-rw-r--r--lib/events/callcandidatesevent.h17
-rw-r--r--lib/events/callinviteevent.h5
-rw-r--r--lib/events/directchatevent.cpp2
-rw-r--r--lib/events/encryptionevent.cpp11
-rw-r--r--lib/events/encryptionevent.h8
-rw-r--r--lib/events/event.cpp2
-rw-r--r--lib/events/event.h55
-rw-r--r--lib/events/eventcontent.cpp60
-rw-r--r--lib/events/eventcontent.h285
-rw-r--r--lib/events/eventloader.h4
-rw-r--r--lib/events/redactionevent.h2
-rw-r--r--lib/events/roomevent.cpp16
-rw-r--r--lib/events/roomevent.h9
-rw-r--r--lib/events/roommemberevent.cpp15
-rw-r--r--lib/events/roommemberevent.h7
-rw-r--r--lib/events/roommessageevent.cpp18
-rw-r--r--lib/events/roommessageevent.h29
-rw-r--r--lib/events/roompowerlevelsevent.cpp28
-rw-r--r--lib/events/roompowerlevelsevent.h8
-rw-r--r--lib/events/simplestateevents.h72
-rw-r--r--lib/events/single_key_value.h27
-rw-r--r--lib/events/stateevent.cpp6
-rw-r--r--lib/events/stateevent.h41
-rw-r--r--lib/room.cpp9
-rw-r--r--lib/room.h3
-rw-r--r--quotest/quotest.cpp18
31 files changed, 412 insertions, 403 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dc2459e9..537d580e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,7 @@ list(APPEND lib_SRCS
lib/events/eventloader.h
lib/events/roomevent.h lib/events/roomevent.cpp
lib/events/stateevent.h lib/events/stateevent.cpp
+ lib/events/single_key_value.h
lib/events/simplestateevents.h
lib/events/eventcontent.h lib/events/eventcontent.cpp
lib/events/eventrelation.h lib/events/eventrelation.cpp
diff --git a/lib/connection.cpp b/lib/connection.cpp
index a969b3b9..1ea394a1 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -1858,11 +1858,11 @@ void Connection::saveState() const
}
{
QJsonArray accountDataEvents {
- basicEventJson(QStringLiteral("m.direct"), toJson(d->directChats))
+ Event::basicJson(QStringLiteral("m.direct"), toJson(d->directChats))
};
for (const auto& e : d->accountData)
accountDataEvents.append(
- basicEventJson(e.first, e.second->contentJson()));
+ Event::basicJson(e.first, e.second->contentJson()));
rootObj.insert(QStringLiteral("account_data"),
QJsonObject {
diff --git a/lib/converters.h b/lib/converters.h
index 515c96fd..6515310a 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -414,4 +414,20 @@ inline void addParam(ContT& container, const QString& key, ValT&& value)
_impl::AddNode<std::decay_t<ValT>, Force>::impl(container, key,
std::forward<ValT>(value));
}
+
+// This is a facility function to convert camelCase method/variable names
+// used throughout Quotient to snake_case JSON keys - see usage in
+// single_key_value.h and event.h (DEFINE_CONTENT_GETTER macro).
+inline auto toSnakeCase(QLatin1String s)
+{
+ QString result { s };
+ for (auto it = result.begin(); it != result.end(); ++it)
+ if (it->isUpper()) {
+ const auto offset = static_cast<int>(it - result.begin());
+ result.insert(offset, '_'); // NB: invalidates iterators
+ it = result.begin() + offset + 1;
+ *it = it->toLower();
+ }
+ return result;
+}
} // namespace Quotient
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
index 12f1f00b..ec2f64e3 100644
--- a/lib/events/accountdataevents.h
+++ b/lib/events/accountdataevents.h
@@ -46,27 +46,14 @@ struct JsonObjectConverter<TagRecord> {
using TagsMap = QHash<QString, TagRecord>;
-#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \
- class QUOTIENT_API _Name : public Event { \
- public: \
- using content_type = _ContentType; \
- DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- explicit _Name(const QJsonObject& obj) : Event(typeId(), obj) {} \
- explicit _Name(const content_type& content) \
- : Event(typeId(), matrixTypeId(), \
- QJsonObject { \
- { QStringLiteral(#_ContentKey), toJson(content) } }) \
- {} \
- auto _ContentKey() const \
- { \
- return contentPart<content_type>(#_ContentKey##_ls); \
- } \
- }; \
- REGISTER_EVENT_TYPE(_Name) \
- // End of macro
-
-DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", TagsMap, tags)
-DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", QString, event_id)
-DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, "m.ignored_user_list", QSet<QString>,
+DEFINE_SIMPLE_EVENT(TagEvent, Event, "m.tag", TagsMap, tags)
+DEFINE_SIMPLE_EVENT(ReadMarkerEventImpl, Event, "m.fully_read", QString, eventId)
+class ReadMarkerEvent : public ReadMarkerEventImpl {
+public:
+ using ReadMarkerEventImpl::ReadMarkerEventImpl;
+ [[deprecated("Use ReadMarkerEvent::eventId() instead")]]
+ QString event_id() const { return eventId(); }
+};
+DEFINE_SIMPLE_EVENT(IgnoredUsersEvent, Event, "m.ignored_user_list", QSet<QString>,
ignored_users)
} // namespace Quotient
diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h
index 8ffe60f2..70292a7a 100644
--- a/lib/events/callanswerevent.h
+++ b/lib/events/callanswerevent.h
@@ -17,10 +17,8 @@ public:
const QString& sdp);
explicit CallAnswerEvent(const QString& callId, const QString& sdp);
- int lifetime() const
- {
- return contentPart<int>("lifetime"_ls);
- } // FIXME: Omittable<>?
+ QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>?
+
QString sdp() const
{
return contentPart<QJsonObject>("answer"_ls).value("sdp"_ls).toString();
diff --git a/lib/events/callcandidatesevent.h b/lib/events/callcandidatesevent.h
index 74c38f2c..e949f722 100644
--- a/lib/events/callcandidatesevent.h
+++ b/lib/events/callcandidatesevent.h
@@ -23,20 +23,9 @@ public:
{ { QStringLiteral("candidates"), candidates } })
{}
- QJsonArray candidates() const
- {
- return contentPart<QJsonArray>("candidates"_ls);
- }
-
- QString callId() const
- {
- return contentPart<QString>("call_id");
- }
-
- int version() const
- {
- return contentPart<int>("version");
- }
+ QUO_CONTENT_GETTER(QJsonArray, candidates)
+ QUO_CONTENT_GETTER(QString, callId)
+ QUO_CONTENT_GETTER(int, version)
};
REGISTER_EVENT_TYPE(CallCandidatesEvent)
diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h
index 47362b5c..1b1f4f0f 100644
--- a/lib/events/callinviteevent.h
+++ b/lib/events/callinviteevent.h
@@ -16,10 +16,7 @@ public:
explicit CallInviteEvent(const QString& callId, const int lifetime,
const QString& sdp);
- int lifetime() const
- {
- return contentPart<int>("lifetime"_ls);
- } // FIXME: Omittable<>?
+ QUO_CONTENT_GETTER(int, lifetime) // FIXME: Omittable<>?
QString sdp() const
{
return contentPart<QJsonObject>("offer"_ls).value("sdp"_ls).toString();
diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp
index 0ee1f7b0..83bb1e32 100644
--- a/lib/events/directchatevent.cpp
+++ b/lib/events/directchatevent.cpp
@@ -3,8 +3,6 @@
#include "directchatevent.h"
-#include <QtCore/QJsonArray>
-
using namespace Quotient;
QMultiHash<QString, QString> DirectChatEvent::usersToDirectChats() const
diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp
index 6272c668..6e994cd4 100644
--- a/lib/events/encryptionevent.cpp
+++ b/lib/events/encryptionevent.cpp
@@ -47,11 +47,12 @@ EncryptionEventContent::EncryptionEventContent(EncryptionType et)
}
}
-void EncryptionEventContent::fillJson(QJsonObject* o) const
+QJsonObject EncryptionEventContent::toJson() const
{
- Q_ASSERT(o);
+ QJsonObject o;
if (encryption != EncryptionType::Undefined)
- o->insert(AlgorithmKey, algorithm);
- o->insert(RotationPeriodMsKey, rotationPeriodMs);
- o->insert(RotationPeriodMsgsKey, rotationPeriodMsgs);
+ o.insert(AlgorithmKey, algorithm);
+ o.insert(RotationPeriodMsKey, rotationPeriodMs);
+ o.insert(RotationPeriodMsgsKey, rotationPeriodMsgs);
+ return o;
}
diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h
index 124ced33..5b5420ec 100644
--- a/lib/events/encryptionevent.h
+++ b/lib/events/encryptionevent.h
@@ -4,12 +4,11 @@
#pragma once
-#include "eventcontent.h"
#include "stateevent.h"
#include "quotient_common.h"
namespace Quotient {
-class QUOTIENT_API EncryptionEventContent : public EventContent::Base {
+class QUOTIENT_API EncryptionEventContent {
public:
enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined };
@@ -20,13 +19,12 @@ public:
{}
explicit EncryptionEventContent(const QJsonObject& json);
+ QJsonObject toJson() const;
+
EncryptionType encryption;
QString algorithm;
int rotationPeriodMs;
int rotationPeriodMsgs;
-
-protected:
- void fillJson(QJsonObject* o) const override;
};
using EncryptionType = EncryptionEventContent::EncryptionType;
diff --git a/lib/events/event.cpp b/lib/events/event.cpp
index 4c304a3c..1f1eebaa 100644
--- a/lib/events/event.cpp
+++ b/lib/events/event.cpp
@@ -29,7 +29,7 @@ Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json)
}
Event::Event(Type type, event_mtype_t matrixType, const QJsonObject& contentJson)
- : Event(type, basicEventJson(matrixType, contentJson))
+ : Event(type, basicJson(matrixType, contentJson))
{}
Event::~Event() = default;
diff --git a/lib/events/event.h b/lib/events/event.h
index 113fa3fa..ec21c6aa 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -48,13 +48,6 @@ const QString RoomIdKey { RoomIdKeyL };
const QString UnsignedKey { UnsignedKeyL };
const QString StateKeyKey { StateKeyKeyL };
-/// Make a minimal correct Matrix event JSON
-inline QJsonObject basicEventJson(const QString& matrixType,
- const QJsonObject& content)
-{
- return { { TypeKey, matrixType }, { ContentKey, content } };
-}
-
// === Event types ===
using event_type_t = QLatin1String;
@@ -193,6 +186,13 @@ public:
Event& operator=(Event&&) = delete;
virtual ~Event();
+ /// Make a minimal correct Matrix event JSON
+ static QJsonObject basicJson(const QString& matrixType,
+ const QJsonObject& content)
+ {
+ return { { TypeKey, matrixType }, { ContentKey, content } };
+ }
+
Type type() const { return _type; }
QString matrixType() const;
[[deprecated("Use fullJson() and stringify it with QJsonDocument::toJson() "
@@ -258,6 +258,21 @@ template <typename EventT>
using EventsArray = std::vector<event_ptr_tt<EventT>>;
using Events = EventsArray<Event>;
+//! \brief Define an inline method obtaining a content part
+//!
+//! This macro adds a const method that extracts a JSON value at the key
+//! <tt>toSnakeCase(PartName_)</tt> (sic) and converts it to the type
+//! \p PartType_. Effectively, the generated method is an equivalent of
+//! \code
+//! contentPart<PartType_>(Quotient::toSnakeCase(#PartName_##_ls));
+//! \endcode
+#define QUO_CONTENT_GETTER(PartType_, PartName_) \
+ PartType_ PartName_() const \
+ { \
+ static const auto JsonKey = toSnakeCase(#PartName_##_ls); \
+ return contentPart<PartType_>(JsonKey); \
+ }
+
// === Facilities for event class definitions ===
// This macro should be used in a public section of an event class to
@@ -278,6 +293,32 @@ using Events = EventsArray<Event>;
Type_::factory.addMethod<Type_>(); \
// End of macro
+/// \brief Define a new event class with a single key-value pair in the content
+///
+/// This macro defines a new event class \p Name_ derived from \p Base_,
+/// with Matrix event type \p TypeId_, providing a getter named \p GetterName_
+/// for a single value of type \p ValueType_ inside the event content.
+/// To retrieve the value the getter uses a JSON key name that corresponds to
+/// its own (getter's) name but written in snake_case. \p GetterName_ must be
+/// in camelCase, no quotes (an identifier, not a literal).
+#define DEFINE_SIMPLE_EVENT(Name_, Base_, TypeId_, ValueType_, GetterName_) \
+ class QUOTIENT_API Name_ : public Base_ { \
+ public: \
+ using content_type = ValueType_; \
+ DEFINE_EVENT_TYPEID(TypeId_, Name_) \
+ explicit Name_(const QJsonObject& obj) : Base_(TypeId, obj) {} \
+ explicit Name_(const content_type& content) \
+ : Name_(Base_::basicJson(TypeId, { { JsonKey, toJson(content) } })) \
+ {} \
+ auto GetterName_() const \
+ { \
+ return contentPart<content_type>(JsonKey); \
+ } \
+ static inline const auto JsonKey = toSnakeCase(#GetterName_##_ls); \
+ }; \
+ REGISTER_EVENT_TYPE(Name_) \
+ // End of macro
+
// === is<>(), eventCast<>() and switchOnType<>() ===
template <class EventT>
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
index 9d7edf20..6218e3b8 100644
--- a/lib/events/eventcontent.cpp
+++ b/lib/events/eventcontent.cpp
@@ -15,7 +15,7 @@ using std::move;
QJsonObject Base::toJson() const
{
QJsonObject o;
- fillJson(&o);
+ fillJson(o);
return o;
}
@@ -29,12 +29,13 @@ FileInfo::FileInfo(const QFileInfo &fi)
}
FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType,
- Omittable<EncryptedFile> file, QString originalFilename)
+ Omittable<EncryptedFile> encryptedFile,
+ QString originalFilename)
: mimeType(mimeType)
, url(move(u))
, payloadSize(payloadSize)
, originalName(move(originalFilename))
- , file(file)
+ , file(move(encryptedFile))
{
if (!isValid())
qCWarning(MESSAGES)
@@ -44,7 +45,7 @@ FileInfo::FileInfo(QUrl u, qint64 payloadSize, const QMimeType& mimeType,
}
FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &file,
+ Omittable<EncryptedFile> encryptedFile,
QString originalFilename)
: originalInfoJson(infoJson)
, mimeType(
@@ -52,7 +53,7 @@ FileInfo::FileInfo(QUrl mxcUrl, const QJsonObject& infoJson,
, url(move(mxcUrl))
, payloadSize(fromJson<qint64>(infoJson["size"_ls]))
, originalName(move(originalFilename))
- , file(file)
+ , file(move(encryptedFile))
{
if(url.isEmpty() && file.has_value()) {
url = file->url;
@@ -67,14 +68,15 @@ bool FileInfo::isValid() const
&& (url.authority() + url.path()).count('/') == 1;
}
-void FileInfo::fillInfoJson(QJsonObject* infoJson) const
+QJsonObject Quotient::EventContent::toInfoJson(const FileInfo& info)
{
- Q_ASSERT(infoJson);
- if (payloadSize != -1)
- infoJson->insert(QStringLiteral("size"), payloadSize);
- if (mimeType.isValid())
- infoJson->insert(QStringLiteral("mimetype"), mimeType.name());
+ QJsonObject infoJson;
+ if (info.payloadSize != -1)
+ infoJson.insert(QStringLiteral("size"), info.payloadSize);
+ if (info.mimeType.isValid())
+ infoJson.insert(QStringLiteral("mimetype"), info.mimeType.name());
//TODO add encryptedfile
+ return infoJson;
}
ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize)
@@ -82,38 +84,40 @@ ImageInfo::ImageInfo(const QFileInfo& fi, QSize 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)
+ QSize imageSize, Omittable<EncryptedFile> encryptedFile,
+ const QString& originalFilename)
+ : FileInfo(mxcUrl, fileSize, type, move(encryptedFile), originalFilename)
, imageSize(imageSize)
{}
ImageInfo::ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &file,
+ Omittable<EncryptedFile> encryptedFile,
const QString& originalFilename)
- : FileInfo(mxcUrl, infoJson, file, originalFilename)
+ : FileInfo(mxcUrl, infoJson, move(encryptedFile), originalFilename)
, imageSize(infoJson["w"_ls].toInt(), infoJson["h"_ls].toInt())
{}
-void ImageInfo::fillInfoJson(QJsonObject* infoJson) const
+QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info)
{
- FileInfo::fillInfoJson(infoJson);
- if (imageSize.width() != -1)
- infoJson->insert(QStringLiteral("w"), imageSize.width());
- if (imageSize.height() != -1)
- infoJson->insert(QStringLiteral("h"), imageSize.height());
+ auto infoJson = toInfoJson(static_cast<const FileInfo&>(info));
+ if (info.imageSize.width() != -1)
+ infoJson.insert(QStringLiteral("w"), info.imageSize.width());
+ if (info.imageSize.height() != -1)
+ infoJson.insert(QStringLiteral("h"), info.imageSize.height());
+ return infoJson;
}
-Thumbnail::Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file)
+Thumbnail::Thumbnail(const QJsonObject& infoJson,
+ Omittable<EncryptedFile> encryptedFile)
: ImageInfo(QUrl(infoJson["thumbnail_url"_ls].toString()),
- infoJson["thumbnail_info"_ls].toObject(),
- file)
+ infoJson["thumbnail_info"_ls].toObject(), move(encryptedFile))
{}
-void Thumbnail::fillInfoJson(QJsonObject* infoJson) const
+void Thumbnail::dumpTo(QJsonObject& infoJson) const
{
if (url.isValid())
- infoJson->insert(QStringLiteral("thumbnail_url"), url.toString());
+ infoJson.insert(QStringLiteral("thumbnail_url"), url.toString());
if (!imageSize.isEmpty())
- infoJson->insert(QStringLiteral("thumbnail_info"),
- toInfoJson<ImageInfo>(*this));
+ infoJson.insert(QStringLiteral("thumbnail_info"),
+ toInfoJson(*this));
}
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
index de9a792b..bbd35618 100644
--- a/lib/events/eventcontent.h
+++ b/lib/events/eventcontent.h
@@ -19,22 +19,18 @@ class QFileInfo;
namespace Quotient {
namespace EventContent {
- /**
- * A base class for all content types that can be stored
- * in a RoomMessageEvent
- *
- * Each content type class should have a constructor taking
- * a QJsonObject and override fillJson() with an implementation
- * that will fill the target QJsonObject with stored values. It is
- * assumed but not required that a content object can also be created
- * from plain data.
- */
+ //! \brief Base for all content types that can be stored in RoomMessageEvent
+ //!
+ //! Each content type class should have a constructor taking
+ //! a QJsonObject and override fillJson() with an implementation
+ //! that will fill the target QJsonObject with stored values. It is
+ //! assumed but not required that a content object can also be created
+ //! from plain data.
class QUOTIENT_API Base {
public:
explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
virtual ~Base() = default;
- // FIXME: make toJson() from converters.* work on base classes
QJsonObject toJson() const;
public:
@@ -44,7 +40,7 @@ namespace EventContent {
Base(const Base&) = default;
Base(Base&&) = default;
- virtual void fillJson(QJsonObject* o) const = 0;
+ virtual void fillJson(QJsonObject&) const = 0;
};
// The below structures fairly follow CS spec 11.2.1.6. The overall
@@ -54,52 +50,64 @@ namespace EventContent {
// A quick classes inheritance structure follows (the definitions are
// spread across eventcontent.h and roommessageevent.h):
+ // UrlBasedContent<InfoT> : InfoT + url and thumbnail data
+ // PlayableContent<InfoT> : + duration attribute
// FileInfo
- // FileContent : UrlWithThumbnailContent<FileInfo>
- // AudioContent : PlayableContent<UrlBasedContent<FileInfo>>
+ // FileContent = UrlBasedContent<FileInfo>
+ // AudioContent = PlayableContent<FileInfo>
// ImageInfo : FileInfo + imageSize attribute
- // ImageContent : UrlWithThumbnailContent<ImageInfo>
- // VideoContent : PlayableContent<UrlWithThumbnailContent<ImageInfo>>
-
- /**
- * A base/mixin class for structures representing an "info" object for
- * some content types. These include most attachment types currently in
- * the CS API spec.
- *
- * In order to use it in a content class, derive both from TypedBase
- * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo)
- * and call fillInfoJson() to fill the "info" subobject. Make sure
- * to pass an "info" part of JSON to FileInfo constructor, not the whole
- * JSON content, as well as contents of "url" (or a similar key) and
- * optionally "filename" node from the main JSON content. Assuming you
- * don't do unusual things, you should use \p UrlBasedContent<> instead
- * of doing multiple inheritance and overriding Base::fillJson() by hand.
- *
- * This class is not polymorphic.
- */
+ // ImageContent = UrlBasedContent<ImageInfo>
+ // VideoContent = PlayableContent<ImageInfo>
+
+ //! \brief Mix-in class representing `info` subobject in content JSON
+ //!
+ //! This is one of base classes for content types that deal with files or
+ //! URLs. It stores the file metadata attributes, such as size, MIME type
+ //! etc. found in the `content/info` subobject of event JSON payloads.
+ //! Actual content classes derive from this class _and_ TypedBase that
+ //! provides a polymorphic interface to access data in the mix-in. FileInfo
+ //! (as well as ImageInfo, that adds image size to the metadata) is NOT
+ //! polymorphic and is used in a non-polymorphic way to store thumbnail
+ //! metadata (in a separate instance), next to the metadata on the file
+ //! itself.
+ //!
+ //! If you need to make a new _content_ (not info) class based on files/URLs
+ //! take UrlBasedContent as the example, i.e.:
+ //! 1. Double-inherit from this class (or ImageInfo) and TypedBase.
+ //! 2. Provide a constructor from QJsonObject that will pass the `info`
+ //! subobject (not the whole content JSON) down to FileInfo/ImageInfo.
+ //! 3. Override fillJson() to customise the JSON export logic. Make sure
+ //! to call toInfoJson() from it to produce the payload for the `info`
+ //! subobject in the JSON payload.
+ //!
+ //! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent,
+ //! UrlBasedContent
class QUOTIENT_API FileInfo {
public:
FileInfo() = default;
+ //! \brief Construct from a QFileInfo object
+ //!
+ //! \param fi a QFileInfo object referring to an existing file
explicit FileInfo(const QFileInfo& fi);
explicit FileInfo(QUrl mxcUrl, qint64 payloadSize = -1,
const QMimeType& mimeType = {},
- Omittable<EncryptedFile> file = none,
+ Omittable<EncryptedFile> encryptedFile = none,
QString originalFilename = {});
+ //! \brief Construct from a JSON `info` payload
+ //!
+ //! Make sure to pass the `info` subobject of content JSON, not the
+ //! whole JSON content.
FileInfo(QUrl mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &file,
+ Omittable<EncryptedFile> encryptedFile,
QString originalFilename = {});
bool isValid() const;
- void fillInfoJson(QJsonObject* infoJson) const;
-
- /**
- * \brief Extract media id from the URL
- *
- * This can be used, e.g., to construct a QML-facing image://
- * URI as follows:
- * \code "image://provider/" + info.mediaId() \endcode
- */
+ //! \brief Extract media id from the URL
+ //!
+ //! This can be used, e.g., to construct a QML-facing image://
+ //! URI as follows:
+ //! \code "image://provider/" + info.mediaId() \endcode
QString mediaId() const { return url.authority() + url.path(); }
public:
@@ -111,52 +119,40 @@ namespace EventContent {
Omittable<EncryptedFile> file = none;
};
- template <typename InfoT>
- QJsonObject toInfoJson(const InfoT& info)
- {
- QJsonObject infoJson;
- info.fillInfoJson(&infoJson);
- return infoJson;
- }
+ QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info);
- /**
- * A content info class for image content types: image, thumbnail, video
- */
+ //! \brief A content info class for image/video content types and thumbnails
class QUOTIENT_API ImageInfo : public FileInfo {
public:
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,
+ Omittable<EncryptedFile> encryptedFile = none,
const QString& originalFilename = {});
ImageInfo(const QUrl& mxcUrl, const QJsonObject& infoJson,
- const Omittable<EncryptedFile> &encryptedFile,
+ Omittable<EncryptedFile> encryptedFile,
const QString& originalFilename = {});
- void fillInfoJson(QJsonObject* infoJson) const;
-
public:
QSize imageSize;
};
- /**
- * An auxiliary class for an info type that carries a thumbnail
- *
- * This class saves/loads a thumbnail to/from "info" subobject of
- * the JSON representation of event content; namely,
- * "info/thumbnail_url" and "info/thumbnail_info" fields are used.
- */
+ QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info);
+
+ //! \brief An auxiliary class for an info type that carries a thumbnail
+ //!
+ //! This class saves/loads a thumbnail to/from `info` subobject of
+ //! the JSON representation of event content; namely, `info/thumbnail_url`
+ //! and `info/thumbnail_info` fields are used.
class QUOTIENT_API Thumbnail : public ImageInfo {
public:
using ImageInfo::ImageInfo;
- Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file = none);
+ Thumbnail(const QJsonObject& infoJson,
+ Omittable<EncryptedFile> encryptedFile = none);
- /**
- * Writes thumbnail information to "thumbnail_info" subobject
- * and thumbnail URL to "thumbnail_url" node inside "info".
- */
- void fillInfoJson(QJsonObject* infoJson) const;
+ //! \brief Add thumbnail information to the passed `info` JSON object
+ void dumpTo(QJsonObject& infoJson) const;
};
class QUOTIENT_API TypedBase : public Base {
@@ -171,114 +167,91 @@ namespace EventContent {
using Base::Base;
};
- /**
- * A base class for content types that have a URL and additional info
- *
- * Types that derive from this class template take "url" and,
- * optionally, "filename" values from the top-level JSON object and
- * the rest of information from the "info" subobject, as defined by
- * the parameter type.
- *
- * \tparam InfoT base info class
- */
+ //! \brief A template class for content types with a URL and additional info
+ //!
+ //! Types that derive from this class template take `url` and,
+ //! optionally, `filename` values from the top-level JSON object and
+ //! the rest of information from the `info` subobject, as defined by
+ //! the parameter type.
+ //! \tparam InfoT base info class - FileInfo or ImageInfo
template <class InfoT>
- class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT {
+ class UrlBasedContent : public TypedBase, public InfoT {
public:
using InfoT::InfoT;
explicit UrlBasedContent(const QJsonObject& json)
: TypedBase(json)
, InfoT(QUrl(json["url"].toString()), json["info"].toObject(),
- fromJson<Omittable<EncryptedFile>>(json["file"]), json["filename"].toString())
+ fromJson<Omittable<EncryptedFile>>(json["file"]),
+ json["filename"].toString())
+ , thumbnail(FileInfo::originalInfoJson)
{
- // A small hack to facilitate links creation in QML.
+ // Two small hacks on originalJson to expose mediaIds to QML
originalJson.insert("mediaId", InfoT::mediaId());
+ originalJson.insert("thumbnailMediaId", thumbnail.mediaId());
}
QMimeType type() const override { return InfoT::mimeType; }
const FileInfo* fileInfo() const override { return this; }
FileInfo* fileInfo() override { return this; }
-
- protected:
- void fillJson(QJsonObject* json) const override
- {
- Q_ASSERT(json);
- 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));
- }
- };
-
- template <typename InfoT>
- class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent<InfoT> {
- public:
- // NB: when using inherited constructors, thumbnail has to be
- // initialised separately
- using UrlBasedContent<InfoT>::UrlBasedContent;
- explicit UrlWithThumbnailContent(const QJsonObject& json)
- : UrlBasedContent<InfoT>(json), thumbnail(InfoT::originalInfoJson)
- {
- // Another small hack, to simplify making a thumbnail link
- UrlBasedContent<InfoT>::originalJson.insert("thumbnailMediaId",
- thumbnail.mediaId());
- }
-
const Thumbnail* thumbnailInfo() const override { return &thumbnail; }
public:
Thumbnail thumbnail;
protected:
- void fillJson(QJsonObject* json) const override
+ virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const
+ {}
+
+ void fillJson(QJsonObject& json) const override
{
- UrlBasedContent<InfoT>::fillJson(json);
- auto infoJson = json->take("info").toObject();
- thumbnail.fillInfoJson(&infoJson);
- json->insert("info", infoJson);
+ 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);
+ auto infoJson = toInfoJson(*this);
+ if (thumbnail.isValid())
+ thumbnail.dumpTo(infoJson);
+ fillInfoJson(infoJson);
+ json.insert("info", infoJson);
}
};
- /**
- * Content class for m.image
- *
- * Available fields:
- * - corresponding to the top-level JSON:
- * - url
- * - filename (extension to the spec)
- * - corresponding to the "info" subobject:
- * - payloadSize ("size" in JSON)
- * - mimeType ("mimetype" in JSON)
- * - 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 the main image:
- * - payloadSize
- * - mimeType
- * - imageSize
- */
- using ImageContent = UrlWithThumbnailContent<ImageInfo>;
-
- /**
- * Content class for m.file
- *
- * Available fields:
- * - corresponding to the top-level JSON:
- * - url
- * - filename
- * - corresponding to the "info" subobject:
- * - payloadSize ("size" in JSON)
- * - mimeType ("mimetype" in JSON)
- * - thumbnail.url ("thumbnail_url" in JSON)
- * - corresponding to the "info/thumbnail_info" subobject:
- * - thumbnail.payloadSize
- * - thumbnail.mimeType
- * - thumbnail.imageSize (QSize for "h" and "w" in JSON)
- */
- using FileContent = UrlWithThumbnailContent<FileInfo>;
+ //! \brief Content class for m.image
+ //!
+ //! Available fields:
+ //! - corresponding to the top-level JSON:
+ //! - url
+ //! - filename (extension to the spec)
+ //! - corresponding to the `info` subobject:
+ //! - payloadSize (`size` in JSON)
+ //! - mimeType (`mimetype` in JSON)
+ //! - 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 the main image:
+ //! - payloadSize
+ //! - mimeType
+ //! - imageSize
+ using ImageContent = UrlBasedContent<ImageInfo>;
+
+ //! \brief Content class for m.file
+ //!
+ //! Available fields:
+ //! - corresponding to the top-level JSON:
+ //! - url
+ //! - filename
+ //! - corresponding to the `info` subobject:
+ //! - payloadSize (`size` in JSON)
+ //! - mimeType (`mimetype` in JSON)
+ //! - thumbnail.url (`thumbnail_url` in JSON)
+ //! - corresponding to the `info/thumbnail_info` subobject:
+ //! - thumbnail.payloadSize
+ //! - thumbnail.mimeType
+ //! - thumbnail.imageSize (QSize for `h` and `w` in JSON)
+ using FileContent = UrlBasedContent<FileInfo>;
} // namespace EventContent
} // namespace Quotient
Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*)
diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h
index fe624d70..c7b82e8e 100644
--- a/lib/events/eventloader.h
+++ b/lib/events/eventloader.h
@@ -29,7 +29,7 @@ template <typename BaseEventT>
inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType,
const QJsonObject& content)
{
- return doLoadEvent<BaseEventT>(basicEventJson(matrixType, content),
+ return doLoadEvent<BaseEventT>(Event::basicJson(matrixType, content),
matrixType);
}
@@ -44,7 +44,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType,
const QString& stateKey = {})
{
return doLoadEvent<StateEventBase>(
- basicStateEventJson(matrixType, content, stateKey), matrixType);
+ StateEventBase::basicJson(matrixType, content, stateKey), matrixType);
}
template <typename EventT>
diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h
index be20bf52..1c486a44 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 contentPart<QString>("reason"_ls); }
+ QUO_CONTENT_GETTER(QString, reason)
};
REGISTER_EVENT_TYPE(RedactionEvent)
} // namespace Quotient
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index 2f482871..3ddf5ac4 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -101,22 +101,22 @@ void RoomEvent::dumpTo(QDebug dbg) const
dbg << " (made at " << originTimestamp().toString(Qt::ISODate) << ')';
}
-QJsonObject makeCallContentJson(const QString& callId, int version,
- QJsonObject content)
+QJsonObject CallEventBase::basicJson(const QString& matrixType,
+ const QString& callId, int version,
+ QJsonObject contentJson)
{
- content.insert(QStringLiteral("call_id"), callId);
- content.insert(QStringLiteral("version"), version);
- return content;
+ contentJson.insert(QStringLiteral("call_id"), callId);
+ contentJson.insert(QStringLiteral("version"), version);
+ return RoomEvent::basicJson(matrixType, contentJson);
}
CallEventBase::CallEventBase(Type type, event_mtype_t matrixType,
const QString& callId, int version,
const QJsonObject& contentJson)
- : RoomEvent(type, matrixType,
- makeCallContentJson(callId, version, contentJson))
+ : RoomEvent(type, basicJson(matrixType, callId, version, contentJson))
{}
-CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json)
+CallEventBase::CallEventBase(Type type, const QJsonObject& json)
: RoomEvent(type, json)
{
if (callId().isEmpty())
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index a7d6c428..7f724689 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -96,8 +96,13 @@ public:
~CallEventBase() override = default;
bool isCallEvent() const override { return true; }
- QString callId() const { return contentPart<QString>("call_id"_ls); }
- int version() const { return contentPart<int>("version"_ls); }
+ QUO_CONTENT_GETTER(QString, callId)
+ QUO_CONTENT_GETTER(int, version)
+
+protected:
+ static QJsonObject basicJson(const QString& matrixType,
+ const QString& callId, int version,
+ QJsonObject contentJson = {});
};
} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::RoomEvent*)
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index b4770224..c3be0e00 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -4,8 +4,6 @@
#include "roommemberevent.h"
-#include "logging.h"
-
#include <QtCore/QtAlgorithms>
namespace Quotient {
@@ -43,19 +41,20 @@ MemberEventContent::MemberEventContent(const QJsonObject& json)
displayName = sanitized(*displayName);
}
-void MemberEventContent::fillJson(QJsonObject* o) const
+QJsonObject MemberEventContent::toJson() const
{
- Q_ASSERT(o);
+ QJsonObject o;
if (membership != Membership::Invalid)
- o->insert(QStringLiteral("membership"),
+ o.insert(QStringLiteral("membership"),
MembershipStrings[qCountTrailingZeroBits(
std::underlying_type_t<Membership>(membership))]);
if (displayName)
- o->insert(QStringLiteral("displayname"), *displayName);
+ o.insert(QStringLiteral("displayname"), *displayName);
if (avatarUrl && avatarUrl->isValid())
- o->insert(QStringLiteral("avatar_url"), avatarUrl->toString());
+ o.insert(QStringLiteral("avatar_url"), avatarUrl->toString());
if (!reason.isEmpty())
- o->insert(QStringLiteral("reason"), reason);
+ o.insert(QStringLiteral("reason"), reason);
+ return o;
}
bool RoomMemberEvent::changesMembership() const
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index ceb7826b..dd33ea6b 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -5,18 +5,18 @@
#pragma once
-#include "eventcontent.h"
#include "stateevent.h"
#include "quotient_common.h"
namespace Quotient {
-class QUOTIENT_API MemberEventContent : public EventContent::Base {
+class QUOTIENT_API MemberEventContent {
public:
using MembershipType
[[deprecated("Use Quotient::Membership instead")]] = Membership;
QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {}
explicit MemberEventContent(const QJsonObject& json);
+ QJsonObject toJson() const;
Membership membership;
/// (Only for invites) Whether the invite is to a direct chat
@@ -24,9 +24,6 @@ public:
Omittable<QString> displayName;
Omittable<QUrl> avatarUrl;
QString reason;
-
-protected:
- void fillJson(QJsonObject* o) const override;
};
using MembershipType [[deprecated("Use Membership instead")]] = Membership;
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
index d63352cb..d9d3fbe0 100644
--- a/lib/events/roommessageevent.cpp
+++ b/lib/events/roommessageevent.cpp
@@ -302,17 +302,16 @@ TextContent::TextContent(const QJsonObject& json)
}
}
-void TextContent::fillJson(QJsonObject* json) const
+void TextContent::fillJson(QJsonObject &json) const
{
static const auto FormatKey = QStringLiteral("format");
- Q_ASSERT(json);
if (mimeType.inherits("text/html")) {
- json->insert(FormatKey, HtmlContentTypeId);
- json->insert(FormattedBodyKey, body);
+ json.insert(FormatKey, HtmlContentTypeId);
+ json.insert(FormattedBodyKey, body);
}
if (relatesTo) {
- json->insert(
+ json.insert(
QStringLiteral("m.relates_to"),
relatesTo->type == EventRelation::ReplyType
? QJsonObject { { relatesTo->type,
@@ -326,7 +325,7 @@ void TextContent::fillJson(QJsonObject* json) const
newContentJson.insert(FormatKey, HtmlContentTypeId);
newContentJson.insert(FormattedBodyKey, body);
}
- json->insert(QStringLiteral("m.new_content"), newContentJson);
+ json.insert(QStringLiteral("m.new_content"), newContentJson);
}
}
}
@@ -347,9 +346,8 @@ QMimeType LocationContent::type() const
return QMimeDatabase().mimeTypeForData(geoUri.toLatin1());
}
-void LocationContent::fillJson(QJsonObject* o) const
+void LocationContent::fillJson(QJsonObject& o) const
{
- Q_ASSERT(o);
- o->insert(QStringLiteral("geo_uri"), geoUri);
- o->insert(QStringLiteral("info"), toInfoJson(thumbnail));
+ o.insert(QStringLiteral("geo_uri"), geoUri);
+ o.insert(QStringLiteral("info"), toInfoJson(thumbnail));
}
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
index 03a51328..6968ad70 100644
--- a/lib/events/roommessageevent.h
+++ b/lib/events/roommessageevent.h
@@ -136,7 +136,7 @@ namespace EventContent {
Omittable<EventRelation> relatesTo;
protected:
- void fillJson(QJsonObject* json) const override;
+ void fillJson(QJsonObject& json) const override;
};
/**
@@ -164,28 +164,25 @@ namespace EventContent {
Thumbnail thumbnail;
protected:
- void fillJson(QJsonObject* o) const override;
+ void fillJson(QJsonObject& o) const override;
};
/**
* A base class for info types that include duration: audio and video
*/
- template <typename ContentT>
- class QUOTIENT_API PlayableContent : public ContentT {
+ template <typename InfoT>
+ class PlayableContent : public UrlBasedContent<InfoT> {
public:
- using ContentT::ContentT;
+ using UrlBasedContent<InfoT>::UrlBasedContent;
PlayableContent(const QJsonObject& json)
- : ContentT(json)
- , duration(ContentT::originalInfoJson["duration"_ls].toInt())
+ : UrlBasedContent<InfoT>(json)
+ , duration(FileInfo::originalInfoJson["duration"_ls].toInt())
{}
protected:
- void fillJson(QJsonObject* json) const override
+ void fillInfoJson(QJsonObject& infoJson) const override
{
- ContentT::fillJson(json);
- auto infoJson = json->take("info"_ls).toObject();
infoJson.insert(QStringLiteral("duration"), duration);
- json->insert(QStringLiteral("info"), infoJson);
}
public:
@@ -211,7 +208,7 @@ namespace EventContent {
* - mimeType
* - imageSize
*/
- using VideoContent = PlayableContent<UrlWithThumbnailContent<ImageInfo>>;
+ using VideoContent = PlayableContent<ImageInfo>;
/**
* Content class for m.audio
@@ -224,7 +221,13 @@ namespace EventContent {
* - 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<UrlBasedContent<FileInfo>>;
+ using AudioContent = PlayableContent<FileInfo>;
} // namespace EventContent
} // namespace Quotient
diff --git a/lib/events/roompowerlevelsevent.cpp b/lib/events/roompowerlevelsevent.cpp
index 8d262ddf..84a31d55 100644
--- a/lib/events/roompowerlevelsevent.cpp
+++ b/lib/events/roompowerlevelsevent.cpp
@@ -3,8 +3,6 @@
#include "roompowerlevelsevent.h"
-#include <QJsonDocument>
-
using namespace Quotient;
PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
@@ -21,17 +19,21 @@ PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
{
}
-void PowerLevelsEventContent::fillJson(QJsonObject* o) const {
- o->insert(QStringLiteral("invite"), invite);
- o->insert(QStringLiteral("kick"), kick);
- o->insert(QStringLiteral("ban"), ban);
- o->insert(QStringLiteral("redact"), redact);
- o->insert(QStringLiteral("events"), Quotient::toJson(events));
- o->insert(QStringLiteral("events_default"), eventsDefault);
- o->insert(QStringLiteral("state_default"), stateDefault);
- o->insert(QStringLiteral("users"), Quotient::toJson(users));
- o->insert(QStringLiteral("users_default"), usersDefault);
- o->insert(QStringLiteral("notifications"), QJsonObject{{"room", notifications.room}});
+QJsonObject PowerLevelsEventContent::toJson() const
+{
+ QJsonObject o;
+ o.insert(QStringLiteral("invite"), invite);
+ o.insert(QStringLiteral("kick"), kick);
+ o.insert(QStringLiteral("ban"), ban);
+ o.insert(QStringLiteral("redact"), redact);
+ o.insert(QStringLiteral("events"), Quotient::toJson(events));
+ o.insert(QStringLiteral("events_default"), eventsDefault);
+ o.insert(QStringLiteral("state_default"), stateDefault);
+ o.insert(QStringLiteral("users"), Quotient::toJson(users));
+ o.insert(QStringLiteral("users_default"), usersDefault);
+ o.insert(QStringLiteral("notifications"),
+ QJsonObject { { "room", notifications.room } });
+ return o;
}
int RoomPowerLevelsEvent::powerLevelForEvent(const QString &eventId) const {
diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h
index 415cc814..a1638a27 100644
--- a/lib/events/roompowerlevelsevent.h
+++ b/lib/events/roompowerlevelsevent.h
@@ -3,17 +3,16 @@
#pragma once
-#include "eventcontent.h"
#include "stateevent.h"
namespace Quotient {
-class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base {
-public:
+struct QUOTIENT_API PowerLevelsEventContent {
struct Notifications {
int room;
};
explicit PowerLevelsEventContent(const QJsonObject& json);
+ QJsonObject toJson() const;
int invite;
int kick;
@@ -29,9 +28,6 @@ public:
int usersDefault;
Notifications notifications;
-
-protected:
- void fillJson(QJsonObject* o) const override;
};
class QUOTIENT_API RoomPowerLevelsEvent
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index 9610574b..33221542 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -4,63 +4,47 @@
#pragma once
#include "stateevent.h"
+#include "single_key_value.h"
namespace Quotient {
-namespace EventContent {
- template <typename T>
- struct SimpleContent {
- using value_type = T;
-
- // The constructor is templated to enable perfect forwarding
- template <typename TT>
- SimpleContent(QString keyName, TT&& value)
- : value(std::forward<TT>(value)), key(std::move(keyName))
- {}
- SimpleContent(const QJsonObject& json, QString keyName)
- : value(fromJson<T>(json[keyName])), key(std::move(keyName))
- {}
- QJsonObject toJson() const
- {
- return { { key, Quotient::toJson(value) } };
- }
-
- T value;
- const QString key;
- };
-} // namespace EventContent
-
-#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
- class QUOTIENT_API _Name \
- : public StateEvent<EventContent::SimpleContent<_ValueType>> { \
- public: \
- using value_type = content_type::value_type; \
- DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- template <typename T> \
- explicit _Name(T&& value) \
- : StateEvent(typeId(), matrixTypeId(), QString(), \
- QStringLiteral(#_ContentKey), std::forward<T>(value)) \
- {} \
- explicit _Name(QJsonObject obj) \
- : StateEvent(typeId(), std::move(obj), \
- QStringLiteral(#_ContentKey)) \
- {} \
- auto _ContentKey() const { return content().value; } \
- }; \
- REGISTER_EVENT_TYPE(_Name) \
+#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
+ constexpr auto _Name##Key = #_ContentKey##_ls; \
+ class QUOTIENT_API _Name \
+ : public StateEvent< \
+ EventContent::SingleKeyValue<_ValueType, &_Name##Key>> { \
+ public: \
+ using value_type = _ValueType; \
+ DEFINE_EVENT_TYPEID(_TypeId, _Name) \
+ template <typename T> \
+ explicit _Name(T&& value) \
+ : StateEvent(TypeId, matrixTypeId(), QString(), \
+ std::forward<T>(value)) \
+ {} \
+ explicit _Name(QJsonObject obj) \
+ : StateEvent(TypeId, std::move(obj)) \
+ {} \
+ auto _ContentKey() const { return content().value; } \
+ }; \
+ REGISTER_EVENT_TYPE(_Name) \
// End of macro
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)
+DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages",
+ QStringList, pinnedEvents)
+constexpr auto RoomAliasesEventKey = "aliases"_ls;
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>> {
+RoomAliasesEvent
+ : public StateEvent<
+ EventContent::SingleKeyValue<QStringList, &RoomAliasesEventKey>>
+{
public:
DEFINE_EVENT_TYPEID("m.room.aliases", RoomAliasesEvent)
explicit RoomAliasesEvent(const QJsonObject& obj)
- : StateEvent(typeId(), obj, QStringLiteral("aliases"))
+ : StateEvent(typeId(), obj)
{}
QString server() const { return stateKey(); }
QStringList aliases() const { return content().value; }
diff --git a/lib/events/single_key_value.h b/lib/events/single_key_value.h
new file mode 100644
index 00000000..75ca8cd2
--- /dev/null
+++ b/lib/events/single_key_value.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "converters.h"
+
+namespace Quotient {
+
+namespace EventContent {
+ template <typename T, const QLatin1String* KeyStr>
+ struct SingleKeyValue {
+ T value;
+ };
+} // namespace EventContent
+
+template <typename ValueT, const QLatin1String* KeyStr>
+struct JsonConverter<EventContent::SingleKeyValue<ValueT, KeyStr>> {
+ using content_type = EventContent::SingleKeyValue<ValueT, KeyStr>;
+ static content_type load(const QJsonValue& jv)
+ {
+ return { fromJson<ValueT>(jv.toObject().value(JsonKey)) };
+ }
+ static QJsonObject dump(const content_type& c)
+ {
+ return { { JsonKey, toJson(c.value) } };
+ }
+ static inline const auto JsonKey = toSnakeCase(*KeyStr);
+};
+} // namespace Quotient
diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp
index e53d47d4..c343e37f 100644
--- a/lib/events/stateevent.cpp
+++ b/lib/events/stateevent.cpp
@@ -6,9 +6,9 @@
using namespace Quotient;
StateEventBase::StateEventBase(Type type, const QJsonObject& json)
- : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json)
+ : RoomEvent(json.contains(StateKeyKeyL) ? type : UnknownEventTypeId, json)
{
- if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL))
+ 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";
}
@@ -16,7 +16,7 @@ StateEventBase::StateEventBase(Type type, const QJsonObject& json)
StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType,
const QString& stateKey,
const QJsonObject& contentJson)
- : RoomEvent(type, basicStateEventJson(matrixType, contentJson, stateKey))
+ : RoomEvent(type, basicJson(matrixType, contentJson, stateKey))
{}
bool StateEventBase::repeatsState() const
diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h
index 88da68f8..343e87a5 100644
--- a/lib/events/stateevent.h
+++ b/lib/events/stateevent.h
@@ -7,16 +7,6 @@
namespace Quotient {
-/// Make a minimal correct Matrix state event JSON
-inline QJsonObject basicStateEventJson(const QString& matrixTypeId,
- const QJsonObject& content,
- const QString& stateKey = {})
-{
- return { { TypeKey, matrixTypeId },
- { StateKeyKey, stateKey },
- { ContentKey, content } };
-}
-
class QUOTIENT_API StateEventBase : public RoomEvent {
public:
static inline EventFactory<StateEventBase> factory { "StateEvent" };
@@ -27,6 +17,16 @@ public:
const QJsonObject& contentJson = {});
~StateEventBase() override = default;
+ //! Make a minimal correct Matrix state event JSON
+ static QJsonObject basicJson(const QString& matrixTypeId,
+ const QJsonObject& content,
+ const QString& stateKey = {})
+ {
+ return { { TypeKey, matrixTypeId },
+ { StateKeyKey, stateKey },
+ { ContentKey, content } };
+ }
+
bool isStateEvent() const override { return true; }
QString replacedState() const;
void dumpTo(QDebug dbg) const override;
@@ -36,6 +36,14 @@ public:
using StateEventPtr = event_ptr_tt<StateEventBase>;
using StateEvents = EventsArray<StateEventBase>;
+[[deprecated("Use StateEventBase::basicJson() instead")]]
+inline QJsonObject basicStateEventJson(const QString& matrixTypeId,
+ const QJsonObject& content,
+ const QString& stateKey = {})
+{
+ return StateEventBase::basicJson(matrixTypeId, content, stateKey);
+}
+
//! \brief Override RoomEvent factory with that from StateEventBase if JSON has
//! stateKey
//!
@@ -64,7 +72,7 @@ inline bool is<StateEventBase>(const Event& e)
* \sa
* https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events
*/
-using StateEventKey = QPair<QString, QString>;
+using StateEventKey = std::pair<QString, QString>;
template <typename ContentT>
struct Prev {
@@ -72,7 +80,7 @@ struct Prev {
explicit Prev(const QJsonObject& unsignedJson,
ContentParamTs&&... contentParams)
: senderId(unsignedJson.value("prev_sender"_ls).toString())
- , content(unsignedJson.value(PrevContentKeyL).toObject(),
+ , content(fromJson<ContentT>(unsignedJson.value(PrevContentKeyL)),
std::forward<ContentParamTs>(contentParams)...)
{}
@@ -89,7 +97,8 @@ public:
explicit StateEvent(Type type, const QJsonObject& fullJson,
ContentParamTs&&... contentParams)
: StateEventBase(type, fullJson)
- , _content(contentJson(), std::forward<ContentParamTs>(contentParams)...)
+ , _content(fromJson<ContentT>(contentJson()),
+ std::forward<ContentParamTs>(contentParams)...)
{
const auto& unsignedData = unsignedJson();
if (unsignedData.contains(PrevContentKeyL))
@@ -101,9 +110,9 @@ public:
const QString& stateKey,
ContentParamTs&&... contentParams)
: StateEventBase(type, matrixType, stateKey)
- , _content(std::forward<ContentParamTs>(contentParams)...)
+ , _content{std::forward<ContentParamTs>(contentParams)...}
{
- editJson().insert(ContentKey, _content.toJson());
+ editJson().insert(ContentKey, toJson(_content));
}
const ContentT& content() const { return _content; }
@@ -111,7 +120,7 @@ public:
void editContent(VisitorT&& visitor)
{
visitor(_content);
- editJson()[ContentKeyL] = _content.toJson();
+ editJson()[ContentKeyL] = toJson(_content);
}
const ContentT* prevContent() const
{
diff --git a/lib/room.cpp b/lib/room.cpp
index 183e242a..4d9f952c 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -3064,15 +3064,16 @@ Room::Changes Room::processEphemeralEvent(EventPtr&& event)
QElapsedTimer et;
et.start();
if (auto* evt = eventCast<TypingEvent>(event)) {
+ const auto& users = evt->users();
d->usersTyping.clear();
- d->usersTyping.reserve(evt->users().size()); // Assume all are members
- for (const auto& userId : evt->users())
+ d->usersTyping.reserve(users.size()); // Assume all are members
+ for (const auto& userId : users)
if (isMember(userId))
d->usersTyping.append(user(userId));
- if (evt->users().size() > 3 || et.nsecsElapsed() >= profilerMinNsecs())
+ if (users.size() > 3 || et.nsecsElapsed() >= profilerMinNsecs())
qCDebug(PROFILER)
- << "Processing typing events from" << evt->users().size()
+ << "Processing typing events from" << users.size()
<< "user(s) in" << objectName() << "took" << et;
emit typingChanged();
}
diff --git a/lib/room.h b/lib/room.h
index 6ba7feac..7e53aed0 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -758,7 +758,8 @@ public:
[[deprecated("Use currentState().get() instead; "
"make sure to check its result for nullptrs")]] //
const Quotient::StateEventBase*
- getCurrentState(const QString& evtType, const QString& stateKey = {}) const;
+ getCurrentState(const QString& evtType,
+ const QString& stateKey = {}) const;
/// Get a state event with the given event type and state key
/*! This is a typesafe overload that accepts a C++ event type instead of
diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp
index 792faabd..b14442b9 100644
--- a/quotest/quotest.cpp
+++ b/quotest/quotest.cpp
@@ -523,23 +523,7 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest,
return true;
}
-class CustomEvent : public RoomEvent {
-public:
- DEFINE_EVENT_TYPEID("quotest.custom", CustomEvent)
-
- CustomEvent(const QJsonObject& jo)
- : RoomEvent(typeId(), jo)
- {}
- CustomEvent(int testValue)
- : RoomEvent(typeId(),
- basicEventJson(matrixTypeId(),
- QJsonObject { { "testValue"_ls,
- toJson(testValue) } }))
- {}
-
- auto testValue() const { return contentPart<int>("testValue"_ls); }
-};
-REGISTER_EVENT_TYPE(CustomEvent)
+DEFINE_SIMPLE_EVENT(CustomEvent, RoomEvent, "quotest.custom", int, testValue)
TEST_IMPL(sendCustomEvent)
{