From 2a51e977575387001eb025e09eba77d9afbd9981 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Jun 2017 16:48:27 +0900 Subject: Enable creation of RoomMessageEvents RoomMessageEvent and MessageContentEvent::* classes have been massively overhauled to enable creation of m.room.message events locally instead of from JSON. --- events/roommessageevent.cpp | 180 +++++++++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 52 deletions(-) (limited to 'events/roommessageevent.cpp') diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 19da8827..7697c5c3 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -25,37 +25,56 @@ using namespace QMatrixClient; using namespace MessageEventContent; -using ContentPair = std::pair; +using MsgType = RoomMessageEvent::MsgType; -template -ContentPair make(const QJsonObject& json) +template +Base* make(const QJsonObject& json) { - return { EnumType, new ContentT(json) }; + return new ContentT(json); } -ContentPair makeVideo(const QJsonObject& json) +struct MsgTypeDesc { - auto c = new VideoContent(json); - // Only for m.video, the spec puts a thumbnail inside "info" JSON key. Once - // this is fixed, VideoContent creation will switch to make<>(). - const QJsonObject infoJson = json["info"].toObject(); - if (infoJson.contains("thumbnail_url")) - { - c->thumbnail = ImageInfo(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()); - } - return { CType::Video, c }; + QString jsonType; + MsgType enumType; + Base* (*maker)(const QJsonObject&); }; -ContentPair makeUnknown(const QJsonObject& json) +const std::vector msgTypes = + { { QStringLiteral("m.text"), MsgType::Text, make } + , { QStringLiteral("m.emote"), MsgType::Emote, make } + , { QStringLiteral("m.notice"), MsgType::Notice, make } + , { QStringLiteral("m.image"), MsgType::Image, make } + , { QStringLiteral("m.file"), MsgType::File, make } + , { QStringLiteral("m.location"), MsgType::Location, make } + , { QStringLiteral("m.video"), MsgType::Video, make } + , { QStringLiteral("m.audio"), MsgType::Audio, make } + }; + +QJsonValue msgTypeToJson(MsgType enumType) +{ + auto it = std::find_if(msgTypes.begin(), msgTypes.end(), + [=](const MsgTypeDesc& mtd) { return mtd.enumType == enumType; }); + if (it != msgTypes.end()) + return it->jsonType; + + qCCritical(EVENTS) << "Unknown msgtype:" << enumType; + return {}; +} + +MsgType jsonToMsgType(const QString& jsonType) { - qCDebug(EVENTS) << "RoomMessageEvent: couldn't resolve msgtype, JSON follows:"; - qCDebug(EVENTS) << json; - return { CType::Unknown, new Base() }; + auto it = std::find_if(msgTypes.begin(), msgTypes.end(), + [=](const MsgTypeDesc& mtd) { return mtd.jsonType == jsonType; }); + if (it != msgTypes.end()) + return it->enumType; + + qCCritical(EVENTS) << "Unknown msgtype:" << jsonType; + return {}; } RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomMessage, obj), _msgtype(CType::Unknown) + : RoomEvent(Type::RoomMessage, obj), _msgtype(MsgType::Unknown) , _content(nullptr) { const QJsonObject content = contentJson(); @@ -63,19 +82,20 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) { _plainBody = content["body"].toString(); - auto factory = lookup(content["msgtype"].toString(), - "m.text", &make, - "m.emote", &make, - "m.notice", &make, - "m.image", &make, - "m.file", &make, - "m.location", &make, - "m.video", &makeVideo, - "m.audio", &make, - // Insert new message types before this line - &makeUnknown - ); - std::tie(_msgtype, _content) = factory(content); + auto msgtype = content["msgtype"].toString(); + for (auto mt: msgTypes) + if (mt.jsonType == msgtype) + { + _msgtype = mt.enumType; + _content.reset(mt.maker(content)); + } + + if (_msgtype == MsgType::Unknown) + { + qCDebug(EVENTS) << "RoomMessageEvent: unknown msgtype" << msgtype + << ", full content dump follows"; + qCDebug(EVENTS) << formatJson << content; + } } else { @@ -84,12 +104,25 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) } } -RoomMessageEvent::~RoomMessageEvent() +QJsonObject RoomMessageEvent::toJson() const { - if (_content) - delete _content; + QJsonObject obj = _content ? _content->toJson() : QJsonObject(); + obj.insert("msgtype", msgTypeToJson(msgtype())); + obj.insert("body", plainBody()); + return obj; } +QJsonObject InfoBase::toInfoJson() const +{ + QJsonObject info; + fillInfoJson(&info); + return info; +} + +TextContent::TextContent(const QString& text, const QString& contentType) + : mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(text) +{ } + TextContent::TextContent(const QJsonObject& json) { QMimeDatabase db; @@ -108,34 +141,77 @@ TextContent::TextContent(const QJsonObject& json) } } -FileInfo::FileInfo(QUrl u, const QJsonObject& infoJson, QString originalFilename) - : url(u) - , fileSize(infoJson["size"].toInt()) - , mimetype(QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString())) +void TextContent::fillJson(QJsonObject* json) const +{ + Q_ASSERT(json); + json->insert("format", QStringLiteral("org.matrix.custom.html")); + json->insert("formatted_body", body); +} + +FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, + const QString& originalFilename) + : url(u), payloadSize(payloadSize), mimetype(mimeType) , originalName(originalFilename) +{ } + +FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename) + : FileInfo(u, infoJson["size"].toInt(), + QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), + originalFilename) { if (!mimetype.isValid()) mimetype = QMimeDatabase().mimeTypeForData(QByteArray()); } -ImageInfo::ImageInfo(QUrl u, const QJsonObject& infoJson) - : FileInfo(u, infoJson) - , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) +void FileInfo::fillInfoJson(QJsonObject* infoJson) const +{ + Q_ASSERT(infoJson); + infoJson->insert("size", payloadSize); + infoJson->insert("mimetype", mimetype.name()); +} + +void FileInfo::fillJson(QJsonObject* json) const +{ + Q_ASSERT(json); + json->insert("url", url.toString()); + if (!originalName.isEmpty()) + json->insert("filename", originalName); + json->insert("info", toInfoJson()); +} + +LocationContent::LocationContent(const QString& geoUri, + const ImageInfo<>& thumbnail) + : Thumbnailed<>(thumbnail), geoUri(geoUri) { } LocationContent::LocationContent(const QJsonObject& json) - : geoUri(json["geo_uri"].toString()) - , thumbnail(json["thumbnail_url"].toString(), - json["thumbnail_info"].toObject()) + : Thumbnailed<>(json["info"].toObject()) + , geoUri(json["geo_uri"].toString()) { } -VideoInfo::VideoInfo(QUrl u, const QJsonObject& infoJson) - : FileInfo(u, infoJson) - , duration(infoJson["duration"].toInt()) - , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt()) +void LocationContent::fillJson(QJsonObject* o) const +{ + Q_ASSERT(o); + o->insert("geo_uri", geoUri); + o->insert("info", Thumbnailed::toInfoJson()); +} + +PlayableInfo::PlayableInfo(const QUrl& u, int fileSize, + const QMimeType& mimeType, int duration, + const QString& originalFilename) + : FileInfo(u, fileSize, mimeType, originalFilename) + , duration(duration) { } -AudioInfo::AudioInfo(QUrl u, const QJsonObject& infoJson) - : FileInfo(u, infoJson) +PlayableInfo::PlayableInfo(const QUrl& u, const QJsonObject& infoJson, + const QString& originalFilename) + : FileInfo(u, infoJson, originalFilename) , duration(infoJson["duration"].toInt()) { } + +void PlayableInfo::fillInfoJson(QJsonObject* infoJson) const +{ + FileInfo::fillInfoJson(infoJson); + infoJson->insert("duration", duration); +} -- cgit v1.2.3 From 631b322ba2b7e7ffc44b2a1ab653b851be86fd33 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Jun 2017 16:50:24 +0900 Subject: MessageEventContent: generalise mimeType mimeType is relevant to most of the content types, and at the same time getting a MIME type in a generic way is handy for clients to uniformly detect whether they can display the content and what renderer to use for it. --- events/roommessageevent.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) (limited to 'events/roommessageevent.cpp') diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 7697c5c3..ccaa6226 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -92,8 +92,8 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) if (_msgtype == MsgType::Unknown) { - qCDebug(EVENTS) << "RoomMessageEvent: unknown msgtype" << msgtype - << ", full content dump follows"; + qCDebug(EVENTS) << "RoomMessageEvent: couldn't load content," + << " full content dump follows"; qCDebug(EVENTS) << formatJson << content; } } @@ -104,6 +104,12 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) } } +QMimeType RoomMessageEvent::mimeType() const +{ + return _content ? _content->mimeType : + QMimeDatabase().mimeTypeForName("text/plain"); +} + QJsonObject RoomMessageEvent::toJson() const { QJsonObject obj = _content ? _content->toJson() : QJsonObject(); @@ -112,6 +118,13 @@ QJsonObject RoomMessageEvent::toJson() const return obj; } +QJsonObject Base::toJson() const +{ + QJsonObject o; + fillJson(&o); + return o; +} + QJsonObject InfoBase::toInfoJson() const { QJsonObject info; @@ -120,7 +133,7 @@ QJsonObject InfoBase::toInfoJson() const } TextContent::TextContent(const QString& text, const QString& contentType) - : mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(text) + : Base(QMimeDatabase().mimeTypeForName(contentType)), body(text) { } TextContent::TextContent(const QJsonObject& json) @@ -136,8 +149,8 @@ TextContent::TextContent(const QJsonObject& json) } else { // Falling back to plain text, as there's no standard way to describe // rich text in messages. - body = json["body"].toString(); mimeType = db.mimeTypeForName("text/plain"); + body = json["body"].toString(); } } @@ -150,7 +163,7 @@ void TextContent::fillJson(QJsonObject* json) const FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType, const QString& originalFilename) - : url(u), payloadSize(payloadSize), mimetype(mimeType) + : InfoBase(mimeType), url(u), payloadSize(payloadSize) , originalName(originalFilename) { } @@ -160,15 +173,15 @@ FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson, QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()), originalFilename) { - if (!mimetype.isValid()) - mimetype = QMimeDatabase().mimeTypeForData(QByteArray()); + if (!mimeType.isValid()) + mimeType = QMimeDatabase().mimeTypeForData(QByteArray()); } void FileInfo::fillInfoJson(QJsonObject* infoJson) const { Q_ASSERT(infoJson); infoJson->insert("size", payloadSize); - infoJson->insert("mimetype", mimetype.name()); + infoJson->insert("mimetype", mimeType.name()); } void FileInfo::fillJson(QJsonObject* json) const -- cgit v1.2.3 From 7d745dca7bdd328fd96acdf53f15f4a5cd7cf484 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 22 Jun 2017 17:36:23 +0900 Subject: RoomMessageEvent: Simplify constructors, use QString msgType internally QString msgType allows non-standard types (we don't want to restrict clients to types from the spec) --- events/roommessageevent.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'events/roommessageevent.cpp') diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index ccaa6226..179eac77 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -51,7 +51,7 @@ const std::vector msgTypes = , { QStringLiteral("m.audio"), MsgType::Audio, make } }; -QJsonValue msgTypeToJson(MsgType enumType) +QString msgTypeToJson(MsgType enumType) { auto it = std::find_if(msgTypes.begin(), msgTypes.end(), [=](const MsgTypeDesc& mtd) { return mtd.enumType == enumType; }); @@ -73,28 +73,29 @@ MsgType jsonToMsgType(const QString& jsonType) return {}; } +RoomMessageEvent::RoomMessageEvent(const QString& plainBody, + MsgType msgType, Base* content) + : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content) +{ } + RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) - : RoomEvent(Type::RoomMessage, obj), _msgtype(MsgType::Unknown) - , _content(nullptr) + : RoomEvent(Type::RoomMessage, obj), _content(nullptr) { const QJsonObject content = contentJson(); if ( content.contains("msgtype") && content.contains("body") ) { _plainBody = content["body"].toString(); - auto msgtype = content["msgtype"].toString(); + _msgtype = content["msgtype"].toString(); for (auto mt: msgTypes) - if (mt.jsonType == msgtype) - { - _msgtype = mt.enumType; + if (mt.jsonType == _msgtype) _content.reset(mt.maker(content)); - } - if (_msgtype == MsgType::Unknown) + if (!_content) { - qCDebug(EVENTS) << "RoomMessageEvent: couldn't load content," - << " full content dump follows"; - qCDebug(EVENTS) << formatJson << content; + qCWarning(EVENTS) << "RoomMessageEvent: couldn't load content," + << " full content dump follows"; + qCWarning(EVENTS) << formatJson << content; } } else @@ -104,6 +105,11 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) } } +RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const +{ + return jsonToMsgType(_msgtype); +} + QMimeType RoomMessageEvent::mimeType() const { return _content ? _content->mimeType : -- cgit v1.2.3