aboutsummaryrefslogtreecommitdiff
path: root/lib/events/event.h
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-07-01 22:48:38 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-07-04 09:07:32 +0900
commitf1ffe1e7a3e81c07a07a8416ce307e4413ec8fbc (patch)
treef2435183d11a4cea52a7532eb9ff3d4d837e1d22 /lib/events/event.h
parentd5397fe5ae2ca34d5cfb11394dac17728a2b50ce (diff)
downloadlibquotient-f1ffe1e7a3e81c07a07a8416ce307e4413ec8fbc.tar.gz
libquotient-f1ffe1e7a3e81c07a07a8416ce307e4413ec8fbc.zip
Event types system remade to be extensible
There were two common points that had to be updated every time a new event is introduced: the EventType enumeration and one of 3 doMakeEvent<> specialisations. The new code has a template class, EventFactory<>, that uses a list of static factory methods to create events instead of typelists used in doMakeEvent<>(); the EventType enumeration is replaced with a namespace populated with constants as necessary. In general, EventType is considered a deprecated mechanism altogether; instead, a set of facilities is provided: is<>() to check if an event has a certain type (to replace comparison against an EventType value) and visit<>() to execute actions based on the event type (replacing switch statements over EventType values). Closes #129.
Diffstat (limited to 'lib/events/event.h')
-rw-r--r--lib/events/event.h349
1 files changed, 249 insertions, 100 deletions
diff --git a/lib/events/event.h b/lib/events/event.h
index cbfa06ac..f8264baf 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -23,6 +23,8 @@
namespace QMatrixClient
{
+ // === event_ptr_tt<> and type casting facilities ===
+
template <typename EventT>
using event_ptr_tt = std::unique_ptr<EventT>;
@@ -44,106 +46,262 @@ namespace QMatrixClient
return unique_ptr_cast<TargetT>(ptr);
}
- namespace _impl
+ // === Predefined types and JSON key names
+
+ using event_type_t = uint;
+ using event_mtype_t = const char*;
+
+ static const auto TypeKey = QStringLiteral("type");
+ static const auto ContentKey = QStringLiteral("content");
+ static const auto EventIdKey = QStringLiteral("event_id");
+ static const auto TypeKeyL = "type"_ls;
+ static const auto ContentKeyL = "content"_ls;
+ static const auto EventIdKeyL = "event_id"_ls;
+ static const auto UnsignedKeyL = "unsigned"_ls;
+ static const auto RedactedCauseKeyL = "redacted_because"_ls;
+ static const auto PrevContentKeyL = "prev_content"_ls;
+
+ // === Event factory ===
+
+ template <typename EventT, typename... ArgTs>
+ inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args)
{
- template <typename EventT, typename... ArgTs>
- inline event_ptr_tt<EventT> create(ArgTs&&... args)
- {
- return std::make_unique<EventT>(std::forward<ArgTs>(args)...);
- }
+ return std::make_unique<EventT>(std::forward<ArgTs>(args)...);
+ }
+
+ class EventTypeRegistry
+ {
+ public:
+ static constexpr event_type_t unknownTypeId() { return 0; }
+ static event_type_t nextTypeId();
+
+ /** Chain two type factories
+ * Adds the factory class of EventT2 (EventT2::factory_t) to
+ * the list in factory class of EventT1 (EventT1::factory_t) so
+ * that when EventT1::factory_t::make() is invoked, types of
+ * EventT2 factory are looked through as well. This is used
+ * to include RoomEvent types into the more general Event factory,
+ * and state event types into the RoomEvent factory.
+ */
+ template <typename EventT1, typename EventT2>
+ static auto chainFactories()
+ {
+ EventT1::factory_t::addFactory(&EventT2::factory_t::make);
+ return 0;
+ }
+
+ /** Add a type to its default factory
+ * Adds a standard factory method (via makeEvent<>) for a given
+ * type to EventT::factory_t factory class so that it can be
+ * created dynamically from loadEvent<>().
+ *
+ * \tparam EventT the type to enable dynamic creation of
+ * \return the registered type id
+ * \sa loadEvent, Event::type
+ */
+ template <typename EventT>
+ static auto addType()
+ {
+ EventT::factory_t::addFactory(
+ [] (const QJsonObject& json, const QString& jsonMatrixType)
+ {
+ return EventT::matrixTypeId() == jsonMatrixType
+ ? makeEvent<EventT>(json) : nullptr;
+ });
+ return nextTypeId();
+ }
+
+ template <typename EventT>
+ static auto typeId() { return _typeId<std::decay_t<EventT>>; }
+
+ private:
+ template <typename EventT>
+ static const event_type_t _typeId;
+ };
+
+ template <typename EventT>
+ const event_type_t EventTypeRegistry::_typeId = addType<EventT>();
+
+ template <typename BaseEventT>
+ class EventFactory
+ {
+ public:
+ template <typename FnT>
+ static void addFactory(FnT&& factory)
+ {
+ factories().emplace_back(std::forward<FnT>(factory));
+ }
+
+ static event_ptr_tt<BaseEventT> make(const QJsonObject& json,
+ const QString& matrixType)
+ {
+ for (const auto& f: factories())
+ if (auto e = f(json, matrixType))
+ return e;
+ return makeEvent<BaseEventT>(EventTypeRegistry::unknownTypeId(),
+ json);
+ }
+
+ private:
+ static auto& factories()
+ {
+ using inner_factory_tt =
+ std::function<event_ptr_tt<BaseEventT>(const QJsonObject&,
+ const QString&)>;
+ static std::vector<inner_factory_tt> _factories {};
+ return _factories;
+ }
+ };
+
+ template <typename StrT>
+ inline QJsonObject basicEventJson(StrT matrixType,
+ const QJsonObject& content)
+ {
+ return { { TypeKey, std::forward<StrT>(matrixType) },
+ { ContentKey, content } };
+ }
+
+ /** Create an event with proper type from a JSON object
+ * Use this factory template to detect the type from the JSON object
+ * contents (the detected event type should derive from the template
+ * parameter type) and create an event object of that type.
+ */
+ template <typename BaseEventT>
+ inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson)
+ {
+ return EventFactory<BaseEventT>
+ ::make(fullJson, fullJson[TypeKeyL].toString());
+ }
- template <typename EventT>
- inline event_ptr_tt<EventT> doMakeEvent(const QJsonObject& obj)
+ /** Create an event from a type string and content JSON
+ * Use this factory template to resolve the C++ type from the Matrix
+ * type string in \p matrixType and create an event of that type that has
+ * its content part set to \p content.
+ */
+ template <typename BaseEventT>
+ inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType,
+ const QJsonObject& content)
+ {
+ return EventFactory<BaseEventT>
+ ::make(basicEventJson(matrixType, content), matrixType);
+ }
+
+ template <typename EventT> struct FromJson<event_ptr_tt<EventT>>
+ {
+ auto operator()(const QJsonValue& jv) const
{
- return create<EventT>(obj);
+ return loadEvent<EventT>(jv.toObject());
}
- }
+ };
+
+ // === Event ===
class Event
{
Q_GADGET
public:
- enum class Type : quint16
- {
- Unknown = 0,
- Typing, Receipt, Tag, DirectChat, ReadMarker,
- RoomEventBase = 0x1000,
- RoomMessage = RoomEventBase + 1,
- RoomEncryptedMessage, Redaction,
- RoomStateEventBase = 0x1800,
- RoomName = RoomStateEventBase + 1,
- RoomAliases, RoomCanonicalAlias, RoomMember, RoomTopic,
- RoomAvatar, RoomEncryption, RoomCreate, RoomJoinRules,
- RoomPowerLevels,
- Reserved = 0x2000
- };
-
- explicit Event(Type type) : _type(type) { }
- Event(Type type, const QJsonObject& rep);
+ using Type = event_type_t;
+ using factory_t = EventFactory<Event>;
+
+ explicit Event(Type type, const QJsonObject& json);
+ explicit Event(Type type, event_mtype_t matrixType,
+ const QJsonObject& contentJson = {});
Event(const Event&) = delete;
+ Event(Event&&) = default;
+ Event& operator=(const Event&) = delete;
+ Event& operator=(Event&&) = delete;
virtual ~Event();
Type type() const { return _type; }
- QString jsonType() const;
- bool isStateEvent() const
- {
- return (quint16(_type) & 0x1800) == 0x1800;
- }
+ QString matrixType() const;
QByteArray originalJson() const;
- QJsonObject originalJsonObject() const;
+ QJsonObject originalJsonObject() const { return fullJson(); }
+
+ const QJsonObject& fullJson() const { return _json; }
// According to the CS API spec, every event also has
// a "content" object; but since its structure is different for
- // different types, we're implementing it per-event type
- // (and in most cases it will be a combination of other fields
- // instead of "content" field).
+ // different types, we're implementing it per-event type.
const QJsonObject contentJson() const;
+ const QJsonObject unsignedJson() const;
+
+ virtual bool isStateEvent() const { return false; }
+
+ template <typename EventT>
+ bool is() const
+ {
+ const auto eventTypeId = EventTypeRegistry::typeId<EventT>();
+ return _type == eventTypeId;
+ }
- virtual QJsonObject toJson() const { Q_ASSERT(false); return {}; }
+ protected:
+ QJsonObject& editJson() { return _json; }
private:
Type _type;
- QJsonObject _originalJson;
+ QJsonObject _json;
- REGISTER_ENUM(Type)
Q_PROPERTY(Type type READ type CONSTANT)
Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT)
};
- using EventType = Event::Type;
using EventPtr = event_ptr_tt<Event>;
- /** Create an event with proper type from a JSON object
- * Use this factory template to detect the type from the JSON object
- * contents (the detected event type should derive from the template
- * parameter type) and create an event object of that type.
- */
template <typename EventT>
- inline event_ptr_tt<EventT> makeEvent(const QJsonObject& obj)
+ using EventsArray = std::vector<event_ptr_tt<EventT>>;
+ using Events = EventsArray<Event>;
+
+ // This macro should be used in a public section of an event class to
+ // provide matrixTypeId() and typeId().
+#define DEFINE_EVENT_TYPEID(_Id, _Type) \
+ static constexpr event_mtype_t matrixTypeId() { return _Id; } \
+ static event_type_t typeId() { return EventTypeRegistry::typeId<_Type>(); }
+
+ // This macro should be put after an event class definition to define an
+ // additional constant that can be used for an event type id. The constant
+ // will be inside EventType namespace. This is for back-compatibility,
+ // to support clients checking for EventType::ShortName (previously
+ // EventType was a typedef for an enumeration). New code should use
+ // either typeId() for a specific event type, or (better) casting methods
+ // defined in the very beginning of this file.
+#define DEFINE_EVENTTYPE_ALIAS(_Id, _Type) \
+ namespace EventType \
+ { \
+ [[deprecated("Use "#_Type"::typeId(), Event::is<>() or visit<>()")]] \
+ static const auto _Id { _Type::typeId() }; \
+ } // End of macro
+
+ // === visit<>() ===
+
+ template <typename FnT>
+ inline fn_return_t<FnT> visit(const Event& event, FnT visitor)
{
- auto e = _impl::doMakeEvent<EventT>(obj);
- if (!e)
- e = _impl::create<EventT>(EventType::Unknown, obj);
- return e;
+ using event_type = fn_arg_t<FnT>;
+ if (event.is<event_type>())
+ return visitor(static_cast<event_type>(event));
+ return fn_return_t<FnT>();
}
- namespace _impl
+ template <typename FnT, typename... FnTs>
+ inline auto visit(const Event& event, FnT visitor1, FnTs&&... visitors)
{
- template <>
- EventPtr doMakeEvent<Event>(const QJsonObject& obj);
+ using event_type1 = fn_arg_t<FnT>;
+ if (event.is<event_type1>())
+ return visitor1(static_cast<event_type1&>(event));
+
+ return visit(event, std::forward<FnTs>(visitors)...);
}
- template <typename EventT> struct FromJson<event_ptr_tt<EventT>>
+ template <typename BaseEventT, typename... FnTs>
+ inline auto visit(const event_ptr_tt<BaseEventT>& eptr, FnTs&&... visitors)
{
- auto operator()(const QJsonValue& jv) const
- {
- return makeEvent<EventT>(jv.toObject());
- }
- };
+ using return_type = decltype(visit(*eptr, visitors...));
+ if (eptr)
+ return visit(*eptr, visitors...);
+ return return_type();
+ }
- template <typename EventT>
- using EventsArray = std::vector<event_ptr_tt<EventT>>;
- using Events = EventsArray<Event>;
+ // === RoomEvent ===
class RedactionEvent;
@@ -159,13 +317,16 @@ namespace QMatrixClient
Q_PROPERTY(bool isRedacted READ isRedacted)
Q_PROPERTY(QString transactionId READ transactionId)
public:
+ using factory_t = EventFactory<RoomEvent>;
+
// RedactionEvent is an incomplete type here so we cannot inline
- // constructors and destructors
- explicit RoomEvent(Type type);
- RoomEvent(Type type, const QJsonObject& rep);
+ // constructors and destructors and we cannot use 'using'.
+ RoomEvent(Type type, event_mtype_t matrixType,
+ const QJsonObject& contentJson = {});
+ RoomEvent(Type type, const QJsonObject& json);
~RoomEvent() override;
- QString id() const { return _id; }
+ QString id() const;
QDateTime timestamp() const;
QString roomId() const;
QString senderId() const;
@@ -196,10 +357,9 @@ namespace QMatrixClient
* in the event. It's the responsibility of the code calling addId()
* to notify clients that use Q_PROPERTY(id) about its change
*/
- void addId(const QString& id);
+ void addId(const QString& newId);
private:
- QString _id;
event_ptr_tt<RedactionEvent> _redactedBecause;
QString _txnId;
};
@@ -207,43 +367,30 @@ namespace QMatrixClient
using RoomEvents = EventsArray<RoomEvent>;
using RoomEventsRange = Range<RoomEvents>;
- namespace _impl
- {
- template <>
- RoomEventPtr doMakeEvent<RoomEvent>(const QJsonObject& obj);
- }
+ // === State events ===
class StateEventBase: public RoomEvent
{
public:
- explicit StateEventBase(Type type, const QJsonObject& obj)
- : RoomEvent(obj.contains("state_key") ? type : Type::Unknown,
- obj)
- { }
- explicit StateEventBase(Type type)
- : RoomEvent(type)
- { }
+ using factory_t = EventFactory<StateEventBase>;
+
+ using RoomEvent::RoomEvent;
~StateEventBase() override = default;
+ bool isStateEvent() const override { return true; }
virtual bool repeatsState() const;
};
using StateEventPtr = event_ptr_tt<StateEventBase>;
using StateEvents = EventsArray<StateEventBase>;
- namespace _impl
- {
- template <>
- StateEventPtr doMakeEvent<StateEventBase>(const QJsonObject& obj);
- }
-
template <typename ContentT>
struct Prev
{
template <typename... ContentParamTs>
explicit Prev(const QJsonObject& unsignedJson,
ContentParamTs&&... contentParams)
- : senderId(unsignedJson.value("prev_sender").toString())
- , content(unsignedJson.value("prev_content").toObject(),
+ : senderId(unsignedJson.value("prev_sender"_ls).toString())
+ , content(unsignedJson.value(PrevContentKeyL).toObject(),
std::forward<ContentParamTs>(contentParams)...)
{ }
@@ -258,31 +405,33 @@ namespace QMatrixClient
using content_type = ContentT;
template <typename... ContentParamTs>
- explicit StateEvent(Type type, const QJsonObject& obj,
+ explicit StateEvent(Type type, const QJsonObject& fullJson,
ContentParamTs&&... contentParams)
- : StateEventBase(type, obj)
+ : StateEventBase(type, fullJson)
, _content(contentJson(),
std::forward<ContentParamTs>(contentParams)...)
{
- auto unsignedData = obj.value("unsigned").toObject();
- if (unsignedData.contains("prev_content"))
+ const auto& unsignedData = unsignedJson();
+ if (unsignedData.contains(PrevContentKeyL))
_prev = std::make_unique<Prev<ContentT>>(unsignedData,
- std::forward<ContentParamTs>(contentParams)...);
+ std::forward<ContentParamTs>(contentParams)...);
}
template <typename... ContentParamTs>
- explicit StateEvent(Type type, ContentParamTs&&... contentParams)
- : StateEventBase(type)
+ explicit StateEvent(Type type, event_mtype_t matrixType,
+ ContentParamTs&&... contentParams)
+ : StateEventBase(type, matrixType)
, _content(std::forward<ContentParamTs>(contentParams)...)
- { }
-
- QJsonObject toJson() const override { return _content.toJson(); }
+ {
+ editJson().insert(ContentKey, _content.toJson());
+ }
const ContentT& content() const { return _content; }
- /** @deprecated Use prevContent instead */
+ [[deprecated("Use prevContent instead")]]
const ContentT* prev_content() const { return prevContent(); }
const ContentT* prevContent() const
{ return _prev ? &_prev->content : nullptr; }
- QString prevSenderId() const { return _prev ? _prev->senderId : ""; }
+ QString prevSenderId() const
+ { return _prev ? _prev->senderId : QString(); }
protected:
ContentT _content;