aboutsummaryrefslogtreecommitdiff
path: root/lib/events
diff options
context:
space:
mode:
Diffstat (limited to 'lib/events')
-rw-r--r--lib/events/accountdataevents.h35
-rw-r--r--lib/events/event.cpp8
-rw-r--r--lib/events/event.h28
-rw-r--r--lib/events/eventcontent.cpp38
-rw-r--r--lib/events/eventcontent.h28
-rw-r--r--lib/events/eventloader.h11
-rw-r--r--lib/events/roomavatarevent.h3
-rw-r--r--lib/events/roomcreateevent.cpp45
-rw-r--r--lib/events/roomcreateevent.h49
-rw-r--r--lib/events/roomevent.cpp5
-rw-r--r--lib/events/roommemberevent.cpp14
-rw-r--r--lib/events/roommemberevent.h31
-rw-r--r--lib/events/roommessageevent.cpp129
-rw-r--r--lib/events/roommessageevent.h36
-rw-r--r--lib/events/roomtombstoneevent.cpp31
-rw-r--r--lib/events/roomtombstoneevent.h41
-rw-r--r--lib/events/simplestateevents.h31
-rw-r--r--lib/events/stateevent.cpp22
-rw-r--r--lib/events/stateevent.h31
19 files changed, 501 insertions, 115 deletions
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
index d1c1abc8..a99d85ac 100644
--- a/lib/events/accountdataevents.h
+++ b/lib/events/accountdataevents.h
@@ -36,37 +36,38 @@ namespace QMatrixClient
order_type order;
TagRecord (order_type order = none) : order(order) { }
- explicit TagRecord(const QJsonObject& jo)
+
+ bool operator<(const TagRecord& other) const
+ {
+ // Per The Spec, rooms with no order should be after those with order
+ return !order.omitted() &&
+ (other.order.omitted() || order.value() < other.order.value());
+ }
+ };
+
+ template <> struct JsonObjectConverter<TagRecord>
+ {
+ static void fillFrom(const QJsonObject& jo, TagRecord& rec)
{
// Parse a float both from JSON double and JSON string because
// libqmatrixclient previously used to use strings to store order.
const auto orderJv = jo.value("order"_ls);
if (orderJv.isDouble())
- order = fromJson<float>(orderJv);
- else if (orderJv.isString())
+ rec.order = fromJson<float>(orderJv);
+ if (orderJv.isString())
{
bool ok;
- order = orderJv.toString().toFloat(&ok);
+ rec.order = orderJv.toString().toFloat(&ok);
if (!ok)
- order = none;
+ rec.order = none;
}
}
-
- bool operator<(const TagRecord& other) const
+ static void dumpTo(QJsonObject& jo, const TagRecord& rec)
{
- // Per The Spec, rooms with no order should be after those with order
- return !order.omitted() &&
- (other.order.omitted() || order.value() < other.order.value());
+ addParam<IfNotEmpty>(jo, QStringLiteral("order"), rec.order);
}
};
- inline QJsonValue toJson(const TagRecord& rec)
- {
- QJsonObject o;
- addParam<IfNotEmpty>(o, QStringLiteral("order"), rec.order);
- return o;
- }
-
using TagsMap = QHash<QString, TagRecord>;
#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \
diff --git a/lib/events/event.cpp b/lib/events/event.cpp
index fd6e3939..6505d89a 100644
--- a/lib/events/event.cpp
+++ b/lib/events/event.cpp
@@ -38,7 +38,8 @@ event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId)
QString EventTypeRegistry::getMatrixType(event_type_t typeId)
{
- return typeId < get().eventTypes.size() ? get().eventTypes[typeId] : "";
+ return typeId < get().eventTypes.size()
+ ? get().eventTypes[typeId] : QString();
}
Event::Event(Type type, const QJsonObject& json)
@@ -77,3 +78,8 @@ const QJsonObject Event::unsignedJson() const
{
return fullJson()[UnsignedKeyL].toObject();
}
+
+void Event::dumpTo(QDebug dbg) const
+{
+ dbg << QJsonDocument(contentJson()).toJson(QJsonDocument::Compact);
+}
diff --git a/lib/events/event.h b/lib/events/event.h
index e0d83976..b7bbd83e 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -32,19 +32,23 @@ namespace QMatrixClient
template <typename EventT>
using event_ptr_tt = std::unique_ptr<EventT>;
+ /// Unwrap a plain pointer from a smart pointer
template <typename EventT>
- inline EventT* rawPtr(const event_ptr_tt<EventT>& ptr) // unwrap
+ inline EventT* rawPtr(const event_ptr_tt<EventT>& ptr)
{
return ptr.get();
}
+ /// Unwrap a plain pointer and downcast it to the specified type
template <typename TargetEventT, typename EventT>
inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr)
{
return static_cast<TargetEventT*>(rawPtr(ptr));
}
+ /// Re-wrap a smart pointer to base into a smart pointer to derived
template <typename TargetT, typename SourceT>
+ [[deprecated("Consider using eventCast() or visit() instead")]]
inline event_ptr_tt<TargetT> ptrCast(event_ptr_tt<SourceT>&& ptr)
{
return unique_ptr_cast<TargetT>(ptr);
@@ -208,6 +212,9 @@ namespace QMatrixClient
template <typename EventT>
inline auto registerEventType()
{
+ // Initialise exactly once, even if this function is called twice for
+ // the same type (for whatever reason - you never know the ways of
+ // static initialisation is done).
static const auto _ = setupFactory<EventT>();
return _; // Only to facilitate usage in static initialisation
}
@@ -257,8 +264,18 @@ namespace QMatrixClient
return fromJson<T>(contentJson()[key]);
}
+ friend QDebug operator<<(QDebug dbg, const Event& e)
+ {
+ QDebugStateSaver _dss { dbg };
+ dbg.noquote().nospace()
+ << e.matrixType() << '(' << e.type() << "): ";
+ e.dumpTo(dbg);
+ return dbg;
+ }
+
virtual bool isStateEvent() const { return false; }
virtual bool isCallEvent() const { return false; }
+ virtual void dumpTo(QDebug dbg) const;
protected:
QJsonObject& editJson() { return _json; }
@@ -327,7 +344,8 @@ namespace QMatrixClient
-> decltype(static_cast<EventT*>(&*eptr))
{
Q_ASSERT(eptr);
- return is<EventT>(*eptr) ? static_cast<EventT*>(&*eptr) : nullptr;
+ return is<std::decay_t<EventT>>(*eptr)
+ ? static_cast<EventT*>(&*eptr) : nullptr;
}
// A single generic catch-all visitor
@@ -359,7 +377,7 @@ namespace QMatrixClient
visit(const BaseEventT& event, FnT&& visitor)
{
using event_type = fn_arg_t<FnT>;
- if (is<event_type>(event))
+ if (is<std::decay_t<event_type>>(event))
visitor(static_cast<event_type>(event));
}
@@ -373,7 +391,7 @@ namespace QMatrixClient
fn_return_t<FnT>&& defaultValue = {})
{
using event_type = fn_arg_t<FnT>;
- if (is<event_type>(event))
+ if (is<std::decay_t<event_type>>(event))
return visitor(static_cast<event_type>(event));
return std::forward<fn_return_t<FnT>>(defaultValue);
}
@@ -386,7 +404,7 @@ namespace QMatrixClient
FnTs&&... visitors)
{
using event_type1 = fn_arg_t<FnT1>;
- if (is<event_type1>(event))
+ if (is<std::decay_t<event_type1>>(event))
return visitor1(static_cast<event_type1&>(event));
return visit(event, std::forward<FnT2>(visitor2),
std::forward<FnTs>(visitors)...);
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
index a6b1c763..77f756cd 100644
--- a/lib/events/eventcontent.cpp
+++ b/lib/events/eventcontent.cpp
@@ -17,6 +17,8 @@
*/
#include "eventcontent.h"
+
+#include "converters.h"
#include "util.h"
#include <QtCore/QMimeDatabase>
@@ -30,7 +32,7 @@ QJsonObject Base::toJson() const
return o;
}
-FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType,
+FileInfo::FileInfo(const QUrl& u, qint64 payloadSize, const QMimeType& mimeType,
const QString& originalFilename)
: mimeType(mimeType), url(u), payloadSize(payloadSize)
, originalName(originalFilename)
@@ -41,23 +43,31 @@ FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson,
: originalInfoJson(infoJson)
, mimeType(QMimeDatabase().mimeTypeForName(infoJson["mimetype"_ls].toString()))
, url(u)
- , payloadSize(infoJson["size"_ls].toInt())
+ , payloadSize(fromJson<qint64>(infoJson["size"_ls]))
, originalName(originalFilename)
{
if (!mimeType.isValid())
mimeType = QMimeDatabase().mimeTypeForData(QByteArray());
}
+bool FileInfo::isValid() const
+{
+ return url.scheme() == "mxc"
+ && (url.authority() + url.path()).count('/') == 1;
+}
+
void FileInfo::fillInfoJson(QJsonObject* infoJson) const
{
Q_ASSERT(infoJson);
- infoJson->insert(QStringLiteral("size"), payloadSize);
- infoJson->insert(QStringLiteral("mimetype"), mimeType.name());
+ if (payloadSize != -1)
+ infoJson->insert(QStringLiteral("size"), payloadSize);
+ if (mimeType.isValid())
+ infoJson->insert(QStringLiteral("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, qint64 fileSize, QMimeType mimeType,
+ const QSize& imageSize, const QString& originalFilename)
+ : FileInfo(u, fileSize, mimeType, originalFilename), imageSize(imageSize)
{ }
ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson,
@@ -69,8 +79,10 @@ ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson,
void ImageInfo::fillInfoJson(QJsonObject* infoJson) const
{
FileInfo::fillInfoJson(infoJson);
- infoJson->insert(QStringLiteral("w"), imageSize.width());
- infoJson->insert(QStringLiteral("h"), imageSize.height());
+ if (imageSize.width() != -1)
+ infoJson->insert(QStringLiteral("w"), imageSize.width());
+ if (imageSize.height() != -1)
+ infoJson->insert(QStringLiteral("h"), imageSize.height());
}
Thumbnail::Thumbnail(const QJsonObject& infoJson)
@@ -80,7 +92,9 @@ Thumbnail::Thumbnail(const QJsonObject& infoJson)
void Thumbnail::fillInfoJson(QJsonObject* infoJson) const
{
- infoJson->insert(QStringLiteral("thumbnail_url"), url.toString());
- infoJson->insert(QStringLiteral("thumbnail_info"),
- toInfoJson<ImageInfo>(*this));
+ if (url.isValid())
+ infoJson->insert(QStringLiteral("thumbnail_url"), url.toString());
+ if (!imageSize.isEmpty())
+ infoJson->insert(QStringLiteral("thumbnail_info"),
+ toInfoJson<ImageInfo>(*this));
}
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
index 91d7a8c8..ab31a75d 100644
--- a/lib/events/eventcontent.h
+++ b/lib/events/eventcontent.h
@@ -43,9 +43,10 @@ namespace QMatrixClient
class Base
{
public:
- explicit Base (const QJsonObject& o = {}) : originalJson(o) { }
+ explicit Base (QJsonObject o = {}) : originalJson(std::move(o)) { }
virtual ~Base() = default;
+ // FIXME: make toJson() from converters.* work on base classes
QJsonObject toJson() const;
public:
@@ -87,12 +88,14 @@ namespace QMatrixClient
class FileInfo
{
public:
- explicit FileInfo(const QUrl& u, int payloadSize = -1,
+ explicit FileInfo(const QUrl& u, qint64 payloadSize = -1,
const QMimeType& mimeType = {},
const QString& originalFilename = {});
FileInfo(const QUrl& u, const QJsonObject& infoJson,
const QString& originalFilename = {});
+ bool isValid() const;
+
void fillInfoJson(QJsonObject* infoJson) const;
/**
@@ -108,7 +111,7 @@ namespace QMatrixClient
QJsonObject originalInfoJson;
QMimeType mimeType;
QUrl url;
- int payloadSize;
+ qint64 payloadSize;
QString originalName;
};
@@ -126,9 +129,10 @@ namespace QMatrixClient
class ImageInfo : public FileInfo
{
public:
- explicit ImageInfo(const QUrl& u, int fileSize = -1,
+ explicit ImageInfo(const QUrl& u, qint64 fileSize = -1,
QMimeType mimeType = {},
- const QSize& imageSize = {});
+ const QSize& imageSize = {},
+ const QString& originalFilename = {});
ImageInfo(const QUrl& u, const QJsonObject& infoJson,
const QString& originalFilename = {});
@@ -148,10 +152,10 @@ namespace QMatrixClient
class Thumbnail : public ImageInfo
{
public:
+ Thumbnail() : ImageInfo(QUrl()) { } // To allow empty thumbnails
Thumbnail(const QJsonObject& infoJson);
- Thumbnail(const ImageInfo& info)
- : ImageInfo(info)
- { }
+ Thumbnail(const ImageInfo& info) : ImageInfo(info) { }
+ using ImageInfo::ImageInfo;
/**
* Writes thumbnail information to "thumbnail_info" subobject
@@ -166,6 +170,7 @@ namespace QMatrixClient
explicit TypedBase(const QJsonObject& o = {}) : Base(o) { }
virtual QMimeType type() const = 0;
virtual const FileInfo* fileInfo() const { return nullptr; }
+ virtual FileInfo* fileInfo() { return nullptr; }
virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
};
@@ -183,9 +188,7 @@ namespace QMatrixClient
class UrlBasedContent : public TypedBase, public InfoT
{
public:
- UrlBasedContent(QUrl url, InfoT&& info, QString filename = {})
- : InfoT(url, std::forward<InfoT>(info), filename)
- { }
+ using InfoT::InfoT;
explicit UrlBasedContent(const QJsonObject& json)
: TypedBase(json)
, InfoT(json["url"].toString(), json["info"].toObject(),
@@ -197,6 +200,7 @@ namespace QMatrixClient
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
@@ -213,7 +217,7 @@ namespace QMatrixClient
class UrlWithThumbnailContent : public UrlBasedContent<InfoT>
{
public:
- // TODO: POD constructor
+ using UrlBasedContent<InfoT>::UrlBasedContent;
explicit UrlWithThumbnailContent(const QJsonObject& json)
: UrlBasedContent<InfoT>(json)
, thumbnail(InfoT::originalInfoJson)
diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h
index 3ee9a181..da663392 100644
--- a/lib/events/eventloader.h
+++ b/lib/events/eventloader.h
@@ -19,7 +19,6 @@
#pragma once
#include "stateevent.h"
-#include "converters.h"
namespace QMatrixClient {
namespace _impl {
@@ -58,11 +57,15 @@ namespace QMatrixClient {
matrixType);
}
- template <typename EventT> struct FromJsonObject<event_ptr_tt<EventT>>
+ template <typename EventT> struct JsonConverter<event_ptr_tt<EventT>>
{
- auto operator()(const QJsonObject& jo) const
+ static auto load(const QJsonValue& jv)
{
- return loadEvent<EventT>(jo);
+ return loadEvent<EventT>(jv.toObject());
+ }
+ static auto load(const QJsonDocument& jd)
+ {
+ return loadEvent<EventT>(jd.object());
}
};
} // namespace QMatrixClient
diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h
index 491861b1..a43d3a85 100644
--- a/lib/events/roomavatarevent.h
+++ b/lib/events/roomavatarevent.h
@@ -18,8 +18,7 @@
#pragma once
-#include "event.h"
-
+#include "stateevent.h"
#include "eventcontent.h"
namespace QMatrixClient
diff --git a/lib/events/roomcreateevent.cpp b/lib/events/roomcreateevent.cpp
new file mode 100644
index 00000000..8fd0f1de
--- /dev/null
+++ b/lib/events/roomcreateevent.cpp
@@ -0,0 +1,45 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "roomcreateevent.h"
+
+using namespace QMatrixClient;
+
+bool RoomCreateEvent::isFederated() const
+{
+ return fromJson<bool>(contentJson()["m.federate"_ls]);
+}
+
+QString RoomCreateEvent::version() const
+{
+ return fromJson<QString>(contentJson()["room_version"_ls]);
+}
+
+RoomCreateEvent::Predecessor RoomCreateEvent::predecessor() const
+{
+ const auto predJson = contentJson()["predecessor"_ls].toObject();
+ return {
+ fromJson<QString>(predJson["room_id"_ls]),
+ fromJson<QString>(predJson["event_id"_ls])
+ };
+}
+
+bool RoomCreateEvent::isUpgrade() const
+{
+ return contentJson().contains("predecessor"_ls);
+}
diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h
new file mode 100644
index 00000000..0a8f27cc
--- /dev/null
+++ b/lib/events/roomcreateevent.h
@@ -0,0 +1,49 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "stateevent.h"
+
+namespace QMatrixClient
+{
+ class RoomCreateEvent : public StateEventBase
+ {
+ public:
+ DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent)
+
+ explicit RoomCreateEvent()
+ : StateEventBase(typeId(), matrixTypeId())
+ { }
+ explicit RoomCreateEvent(const QJsonObject& obj)
+ : StateEventBase(typeId(), obj)
+ { }
+
+ struct Predecessor
+ {
+ QString roomId;
+ QString eventId;
+ };
+
+ bool isFederated() const;
+ QString version() const;
+ Predecessor predecessor() const;
+ bool isUpgrade() const;
+ };
+ REGISTER_EVENT_TYPE(RoomCreateEvent)
+}
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index 80d121de..3d03509f 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -42,10 +42,6 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& json)
_redactedBecause = makeEvent<RedactionEvent>(redaction.toObject());
return;
}
-
- const auto& txnId = transactionId();
- if (!txnId.isEmpty())
- qCDebug(EVENTS) << "Event transactionId:" << txnId;
}
RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job
@@ -90,7 +86,6 @@ void RoomEvent::setTransactionId(const QString& txnId)
auto unsignedData = fullJson()[UnsignedKeyL].toObject();
unsignedData.insert(QStringLiteral("transaction_id"), txnId);
editJson().insert(UnsignedKey, unsignedData);
- qCDebug(EVENTS) << "New event transactionId:" << txnId;
Q_ASSERT(transactionId() == txnId);
}
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index eaa3302c..6da76526 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -23,20 +23,17 @@
#include <array>
-using namespace QMatrixClient;
-
static const std::array<QString, 5> membershipStrings = { {
QStringLiteral("invite"), QStringLiteral("join"),
QStringLiteral("knock"), QStringLiteral("leave"),
QStringLiteral("ban")
} };
-namespace QMatrixClient
-{
+namespace QMatrixClient {
template <>
- struct FromJson<MembershipType>
+ struct JsonConverter<MembershipType>
{
- MembershipType operator()(const QJsonValue& jv) const
+ static MembershipType load(const QJsonValue& jv)
{
const auto& membershipString = jv.toString();
for (auto it = membershipStrings.begin();
@@ -48,13 +45,14 @@ namespace QMatrixClient
return MembershipType::Undefined;
}
};
-
}
+using namespace QMatrixClient;
+
MemberEventContent::MemberEventContent(const QJsonObject& json)
: membership(fromJson<MembershipType>(json["membership"_ls]))
, isDirect(json["is_direct"_ls].toBool())
- , displayName(json["displayname"_ls].toString())
+ , displayName(sanitized(json["displayname"_ls].toString()))
, avatarUrl(json["avatar_url"_ls].toString())
{ }
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index db25d026..b8224033 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -29,13 +29,10 @@ namespace QMatrixClient
enum MembershipType : size_t { Invite = 0, Join, Knock, Leave, Ban,
Undefined };
- explicit MemberEventContent(MembershipType mt = MembershipType::Join)
+ explicit MemberEventContent(MembershipType mt = Join)
: membership(mt)
{ }
explicit MemberEventContent(const QJsonObject& json);
- explicit MemberEventContent(const QJsonValue& jv)
- : MemberEventContent(jv.toObject())
- { }
MembershipType membership;
bool isDirect = false;
@@ -60,11 +57,19 @@ namespace QMatrixClient
: StateEvent(typeId(), obj)
{ }
RoomMemberEvent(MemberEventContent&& c)
- : StateEvent(typeId(), matrixTypeId(), c.toJson())
+ : StateEvent(typeId(), matrixTypeId(), c)
{ }
- // This is a special constructor enabling RoomMemberEvent to be
- // a base class for more specific member events.
+ /// A special constructor to create unknown RoomMemberEvents
+ /**
+ * This is needed in order to use RoomMemberEvent as a "base event
+ * class" in cases like GetMembersByRoomJob when RoomMemberEvents
+ * (rather than RoomEvents or StateEvents) are resolved from JSON.
+ * For such cases loadEvent<> requires an underlying class to be
+ * constructible with unknownTypeId() instead of its genuine id.
+ * Don't use it directly.
+ * \sa GetMembersByRoomJob, loadEvent, unknownTypeId
+ */
RoomMemberEvent(Type type, const QJsonObject& fullJson)
: StateEvent(type, fullJson)
{ }
@@ -84,6 +89,18 @@ namespace QMatrixClient
private:
REGISTER_ENUM(MembershipType)
};
+
+ template <>
+ class EventFactory<RoomMemberEvent>
+ {
+ public:
+ static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json,
+ const QString&)
+ {
+ return makeEvent<RoomMemberEvent>(json);
+ }
+ };
+
REGISTER_EVENT_TYPE(RoomMemberEvent)
DEFINE_EVENTTYPE_ALIAS(RoomMember, RoomMemberEvent)
} // namespace QMatrixClient
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
index 1c5cf058..8f4e0ebc 100644
--- a/lib/events/roommessageevent.cpp
+++ b/lib/events/roommessageevent.cpp
@@ -21,18 +21,38 @@
#include "logging.h"
#include <QtCore/QMimeDatabase>
+#include <QtCore/QFileInfo>
+#include <QtGui/QImageReader>
+#include <QtMultimedia/QMediaResource>
using namespace QMatrixClient;
using namespace EventContent;
using MsgType = RoomMessageEvent::MsgType;
+static const auto RelatesToKey = "m.relates_to"_ls;
+static const auto MsgTypeKey = "msgtype"_ls;
+static const auto BodyKey = "body"_ls;
+static const auto FormattedBodyKey = "formatted_body"_ls;
+
+static const auto TextTypeKey = "m.text";
+static const auto NoticeTypeKey = "m.notice";
+
+static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html");
+
template <typename ContentT>
TypedBase* make(const QJsonObject& json)
{
return new ContentT(json);
}
+template <>
+TypedBase* make<TextContent>(const QJsonObject& json)
+{
+ return json.contains(FormattedBodyKey) || json.contains(RelatesToKey)
+ ? new TextContent(json) : nullptr;
+}
+
struct MsgTypeDesc
{
QString matrixType;
@@ -41,9 +61,9 @@ struct MsgTypeDesc
};
const std::vector<MsgTypeDesc> msgTypes =
- { { QStringLiteral("m.text"), MsgType::Text, make<TextContent> }
+ { { TextTypeKey, MsgType::Text, make<TextContent> }
, { QStringLiteral("m.emote"), MsgType::Emote, make<TextContent> }
- , { QStringLiteral("m.notice"), MsgType::Notice, make<TextContent> }
+ , { NoticeTypeKey, MsgType::Notice, make<TextContent> }
, { QStringLiteral("m.image"), MsgType::Image, make<ImageContent> }
, { QStringLiteral("m.file"), MsgType::File, make<FileContent> }
, { QStringLiteral("m.location"), MsgType::Location, make<LocationContent> }
@@ -71,22 +91,26 @@ MsgType jsonToMsgType(const QString& matrixType)
return MsgType::Unknown;
}
-inline QJsonObject toMsgJson(const QString& plainBody, const QString& jsonMsgType,
- TypedBase* content)
+QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody,
+ const QString& jsonMsgType, TypedBase* content)
{
auto json = content ? content->toJson() : QJsonObject();
+ if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey &&
+ json.contains(RelatesToKey))
+ {
+ json.remove(RelatesToKey);
+ qCWarning(EVENTS) << RelatesToKey << "cannot be used in" << jsonMsgType
+ << "messages; the relation has been stripped off";
+ }
json.insert(QStringLiteral("msgtype"), jsonMsgType);
json.insert(QStringLiteral("body"), plainBody);
return json;
}
-static const auto MsgTypeKey = "msgtype"_ls;
-static const auto BodyKey = "body"_ls;
-
RoomMessageEvent::RoomMessageEvent(const QString& plainBody,
const QString& jsonMsgType, TypedBase* content)
: RoomEvent(typeId(), matrixTypeId(),
- toMsgJson(plainBody, jsonMsgType, content))
+ assembleContentJson(plainBody, jsonMsgType, content))
, _content(content)
{ }
@@ -95,6 +119,40 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody,
: RoomMessageEvent(plainBody, msgTypeToJson(msgType), content)
{ }
+TypedBase* contentFromFile(const QFileInfo& file, bool asGenericFile)
+{
+ auto filePath = file.absoluteFilePath();
+ auto localUrl = QUrl::fromLocalFile(filePath);
+ auto mimeType = QMimeDatabase().mimeTypeForFile(file);
+ if (!asGenericFile)
+ {
+ auto mimeTypeName = mimeType.name();
+ if (mimeTypeName.startsWith("image/"))
+ return new ImageContent(localUrl, file.size(), mimeType,
+ QImageReader(filePath).size(),
+ file.fileName());
+
+ // duration can only be obtained asynchronously and can only be reliably
+ // done by starting to play the file. Left for a future implementation.
+ if (mimeTypeName.startsWith("video/"))
+ return new VideoContent(localUrl, file.size(), mimeType,
+ QMediaResource(localUrl).resolution(),
+ file.fileName());
+
+ if (mimeTypeName.startsWith("audio/"))
+ return new AudioContent(localUrl, file.size(), mimeType,
+ file.fileName());
+ }
+ return new FileContent(localUrl, file.size(), mimeType, file.fileName());
+}
+
+RoomMessageEvent::RoomMessageEvent(const QString& plainBody,
+ const QFileInfo& file, bool asGenericFile)
+ : RoomMessageEvent(plainBody,
+ asGenericFile ? QStringLiteral("m.file") : rawMsgTypeForFile(file),
+ contentFromFile(file, asGenericFile))
+{ }
+
RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj)
: RoomEvent(typeId(), obj), _content(nullptr)
{
@@ -104,13 +162,17 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj)
if ( content.contains(MsgTypeKey) && content.contains(BodyKey) )
{
auto msgtype = content[MsgTypeKey].toString();
+ bool msgTypeFound = false;
for (const auto& mt: msgTypes)
if (mt.matrixType == msgtype)
+ {
_content.reset(mt.maker(content));
+ msgTypeFound = true;
+ }
- if (!_content)
+ if (!msgTypeFound)
{
- qCWarning(EVENTS) << "RoomMessageEvent: couldn't load content,"
+ qCWarning(EVENTS) << "RoomMessageEvent: unknown msg_type,"
<< " full content dump follows";
qCWarning(EVENTS) << formatJson << content;
}
@@ -142,14 +204,13 @@ QMimeType RoomMessageEvent::mimeType() const
static const auto PlainTextMimeType =
QMimeDatabase().mimeTypeForName("text/plain");
return _content ? _content->type() : PlainTextMimeType;
- ;
}
bool RoomMessageEvent::hasTextContent() const
{
- return content() &&
+ return !content() ||
(msgtype() == MsgType::Text || msgtype() == MsgType::Emote ||
- msgtype() == MsgType::Notice); // FIXME: Unbind from specific msgtypes
+ msgtype() == MsgType::Notice);
}
bool RoomMessageEvent::hasFileContent() const
@@ -162,10 +223,31 @@ bool RoomMessageEvent::hasThumbnail() const
return content() && content()->thumbnailInfo();
}
-TextContent::TextContent(const QString& text, const QString& contentType)
+QString rawMsgTypeForMimeType(const QMimeType& mimeType)
+{
+ auto name = mimeType.name();
+ return name.startsWith("image/") ? QStringLiteral("m.image") :
+ name.startsWith("video/") ? QStringLiteral("m.video") :
+ name.startsWith("audio/") ? QStringLiteral("m.audio") :
+ QStringLiteral("m.file");
+}
+
+QString RoomMessageEvent::rawMsgTypeForUrl(const QUrl& url)
+{
+ return rawMsgTypeForMimeType(QMimeDatabase().mimeTypeForUrl(url));
+}
+
+QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi)
+{
+ return rawMsgTypeForMimeType(QMimeDatabase().mimeTypeForFile(fi));
+}
+
+TextContent::TextContent(const QString& text, const QString& contentType,
+ Omittable<RelatesTo> relatesTo)
: mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(text)
+ , relatesTo(std::move(relatesTo))
{
- if (contentType == "org.matrix.custom.html")
+ if (contentType == HtmlContentTypeId)
mimeType = QMimeDatabase().mimeTypeForName("text/html");
}
@@ -177,16 +259,20 @@ TextContent::TextContent(const QJsonObject& json)
// Special-casing the custom matrix.org's (actually, Riot's) way
// of sending HTML messages.
- if (json["format"_ls].toString() == "org.matrix.custom.html")
+ if (json["format"_ls].toString() == HtmlContentTypeId)
{
mimeType = HtmlMimeType;
- body = json["formatted_body"_ls].toString();
+ body = json[FormattedBodyKey].toString();
} else {
// Falling back to plain text, as there's no standard way to describe
// rich text in messages.
mimeType = PlainTextMimeType;
body = json[BodyKey].toString();
}
+ const auto replyJson = json[RelatesToKey].toObject()
+ .value(RelatesTo::ReplyTypeId()).toObject();
+ if (!replyJson.isEmpty())
+ relatesTo = replyTo(fromJson<QString>(replyJson[EventIdKeyL]));
}
void TextContent::fillJson(QJsonObject* json) const
@@ -194,13 +280,16 @@ void TextContent::fillJson(QJsonObject* json) const
Q_ASSERT(json);
if (mimeType.inherits("text/html"))
{
- json->insert(QStringLiteral("format"),
- QStringLiteral("org.matrix.custom.html"));
+ json->insert(QStringLiteral("format"), HtmlContentTypeId);
json->insert(QStringLiteral("formatted_body"), body);
}
+ if (!relatesTo.omitted())
+ json->insert(QStringLiteral("m.relates_to"),
+ QJsonObject { { relatesTo->type, relatesTo->eventId } });
}
-LocationContent::LocationContent(const QString& geoUri, const ImageInfo& thumbnail)
+LocationContent::LocationContent(const QString& geoUri,
+ const Thumbnail& thumbnail)
: geoUri(geoUri), thumbnail(thumbnail)
{ }
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
index 4c29a93e..c2e075eb 100644
--- a/lib/events/roommessageevent.h
+++ b/lib/events/roommessageevent.h
@@ -21,6 +21,8 @@
#include "roomevent.h"
#include "eventcontent.h"
+class QFileInfo;
+
namespace QMatrixClient
{
namespace MessageEventContent = EventContent; // Back-compatibility
@@ -49,6 +51,9 @@ namespace QMatrixClient
explicit RoomMessageEvent(const QString& plainBody,
MsgType msgType = MsgType::Text,
EventContent::TypedBase* content = nullptr);
+ explicit RoomMessageEvent(const QString& plainBody,
+ const QFileInfo& file,
+ bool asGenericFile = false);
explicit RoomMessageEvent(const QJsonObject& obj);
MsgType msgtype() const;
@@ -56,14 +61,27 @@ namespace QMatrixClient
QString plainBody() const;
EventContent::TypedBase* content() const
{ return _content.data(); }
+ template <typename VisitorT>
+ void editContent(VisitorT visitor)
+ {
+ visitor(*_content);
+ editJson()[ContentKeyL] =
+ assembleContentJson(plainBody(), rawMsgtype(), content());
+ }
QMimeType mimeType() const;
bool hasTextContent() const;
bool hasFileContent() const;
bool hasThumbnail() const;
+ static QString rawMsgTypeForUrl(const QUrl& url);
+ static QString rawMsgTypeForFile(const QFileInfo& fi);
+
private:
QScopedPointer<EventContent::TypedBase> _content;
+ static QJsonObject assembleContentJson(const QString& plainBody,
+ const QString& jsonMsgType, EventContent::TypedBase* content);
+
REGISTER_ENUM(MsgType)
};
REGISTER_EVENT_TYPE(RoomMessageEvent)
@@ -74,6 +92,17 @@ namespace QMatrixClient
{
// Additional event content types
+ struct RelatesTo
+ {
+ static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; }
+ QString type; // The only supported relation so far
+ QString eventId;
+ };
+ inline RelatesTo replyTo(QString eventId)
+ {
+ return { RelatesTo::ReplyTypeId(), std::move(eventId) };
+ }
+
/**
* Rich text content for m.text, m.emote, m.notice
*
@@ -83,13 +112,15 @@ namespace QMatrixClient
class TextContent: public TypedBase
{
public:
- TextContent(const QString& text, const QString& contentType);
+ TextContent(const QString& text, const QString& contentType,
+ Omittable<RelatesTo> relatesTo = none);
explicit TextContent(const QJsonObject& json);
QMimeType type() const override { return mimeType; }
QMimeType mimeType;
QString body;
+ Omittable<RelatesTo> relatesTo;
protected:
void fillJson(QJsonObject* json) const override;
@@ -112,7 +143,7 @@ namespace QMatrixClient
{
public:
LocationContent(const QString& geoUri,
- const ImageInfo& thumbnail);
+ const Thumbnail& thumbnail = {});
explicit LocationContent(const QJsonObject& json);
QMimeType type() const override;
@@ -132,6 +163,7 @@ namespace QMatrixClient
class PlayableContent : public ContentT
{
public:
+ using ContentT::ContentT;
PlayableContent(const QJsonObject& json)
: ContentT(json)
, duration(ContentT::originalInfoJson["duration"_ls].toInt())
diff --git a/lib/events/roomtombstoneevent.cpp b/lib/events/roomtombstoneevent.cpp
new file mode 100644
index 00000000..9c3bafd4
--- /dev/null
+++ b/lib/events/roomtombstoneevent.cpp
@@ -0,0 +1,31 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "roomtombstoneevent.h"
+
+using namespace QMatrixClient;
+
+QString RoomTombstoneEvent::serverMessage() const
+{
+ return fromJson<QString>(contentJson()["body"_ls]);
+}
+
+QString RoomTombstoneEvent::successorRoomId() const
+{
+ return fromJson<QString>(contentJson()["replacement_room"_ls]);
+}
diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h
new file mode 100644
index 00000000..c7008ec4
--- /dev/null
+++ b/lib/events/roomtombstoneevent.h
@@ -0,0 +1,41 @@
+/******************************************************************************
+* Copyright (C) 2019 QMatrixClient project
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 2.1 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#pragma once
+
+#include "stateevent.h"
+
+namespace QMatrixClient
+{
+ class RoomTombstoneEvent : public StateEventBase
+ {
+ public:
+ DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent)
+
+ explicit RoomTombstoneEvent()
+ : StateEventBase(typeId(), matrixTypeId())
+ { }
+ explicit RoomTombstoneEvent(const QJsonObject& obj)
+ : StateEventBase(typeId(), obj)
+ { }
+
+ QString serverMessage() const;
+ QString successorRoomId() const;
+ };
+ REGISTER_EVENT_TYPE(RoomTombstoneEvent)
+}
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index 56be947c..2c23d9ca 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -19,7 +19,6 @@
#pragma once
#include "stateevent.h"
-#include "eventcontent.h"
#include "converters.h"
@@ -28,7 +27,7 @@ namespace QMatrixClient
namespace EventContent
{
template <typename T>
- class SimpleContent: public Base
+ class SimpleContent
{
public:
using value_type = T;
@@ -39,41 +38,39 @@ namespace QMatrixClient
: value(std::forward<TT>(value)), key(std::move(keyName))
{ }
SimpleContent(const QJsonObject& json, QString keyName)
- : Base(json)
- , value(QMatrixClient::fromJson<T>(json[keyName]))
+ : value(fromJson<T>(json[keyName]))
, key(std::move(keyName))
{ }
+ QJsonObject toJson() const
+ {
+ return { { key, QMatrixClient::toJson(value) } };
+ }
public:
T value;
protected:
QString key;
-
- private:
- void fillJson(QJsonObject* json) const override
- {
- Q_ASSERT(json);
- json->insert(key, QMatrixClient::toJson(value));
- }
};
} // namespace EventContent
-#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \
- class _Name : public StateEvent<EventContent::SimpleContent<_ContentType>> \
+#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
+ class _Name : public StateEvent<EventContent::SimpleContent<_ValueType>> \
{ \
public: \
- using content_type = _ContentType; \
+ using value_type = content_type::value_type; \
DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- explicit _Name(const QJsonObject& obj) \
- : StateEvent(typeId(), obj, QStringLiteral(#_ContentKey)) \
- { } \
+ explicit _Name() : _Name(value_type()) { } \
template <typename T> \
explicit _Name(T&& value) \
: StateEvent(typeId(), matrixTypeId(), \
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) \
diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp
index 877d0fae..a84f302b 100644
--- a/lib/events/stateevent.cpp
+++ b/lib/events/stateevent.cpp
@@ -20,17 +20,20 @@
using namespace QMatrixClient;
+// Aside from the normal factory to instantiate StateEventBase inheritors
+// StateEventBase itself can be instantiated if there's a state_key JSON key
+// but the event type is unknown.
[[gnu::unused]] static auto stateEventTypeInitialised =
RoomEvent::factory_t::addMethod(
[] (const QJsonObject& json, const QString& matrixType) -> StateEventPtr
{
- if (!json.contains("state_key"))
+ if (!json.contains("state_key"_ls))
return nullptr;
if (auto e = StateEventBase::factory_t::make(json, matrixType))
return e;
- return nullptr;
+ return makeEvent<StateEventBase>(unknownEventTypeId(), json);
});
bool StateEventBase::repeatsState() const
@@ -38,3 +41,18 @@ bool StateEventBase::repeatsState() const
const auto prevContentJson = unsignedJson().value(PrevContentKeyL);
return fullJson().value(ContentKeyL) == prevContentJson;
}
+
+QString StateEventBase::replacedState() const
+{
+ return unsignedJson().value("replaces_state"_ls).toString();
+}
+
+void StateEventBase::dumpTo(QDebug dbg) const
+{
+ if (!stateKey().isEmpty())
+ dbg << '<' << stateKey() << "> ";
+ if (unsignedJson().contains(PrevContentKeyL))
+ dbg << QJsonDocument(unsignedJson()[PrevContentKeyL].toObject())
+ .toJson(QJsonDocument::Compact) << " -> ";
+ RoomEvent::dumpTo(dbg);
+}
diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h
index 6032132e..3f54f7bf 100644
--- a/lib/events/stateevent.h
+++ b/lib/events/stateevent.h
@@ -30,11 +30,24 @@ namespace QMatrixClient {
~StateEventBase() override = default;
bool isStateEvent() const override { return true; }
+ QString replacedState() const;
+ void dumpTo(QDebug dbg) const override;
+
virtual bool repeatsState() const;
};
using StateEventPtr = event_ptr_tt<StateEventBase>;
using StateEvents = EventsArray<StateEventBase>;
+ template <>
+ inline bool is<StateEventBase>(const Event& e) { return e.isStateEvent(); }
+
+ /**
+ * A combination of event type and state key uniquely identifies a piece
+ * of state in Matrix.
+ * \sa https://matrix.org/docs/spec/client_server/unstable.html#types-of-room-events
+ */
+ using StateEventKey = QPair<QString, QString>;
+
template <typename ContentT>
struct Prev
{
@@ -78,6 +91,12 @@ namespace QMatrixClient {
}
const ContentT& content() const { return _content; }
+ template <typename VisitorT>
+ void editContent(VisitorT&& visitor)
+ {
+ visitor(_content);
+ editJson()[ContentKeyL] = _content.toJson();
+ }
[[deprecated("Use prevContent instead")]]
const ContentT* prev_content() const { return prevContent(); }
const ContentT* prevContent() const
@@ -85,8 +104,18 @@ namespace QMatrixClient {
QString prevSenderId() const
{ return _prev ? _prev->senderId : QString(); }
- protected:
+ private:
ContentT _content;
std::unique_ptr<Prev<ContentT>> _prev;
};
} // namespace QMatrixClient
+
+namespace std {
+ template <> struct hash<QMatrixClient::StateEventKey>
+ {
+ size_t operator()(const QMatrixClient::StateEventKey& k) const Q_DECL_NOEXCEPT
+ {
+ return qHash(k);
+ }
+ };
+}