From b9f4b655273481e64d7d7ead6a30dbf85a901063 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sat, 13 Jan 2018 19:42:37 +0900 Subject: Refactor EventContent; allow to easily check files out of message events The whole inheritance/templating structure has been considerably simplified by using a trick with mixin classes; thanks to that, *Info classes are no more templated, they are just mixed together by the almighty UrlBasedContent<> template (but the same can easily be done outside of it, as LocationContent implementation shows). RoomMessageEvent has gained hasFileContent(); it's also possible to easily get a FileInfo core object just by calling msgEvent->content()->fileInfo(). --- events/eventcontent.cpp | 51 ++++++++--- events/eventcontent.h | 212 ++++++++++++++++++++------------------------ events/roommessageevent.cpp | 43 +++++---- events/roommessageevent.h | 34 +++---- 4 files changed, 175 insertions(+), 165 deletions(-) (limited to 'events') diff --git a/events/eventcontent.cpp b/events/eventcontent.cpp index dcbccf08..271669e2 100644 --- a/events/eventcontent.cpp +++ b/events/eventcontent.cpp @@ -30,27 +30,21 @@ QJsonObject Base::toJson() const return o; } -QJsonObject InfoBase::toInfoJson() const -{ - QJsonObject info; - fillInfoJson(&info); - return info; -} - -void InfoBase::fillInfoJson(QJsonObject*) const { } - FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, const QString& originalFilename) - : InfoBase(mimeType), url(u), payloadSize(payloadSize) + : mimeType(mimeType), url(u), payloadSize(payloadSize) , originalName(originalFilename) { } FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename) - : FileInfo(u, infoJson["size"].toInt(), - QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), - originalFilename) + : originalInfoJson(infoJson) + , mimeType(QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString())) + , url(u) + , payloadSize(infoJson["size"].toInt()) + , originalName(originalFilename) { + originalInfoJson.insert("mediaId", url.authority() + url.path()); if (!mimeType.isValid()) mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } @@ -61,3 +55,34 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert("size", payloadSize); infoJson->insert("mimetype", mimeType.name()); } + +ImageInfo::ImageInfo(const QUrl& u, int fileSize, QMimeType mimeType, + const QSize& imageSize) + : FileInfo(u, fileSize, mimeType), imageSize(imageSize) +{ } + +ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename) + : FileInfo(u, infoJson, originalFilename) + , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) +{ } + +void ImageInfo::fillInfoJson(QJsonObject* infoJson) const +{ + FileInfo::fillInfoJson(infoJson); + infoJson->insert("w", imageSize.width()); + infoJson->insert("h", imageSize.height()); +} + +WithThumbnail::WithThumbnail(const QJsonObject& infoJson) + : thumbnail(infoJson["thumbnail_url"].toString(), + infoJson["thumbnail_info"].toObject()) +{ } + +void WithThumbnail::fillInfoJson(QJsonObject* infoJson) const +{ + infoJson->insert("thumbnail_url", thumbnail.url.toString()); + QJsonObject thumbnailInfoJson; + thumbnail.fillInfoJson(&thumbnailInfoJson); + infoJson->insert("thumbnail_info", thumbnailInfoJson); +} diff --git a/events/eventcontent.h b/events/eventcontent.h index 60437995..b37dc923 100644 --- a/events/eventcontent.h +++ b/events/eventcontent.h @@ -23,7 +23,6 @@ #include "converters.h" -#include #include #include #include @@ -40,28 +39,23 @@ namespace QMatrixClient * 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. fillJson() should only fill the main JSON object - * but not the "info" subobject if it exists for a certain content type; - * use \p InfoBase to de/serialize "info" parts with an optional URL - * on the top level. + * from plain data. */ class Base { public: + explicit Base (const QJsonObject& o = {}) : originalJson(o) { } virtual ~Base() = default; QJsonObject toJson() const; + public: + QJsonObject originalJson; + protected: virtual void fillJson(QJsonObject* o) const = 0; }; - class TypedBase: public Base - { - public: - virtual QMimeType type() const = 0; - }; - template class SimpleContent: public Base { @@ -74,10 +68,12 @@ namespace QMatrixClient : value(std::forward(value)), key(std::move(keyName)) { } SimpleContent(const QJsonObject& json, QString keyName) - : value(QMatrixClient::fromJson(json[keyName])) + : Base(json) + , value(QMatrixClient::fromJson(json[keyName])) , key(std::move(keyName)) { } + public: T value; protected: @@ -91,44 +87,36 @@ namespace QMatrixClient } }; - /** - * A base class for content types that have an "info" object in their - * JSON representation - * - * These include most multimedia types currently in the CS API spec. - * Derived classes should override fillInfoJson() to fill the "info" - * subobject, BUT NOT the main JSON object. Most but not all "info" - * classes (specifically, those deriving from FileInfo) should also - * have a constructor that accepts two parameters, QUrl and QJsonObject, - * in order to load the URL+info part from JSON. - */ - class InfoBase - { - public: - virtual ~InfoBase() = default; - - QJsonObject toInfoJson() const; - - QMimeType mimeType; - - protected: - InfoBase() = default; - explicit InfoBase(const QMimeType& type) : mimeType(type) { } - - virtual void fillInfoJson(QJsonObject* /*infoJson*/) const = 0; - }; - // The below structures fairly follow CS spec 11.2.1.6. The overall // set of attributes for each content types is a superset of the spec // but specific aggregation structure is altered. See doc comments to // each type for the list of available attributes. + // A quick classes inheritance structure follows: + // FileInfo + // FileContent : UrlBasedContent + // AudioContent : UrlBasedContent + // ImageInfo : FileInfo + imageSize attribute + // ImageContent : UrlBasedContent + // VideoContent : UrlBasedContent + /** - * Base class for content types that consist of a URL along with - * additional information. Most of message types except textual fall - * under this category. + * 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. */ - class FileInfo: public InfoBase + class FileInfo { public: explicit FileInfo(const QUrl& u, int payloadSize = -1, @@ -137,111 +125,101 @@ namespace QMatrixClient FileInfo(const QUrl& u, const QJsonObject& infoJson, const QString& originalFilename = {}); + 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 + */ + QString mediaId() const { return url.authority() + url.path(); } + + public: + QJsonObject originalInfoJson; + QMimeType mimeType; QUrl url; int payloadSize; QString originalName; - - protected: - void fillInfoJson(QJsonObject* infoJson) const override; }; /** - * A base class for image info types: image, thumbnail, video - * - * \tparam InfoT base info class; should derive from \p InfoBase + * A content info class for image content types: image, thumbnail, video */ - template - class ImageInfo : public InfoT + class ImageInfo : public FileInfo { public: explicit ImageInfo(const QUrl& u, int fileSize = -1, QMimeType mimeType = {}, - const QSize& imageSize = {}) - : InfoT(u, fileSize, mimeType), imageSize(imageSize) - { } + const QSize& imageSize = {}); ImageInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) - { } + const QString& originalFilename = {}); - QSize imageSize; + void fillInfoJson(QJsonObject* infoJson) const; - protected: - void fillInfoJson(QJsonObject* infoJson) const override - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("w", imageSize.width()); - infoJson->insert("h", imageSize.height()); - } + public: + QSize imageSize; }; /** - * A base class for an info type that carries a thumbnail + * A mixin class for an info type that carries a thumbnail * - * This class decorates the underlying type, adding ability to save/load - * a thumbnail to/from "info" subobject of the JSON representation of - * event content; namely, "info/thumbnail_url" and "info/thumbnail_info" - * fields are used. - * - * \tparam InfoT base info class; should derive from \p InfoBase + * 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. */ - template - class Thumbnailed : public InfoT + class WithThumbnail { public: - template - explicit Thumbnailed(const ImageInfo<>& thumbnail, - ArgTs&&... infoArgs) - : InfoT(std::forward(infoArgs)...) - , thumbnail(thumbnail) - { } - - explicit Thumbnailed(const QJsonObject& infoJson) - : thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) + WithThumbnail(const QJsonObject& infoJson); + WithThumbnail(const ImageInfo& info) + : thumbnail(info) { } - Thumbnailed(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}) - : InfoT(u, infoJson, originalFilename) - , thumbnail(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()) - { } + /** + * Writes thumbnail information to "thumbnail_info" subobject + * and thumbnail URL to "thumbnail_url" node inside "info". + */ + void fillInfoJson(QJsonObject* infoJson) const; - ImageInfo<> thumbnail; + public: + ImageInfo thumbnail; + }; - protected: - void fillInfoJson(QJsonObject* infoJson) const override - { - InfoT::fillInfoJson(infoJson); - infoJson->insert("thumbnail_url", thumbnail.url.toString()); - infoJson->insert("thumbnail_info", thumbnail.toInfoJson()); - } + class TypedBase: public Base + { + public: + explicit TypedBase(const QJsonObject& o = {}) : Base(o) { } + virtual QMimeType type() const = 0; + virtual const FileInfo* fileInfo() const { return nullptr; } }; /** - * One more facility base class for content types that have a URL and - * additional info + * A base class for content types that have a URL and additional info * - * Types that derive from UrlWith take "url" and, optionally, - * "filename" values from the top-level JSON object and the rest of - * information from the "info" subobject. + * 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; should derive from \p FileInfo or - * provide a constructor with a compatible signature + * \tparam InfoT base info class + * \tparam InfoMixinTs... additional info mixin classes (e.g. WithThumbnail) */ - template // InfoT : public FileInfo - class UrlWith : public TypedBase, public InfoT + template + class UrlBasedContent : + public TypedBase, public InfoT, public InfoMixinTs... { public: - using InfoT::InfoT; - explicit UrlWith(const QJsonObject& json) - : InfoT(json["url"].toString(), json["info"].toObject(), + explicit UrlBasedContent(const QJsonObject& json) + : TypedBase(json) + , InfoT(json["url"].toString(), json["info"].toObject(), json["filename"].toString()) + , InfoMixinTs(InfoT::originalInfoJson)... { } QMimeType type() const override { return InfoT::mimeType; } + const FileInfo* fileInfo() const override { return this; } protected: void fillJson(QJsonObject* json) const override @@ -250,7 +228,13 @@ namespace QMatrixClient json->insert("url", InfoT::url.toString()); if (!InfoT::originalName.isEmpty()) json->insert("filename", InfoT::originalName); - json->insert("info", InfoT::toInfoJson()); + QJsonObject infoJson; + InfoT::fillInfoJson(&infoJson); + // http://en.cppreference.com/w/cpp/language/parameter_pack#Brace-enclosed_initializers + // Looking forward to C++17 and its folding awesomeness. + int d[] = { (InfoMixinTs::fillInfoJson(&infoJson), 0)... }; + Q_UNUSED(d); + json->insert("info", infoJson); } }; @@ -272,7 +256,7 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using ImageContent = UrlWith>>; + using ImageContent = UrlBasedContent; /** * Content class for m.file @@ -290,6 +274,6 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize (QSize for "h" and "w" in JSON) */ - using FileContent = UrlWith>; + using FileContent = UrlBasedContent; } // namespace EventContent } // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index bc41abf6..20e81564 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -116,6 +116,11 @@ QMimeType RoomMessageEvent::mimeType() const QMimeDatabase().mimeTypeForName("text/plain"); } +bool RoomMessageEvent::hasFileContent() const +{ + return content() && content()->fileInfo(); +} + QJsonObject RoomMessageEvent::toJson() const { QJsonObject obj = _content ? _content->toJson() : QJsonObject(); @@ -153,43 +158,35 @@ void TextContent::fillJson(QJsonObject* json) const json->insert("formatted_body", body); } -LocationContent::LocationContent(const QString& geoUri, - const ImageInfo<>& thumbnail) - : Thumbnailed<>(thumbnail), geoUri(geoUri) +LocationContent::LocationContent(const QString& geoUri, const ImageInfo& thumbnail) + : WithThumbnail(thumbnail), geoUri(geoUri) { } LocationContent::LocationContent(const QJsonObject& json) - : Thumbnailed<>(json["info"].toObject()) + : TypedBase(json) + , WithThumbnail(json["info"].toObject()) , geoUri(json["geo_uri"].toString()) { } -void LocationContent::fillJson(QJsonObject* o) const -{ - Q_ASSERT(o); - o->insert("geo_uri", geoUri); - o->insert("info", Thumbnailed::toInfoJson()); -} - QMimeType LocationContent::type() const { return QMimeDatabase().mimeTypeForData(geoUri.toLatin1()); } -PlayableInfo::PlayableInfo(const QUrl& u, int fileSize, - const QMimeType& mimeType, int duration, - const QString& originalFilename) - : FileInfo(u, fileSize, mimeType, originalFilename) - , duration(duration) -{ } +void LocationContent::fillJson(QJsonObject* o) const +{ + Q_ASSERT(o); + o->insert("geo_uri", geoUri); + QJsonObject infoJson; + WithThumbnail::fillInfoJson(&infoJson); + o->insert("info", infoJson); +} -PlayableInfo::PlayableInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename) - : FileInfo(u, infoJson, originalFilename) - , duration(infoJson["duration"].toInt()) +WithDuration::WithDuration(const QJsonObject& infoJson) + : duration(infoJson["duration"].toInt()) { } -void PlayableInfo::fillInfoJson(QJsonObject* infoJson) const +void WithDuration::fillInfoJson(QJsonObject* infoJson) const { - FileInfo::fillInfoJson(infoJson); infoJson->insert("duration", duration); } diff --git a/events/roommessageevent.h b/events/roommessageevent.h index eef6b657..6b551b76 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -32,6 +32,10 @@ namespace QMatrixClient 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(EventContent::TypedBase* content READ content CONSTANT) public: enum class MsgType { @@ -52,9 +56,10 @@ namespace QMatrixClient MsgType msgtype() const; QString rawMsgtype() const { return _msgtype; } const QString& plainBody() const { return _plainBody; } - const EventContent::TypedBase* content() const + EventContent::TypedBase* content() const { return _content.data(); } QMimeType mimeType() const; + bool hasFileContent() const; QJsonObject toJson() const; @@ -107,15 +112,16 @@ namespace QMatrixClient * - thumbnail.mimeType * - thumbnail.imageSize */ - class LocationContent: public TypedBase, public Thumbnailed<> + class LocationContent: public TypedBase, public WithThumbnail { public: LocationContent(const QString& geoUri, - const ImageInfo<>& thumbnail); + const ImageInfo& thumbnail); explicit LocationContent(const QJsonObject& json); QMimeType type() const override; + public: QString geoUri; protected: @@ -123,21 +129,18 @@ namespace QMatrixClient }; /** - * A base class for "playable" info types: audio and video + * A mixin class for info types that include duration: audio and video */ - class PlayableInfo : public FileInfo + class WithDuration { public: - explicit PlayableInfo(const QUrl& u, int fileSize, - const QMimeType& mimeType, int duration, - const QString& originalFilename = {}); - PlayableInfo(const QUrl& u, const QJsonObject& infoJson, - const QString& originalFilename = {}); + explicit WithDuration(int duration) : duration(duration) { } + WithDuration(const QJsonObject& infoJson); - int duration; + void fillInfoJson(QJsonObject* infoJson) const; - protected: - void fillInfoJson(QJsonObject* infoJson) const override; + public: + int duration; }; /** @@ -159,7 +162,8 @@ namespace QMatrixClient * - mimeType * - imageSize */ - using VideoContent = UrlWith>>; + using VideoContent = + UrlBasedContent; /** * Content class for m.audio @@ -173,6 +177,6 @@ namespace QMatrixClient * - mimeType ("mimetype" in JSON) * - duration */ - using AudioContent = UrlWith; + using AudioContent = UrlBasedContent; } // namespace EventContent } // namespace QMatrixClient -- cgit v1.2.3