From bb26ca86ad350f2562b51284e7c631b1e4f77106 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 18 May 2017 03:37:06 +0900 Subject: util.h: lookup() uses forwarding refs; added Dispatch/dispatch and REGISTER_ENUM The Dispatch<> template and dispatch(), a facility function for it, simplify dispatching to functions that have different signatures that still can be converted to the same std::function<> type. The case in point is in event.cpp; Event::fromJson calls make() that always returns the type we need; however, once we have several possible base types (Event, RoomEvent, StateEvent), we'd have to either write a specific make() incarnation for each of them, or mess with function return type conversions. Dispatch<> helps to keep the code clean. REGISTER_ENUM is a cross-Qt versions approach to dumping enumeration values to qDebug() and the likes. --- events/event.cpp | 27 ++++----- events/roommessageevent.cpp | 20 +++--- util.h | 144 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 154 insertions(+), 37 deletions(-) diff --git a/events/event.cpp b/events/event.cpp index 07649b02..5df816fe 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -87,27 +87,26 @@ QString Event::originalJson() const return d->originalJson; } -template -Event* make(const QJsonObject& obj) +template +EventT* make(const QJsonObject& obj) { - return T::fromJson(obj); + return EventT::fromJson(obj); } Event* Event::fromJson(const QJsonObject& obj) { - auto delegate = lookup(obj.value("type").toString(), - "m.room.message", make, - "m.room.name", make, - "m.room.aliases", make, - "m.room.canonical_alias", make, - "m.room.member", make, - "m.room.topic", make, - "m.typing", make, - "m.receipt", make, + return dispatch(obj).to(obj["type"].toString(), + "m.room.message", &make, + "m.room.name", &make, + "m.room.aliases", &make, + "m.room.canonical_alias", &make, + "m.room.member", &make, + "m.room.topic", &make, + "m.typing", &make, + "m.receipt", &make, /* Insert new event types BEFORE this line */ - make + &make ); - return delegate(obj); } bool Event::parseJson(const QJsonObject& obj) diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 677bb79f..d5e960a1 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -120,17 +120,17 @@ RoomMessageEvent* RoomMessageEvent::fromJson(const QJsonObject& obj) { e->d->plainBody = content["body"].toString(); - auto delegate = lookup(content.value("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, + auto delegate = 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 + &makeUnknown ); std::tie(e->d->msgtype, e->d->content) = delegate(content); } diff --git a/util.h b/util.h index 79f76860..f2a7b267 100644 --- a/util.h +++ b/util.h @@ -18,6 +18,11 @@ #pragma once +#include +#include + +#include + namespace QMatrixClient { /** @@ -71,14 +76,19 @@ namespace QMatrixClient * @brief Lookup a value by a key in a varargs list * * This function template takes the value of its first argument (selector) - * as a key and searches for it in the key-value map passed in a varargs list - * (every next pair of arguments forms a key-value pair). If a match is found, - * the respective value is returned; if no pairs matched, the last value - * (fallback) is returned. + * as a key and searches for it in the key-value map passed in + * a parameter pack (every next pair of arguments forms a key-value pair). + * If a match is found, the respective value is returned; if no pairs + * matched, the last value (fallback) is returned. * * All options should be of the same type or implicitly castable to the - * type of the first option. Note that pointers to methods of different - * classes are of different object types, in particular. + * type of the first option. If you need some specific type to cast to + * you can explicitly provide it as the ValueT template parameter + * (e.g. lookup(parameters...)). Note that pointers + * to methods of different classes and even to functions with different + * signatures are of different types. If their return types are castable + * to some common one, @see dispatch that deals with this by swallowing + * the method invocation. * * Below is an example of usage to select a parser depending on contents of * a JSON object: @@ -91,7 +101,7 @@ namespace QMatrixClient * } * * The implementation is based on tail recursion; every recursion step - * removes 2 arguments (match and option). There's no selector value for the + * removes 2 arguments (match and value). There's no selector value for the * fallback option (the last one); therefore, the total number of lookup() * arguments should be even: selector + n key-value pairs + fallback * @@ -99,20 +109,128 @@ namespace QMatrixClient * (the first parameter) - most likely it won't do what you expect because * of shallow comparison. */ + template + ValueT lookup(SelectorT/*unused*/, ValueT&& fallback) + { + return std::forward(fallback); + } + template - ValueT lookup(SelectorT selector, KeyT key, ValueT value, Ts... remainingMapping) + ValueT lookup(SelectorT&& selector, KeyT&& key, ValueT&& value, Ts&&... remainder) { if( selector == key ) - return value; + return std::forward(value); // Drop the failed key-value pair and recurse with 2 arguments less. - return lookup(selector, remainingMapping...); + return lookup(std::forward(selector), + std::forward(remainder)...); } - template - ValueT lookup(SelectorT/*unused*/, ValueT fallback) + /** + * A wrapper around lookup() for functions of different types castable + * to a common std::function<> form + * + * This class uses std::function<> magic to first capture arguments of + * a yet-unknown function or function object, and then to coerce types of + * all functions/function objects passed for lookup to the type + * std::function, you would have + * to pass the specific function type to lookup, since your functions have + * different signatures. The type is not always obvious, and the resulting + * construct in client code would almost always be rather cumbersome. + * Dispatch<> deduces the necessary function type (well, almost - you still + * have to specify the result type) and hides the clumsiness. For more + * information on what std::function<> can wrap around, see + * https://cpptruths.blogspot.jp/2015/11/covariance-and-contravariance-in-c.html + * + * The function arguments are captured by value (i.e. copied) to avoid + * hard-to-find issues with dangling references in cases when a Dispatch<> + * object is passed across different contexts (e.g. returned from another + * function). + * + * \tparam ResultT - the desired type of a picked function invocation (mandatory) + * \tparam ArgTs - function argument types (deduced) + */ + template + class Dispatch { - return fallback; + // We take a chapter from functional programming here: Dispatch<> + // uses a function that in turn accepts a function as its argument. + // The sole purpose of the outer function (initialized by + // a lambda-expression in the constructor) is to store the arguments + // to any of the functions later looked up. The inner function (its + // type is defined by fn_t alias) is the one returned by lookup() + // invocation inside to(). + // + // It's a bit counterintuitive to specify function parameters before + // the list of functions but otherwise it would take several overloads + // here to match all the ways a function-like behaviour can be done: + // reference-to-function, pointer-to-function, function object. For + // each of these overloads, we'd have to carefully retrieve the list + // of parameters and cover up possible reference-vs-value + // incompatibilities. Instead, you show what you have and if it's + // possible to bring all your functions to the same std::function<> + // based on what you have as parameters, the code will compile. If + // it's not possible, modern compilers are already good enough at + // pinpointing a specific place where types don't match. + using fn_t = std::function; + public: + explicit Dispatch(ArgTs&&... args) + : boundArgs([=](fn_t &&f) { + return f(std::forward(args)...); + }) + { } + + template + ResultT to(LookupParamTs&&... lookupParams) + { + // Here's the magic, two pieces of it: + // 1. Specifying fn_t in lookup() wraps all functions in + // \p lookupParams into the same std::function<> type. This + // includes conversion of return types from more specific to more + // generic (because std::function is covariant by return types and + // contravariant by argument types (see the link in the Doxygen + // part of the comments). + // 2. Passing the result of lookup() to boundArgs() invokes the + // lambda-expression mentioned in the constructor, which simply + // invokes this passed function with a set of arguments captured + // by lambda. + return boundArgs( + lookup(std::forward(lookupParams)...)); + } + + private: + std::function boundArgs; + }; + + /** + * Dispatch a set of parameters to one of a set of functions, depending on + * a selector value + * + * Use dispatch(parameters).to(lookup parameters) + * instead of lookup() if you need to pick one of several functions returning + * types castable to the same CommonType. See event.cpp for a typical use case. + * + * \see Dispatch + */ + template + Dispatch dispatch(ArgTs&& ... args) + { + return Dispatch(std::forward(args)...); + }; + + // The below enables pretty-printing of enums in logs +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) +#define REGISTER_ENUM(EnumName) Q_ENUM(EnumName) +#else + // Thanks to Olivier for spelling it and for making Q_ENUM to replace it: + // https://woboq.com/blog/q_enum.html +#define REGISTER_ENUM(EnumName) \ + Q_ENUMS(EnumName) \ + friend QDebug operator<<(QDebug dbg, EnumName val) \ + { \ + static int enumIdx = staticMetaObject.indexOfEnumerator(#EnumName); \ + return dbg << Event::staticMetaObject.enumerator(enumIdx).valueToKey(int(val)); \ } +#endif } // namespace QMatrixClient -- cgit v1.2.3 From f51d9ba6c4aa7d682eac7aae49a025721d298045 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 May 2017 10:10:05 +0900 Subject: Dispatch<>: Allow nullptr for a function; special-case GCC 4.8 --- util.h | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/util.h b/util.h index f2a7b267..c9e2d1c9 100644 --- a/util.h +++ b/util.h @@ -150,12 +150,18 @@ namespace QMatrixClient * \tparam ResultT - the desired type of a picked function invocation (mandatory) * \tparam ArgTs - function argument types (deduced) */ +#if __GNUC__ < 5 && __GNUC_MINOR__ < 9 + // GCC 4.8 cannot cope with parameter packs inside lambdas; so provide a single + // argument version of Dispatch<> that we only need so far. + template +#else template +#endif class Dispatch { - // We take a chapter from functional programming here: Dispatch<> - // uses a function that in turn accepts a function as its argument. - // The sole purpose of the outer function (initialized by + // The implementation takes a chapter from functional programming: + // Dispatch<> uses a function that in turn accepts a function as its + // argument. The sole purpose of the outer function (initialized by // a lambda-expression in the constructor) is to store the arguments // to any of the functions later looked up. The inner function (its // type is defined by fn_t alias) is the one returned by lookup() @@ -164,21 +170,25 @@ namespace QMatrixClient // It's a bit counterintuitive to specify function parameters before // the list of functions but otherwise it would take several overloads // here to match all the ways a function-like behaviour can be done: - // reference-to-function, pointer-to-function, function object. For - // each of these overloads, we'd have to carefully retrieve the list - // of parameters and cover up possible reference-vs-value - // incompatibilities. Instead, you show what you have and if it's - // possible to bring all your functions to the same std::function<> - // based on what you have as parameters, the code will compile. If - // it's not possible, modern compilers are already good enough at - // pinpointing a specific place where types don't match. - using fn_t = std::function; + // reference-to-function, pointer-to-function, function object. This + // probably could be done as well but I preferred a more compact + // solution: you show what you have and if it's possible to bring all + // your functions to the same std::function<> based on what you have + // as parameters, the code will compile. If it's not possible, modern + // compilers are already good enough at pinpointing a specific place + // where types don't match. public: +#if __GNUC__ < 5 && __GNUC_MINOR__ < 9 + using fn_t = std::function; + explicit Dispatch(ArgT&& arg) + : boundArgs([=](fn_t &&f) { return f(std::move(arg)); }) + { } +#else + using fn_t = std::function; explicit Dispatch(ArgTs&&... args) - : boundArgs([=](fn_t &&f) { - return f(std::forward(args)...); - }) + : boundArgs([=](fn_t &&f) { return f(std::move(args)...); }) { } +#endif template ResultT to(LookupParamTs&&... lookupParams) @@ -190,12 +200,18 @@ namespace QMatrixClient // generic (because std::function is covariant by return types and // contravariant by argument types (see the link in the Doxygen // part of the comments). + auto fn = lookup(std::forward(lookupParams)...); // 2. Passing the result of lookup() to boundArgs() invokes the // lambda-expression mentioned in the constructor, which simply // invokes this passed function with a set of arguments captured // by lambda. - return boundArgs( - lookup(std::forward(lookupParams)...)); + if (fn) + return boundArgs(std::move(fn)); + + // A shortcut to allow passing nullptr for a function; + // a default-constructed ResultT will be returned + // (for pointers, it will be nullptr) + return {}; } private: -- cgit v1.2.3 From 34764f3020c360ebc769cfe154e79b9e7e98f0f7 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 May 2017 10:17:24 +0900 Subject: Cleanup --- jobs/passwordlogin.h | 2 +- user.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jobs/passwordlogin.h b/jobs/passwordlogin.h index 713a1821..6b7db0b3 100644 --- a/jobs/passwordlogin.h +++ b/jobs/passwordlogin.h @@ -40,4 +40,4 @@ namespace QMatrixClient class Private; Private* d; }; -} +} // namespace QMatrixClient diff --git a/user.cpp b/user.cpp index 17714bee..f2a3a33b 100644 --- a/user.cpp +++ b/user.cpp @@ -133,7 +133,7 @@ void User::processEvent(Event* event) { if( event->type() == EventType::RoomMember ) { - RoomMemberEvent* e = static_cast(event); + auto e = static_cast(event); if (e->membership() == MembershipType::Leave) return; -- cgit v1.2.3 From be838b2f4f294a7e1b3f8a771f91d9d1eac14431 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 May 2017 10:22:28 +0900 Subject: Refactored Events The biggest change is we have no pimpls in Event objects anymore - because it's two new's instead of one per Event, and we have thousands and even more of Events created during initial sync. The other big change is introduction of RoomEvent, so that now the structure of events almost precisely reflects the CS API spec. The refactoring made UnknownEvent unnecessary as a separate class; a respective base class (either RoomEvent or Event) is used for this purpose now. All the other changes are consequences of these (mostly of RoomEvent introduction). --- CMakeLists.txt | 2 - connection.cpp | 2 +- connection.h | 5 +- events/event.cpp | 148 ++++++++++++--------------- events/event.h | 103 ++++++++++++++----- events/receiptevent.cpp | 40 ++------ events/receiptevent.h | 35 +++---- events/roomaliasesevent.cpp | 42 +------- events/roomaliasesevent.h | 14 +-- events/roomcanonicalaliasevent.cpp | 32 ------ events/roomcanonicalaliasevent.h | 17 ++-- events/roommemberevent.cpp | 77 +++----------- events/roommemberevent.h | 29 +++--- events/roommessageevent.cpp | 121 ++++++---------------- events/roommessageevent.h | 67 +++++++------ events/roomnameevent.cpp | 30 ------ events/roomnameevent.h | 17 ++-- events/roomtopicevent.cpp | 29 ------ events/roomtopicevent.h | 17 ++-- events/typingevent.cpp | 42 ++------ events/typingevent.h | 12 +-- examples/qmc-example.cpp | 11 +- jobs/roommessagesjob.cpp | 9 +- jobs/roommessagesjob.h | 2 +- jobs/syncjob.cpp | 6 -- jobs/syncjob.h | 55 +++++----- room.cpp | 201 +++++++++++++++++++------------------ room.h | 24 ++--- 28 files changed, 470 insertions(+), 719 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11cf015d..ad7c5a34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,6 @@ set(libqmatrixclient_SRCS logging.cpp room.cpp user.cpp - state.cpp settings.cpp events/event.cpp events/roommessageevent.cpp @@ -61,7 +60,6 @@ set(libqmatrixclient_SRCS events/roomtopicevent.cpp events/typingevent.cpp events/receiptevent.cpp - events/unknownevent.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp jobs/passwordlogin.cpp diff --git a/connection.cpp b/connection.cpp index f9e2e7ae..56628a07 100644 --- a/connection.cpp +++ b/connection.cpp @@ -191,7 +191,7 @@ void Connection::postMessage(Room* room, const QString& type, const QString& mes callApi(room->id(), type, message); } -PostReceiptJob* Connection::postReceipt(Room* room, Event* event) const +PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { return callApi(room->id(), event->id()); } diff --git a/connection.h b/connection.h index 08184d0d..f0b097fd 100644 --- a/connection.h +++ b/connection.h @@ -26,7 +26,7 @@ namespace QMatrixClient { class Room; class User; - class Event; + class RoomEvent; class ConnectionPrivate; class ConnectionData; @@ -61,7 +61,8 @@ namespace QMatrixClient Q_INVOKABLE virtual void postMessage(Room* room, const QString& type, const QString& message) const; /** @deprecated Use callApi() or Room::postReceipt() instead */ - Q_INVOKABLE virtual PostReceiptJob* postReceipt( Room* room, Event* event ) const; + Q_INVOKABLE virtual PostReceiptJob* postReceipt(Room* room, + RoomEvent* event) const; Q_INVOKABLE virtual JoinRoomJob* joinRoom(const QString& roomAlias); /** @deprecated Use callApi() or Room::leaveRoom() instead */ Q_INVOKABLE virtual void leaveRoom( Room* room ); diff --git a/events/event.cpp b/events/event.cpp index 5df816fe..bd7e1b03 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -18,9 +18,6 @@ #include "event.h" -#include -#include - #include "roommessageevent.h" #include "roomnameevent.h" #include "roomaliasesevent.h" @@ -31,120 +28,101 @@ #include "receiptevent.h" #include "unknownevent.h" #include "logging.h" -#include "util.h" - -using namespace QMatrixClient; - -class Event::Private -{ - public: - EventType type; - QString id; - QDateTime timestamp; - QString roomId; - QString senderId; - QString originalJson; -}; -Event::Event(EventType type) - : d(new Private) -{ - d->type = type; -} +#include -Event::~Event() -{ - delete d; -} +using namespace QMatrixClient; -EventType Event::type() const +Event::Event(Type type, const QJsonObject& rep) + : _type(type), _originalJson(rep) { - return d->type; + if (!rep.contains("content")) + { + qCWarning(EVENTS) << "Event without 'content' node"; + qCWarning(EVENTS) << formatJson << rep; + } } -QString Event::id() const +QByteArray Event::originalJson() const { - return d->id; + return QJsonDocument(_originalJson).toJson(); } -QDateTime Event::timestamp() const +QDateTime Event::toTimestamp(const QJsonValue& v) { - return d->timestamp; + Q_ASSERT(v.isDouble()); + return QDateTime::fromMSecsSinceEpoch( + static_cast(v.toDouble()), Qt::UTC); } -QString Event::roomId() const +QStringList Event::toStringList(const QJsonValue& v) { - return d->roomId; -} + Q_ASSERT(v.isArray()); -QString Event::senderId() const -{ - return d->senderId; + QStringList l; + for( const QJsonValue& e : v.toArray() ) + l.push_back(e.toString()); + return l; } -QString Event::originalJson() const +const QJsonObject Event::contentJson() const { - return d->originalJson; + return _originalJson["content"].toObject(); } template -EventT* make(const QJsonObject& obj) +EventT* make(const QJsonObject& o) { - return EventT::fromJson(obj); + return new EventT(o); } Event* Event::fromJson(const QJsonObject& obj) { + // Check more specific event types first + if (auto e = RoomEvent::fromJson(obj)) + return e; + return dispatch(obj).to(obj["type"].toString(), - "m.room.message", &make, - "m.room.name", &make, - "m.room.aliases", &make, - "m.room.canonical_alias", &make, - "m.room.member", &make, - "m.room.topic", &make, - "m.typing", &make, - "m.receipt", &make, - /* Insert new event types BEFORE this line */ - &make - ); + "m.typing", make, + "m.receipt", make, + /* Insert new event types (except room events) BEFORE this line */ + nullptr + ); } -bool Event::parseJson(const QJsonObject& obj) +RoomEvent::RoomEvent(Type type, const QJsonObject& rep) + : Event(type, rep), _id(rep["event_id"].toString()) + , _serverTimestamp(toTimestamp(rep["origin_server_ts"])) + , _roomId(rep["room_id"].toString()) + , _senderId(rep["sender"].toString()) { - d->originalJson = QString::fromUtf8(QJsonDocument(obj).toJson()); - d->id = obj.value("event_id").toString(); - d->roomId = obj.value("room_id").toString(); - d->senderId = obj.value("sender").toString(); - bool correct = (d->type != EventType::Unknown); - if ( d->type != EventType::Typing && - d->type != EventType::Receipt ) + if (_id.isEmpty()) { - if (d->id.isEmpty()) - { - correct = false; - qCDebug(EVENTS) << "Event: can't find event_id; event dump follows"; - qCDebug(EVENTS) << formatJson << obj; - } - if( obj.contains("origin_server_ts") ) - { - d->timestamp = QDateTime::fromMSecsSinceEpoch( - static_cast(obj.value("origin_server_ts").toDouble()), Qt::UTC ); - } - else if (d->type != EventType::Unknown) - { - correct = false; - qCDebug(EVENTS) << "Event: can't find ts; event dump follows"; - qCDebug(EVENTS) << formatJson << obj; - } + qCWarning(EVENTS) << "Can't find event_id in a room event"; + qCWarning(EVENTS) << formatJson << rep; + } + if (!rep.contains("origin_server_ts")) + { + qCWarning(EVENTS) << "Event: can't find server timestamp in a room event"; + qCWarning(EVENTS) << formatJson << rep; + } + if (_senderId.isEmpty()) + { + qCWarning(EVENTS) << "user_id not found in a room event"; + qCWarning(EVENTS) << formatJson << rep; } - return correct; } -Events QMatrixClient::eventsFromJson(const QJsonArray& json) +RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) { - Events evs; - evs.reserve(json.size()); - for (auto event: json) - evs.push_back(Event::fromJson(event.toObject())); - return evs; + return dispatch(obj).to(obj["type"].toString(), + "m.room.message", make, + "m.room.name", make, + "m.room.aliases", make, + "m.room.canonical_alias", make, + "m.room.member", make, + "m.room.topic", make, + /* Insert new ROOM event types BEFORE this line */ + nullptr + ); } diff --git a/events/event.h b/events/event.h index f60dfb64..fd2f6feb 100644 --- a/events/event.h +++ b/events/event.h @@ -21,43 +21,92 @@ #include #include #include -#include +#include -class QJsonArray; +#include "util.h" namespace QMatrixClient { - enum class EventType - { - RoomMessage, RoomName, RoomAliases, RoomCanonicalAlias, - RoomMember, RoomTopic, Typing, Receipt, Unknown - }; - class Event { + Q_GADGET public: - explicit Event(EventType type); - Event(Event&) = delete; - virtual ~Event(); - - EventType type() const; - QString id() const; - QDateTime timestamp() const; - QString roomId() const; - QString senderId() const; - // only for debug purposes! - QString originalJson() const; + enum class Type + { + RoomMessage, RoomName, RoomAliases, RoomCanonicalAlias, + RoomMember, RoomTopic, Typing, Receipt, Unknown + }; + + explicit Event(Type type, const QJsonObject& rep); + Event(const Event&) = delete; + + Type type() const { return _type; } + QByteArray originalJson() const; + + // 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). static Event* fromJson(const QJsonObject& obj); - + protected: - bool parseJson(const QJsonObject& obj); - + static QDateTime toTimestamp(const QJsonValue& v); + static QStringList toStringList(const QJsonValue& v); + + const QJsonObject contentJson() const; + private: - class Private; - Private* d; + Type _type; + QJsonObject _originalJson; + + REGISTER_ENUM(Type) }; - using Events = QVector; + using EventType = Event::Type; + template + using EventsBatch = std::vector; + using Events = EventsBatch; + + template + BaseEventT* makeEvent(const QJsonObject& obj) + { + if (auto e = BaseEventT::fromJson(obj)) + return e; + + return new BaseEventT(EventType::Unknown, obj); + } + + template > + BatchT makeEvents(const QJsonArray& objs) + { + BatchT evs; + // The below line accommodates the difference in size types of + // STL and Qt containers. + evs.reserve(static_cast(objs.size())); + for (auto obj: objs) + evs.push_back(makeEvent(obj.toObject())); + return evs; + } - Events eventsFromJson(const QJsonArray& json); -} + class RoomEvent : public Event + { + public: + RoomEvent(Type type, const QJsonObject& rep); + + const QString& id() const { return _id; } + const QDateTime& timestamp() const { return _serverTimestamp; } + const QString& roomId() const { return _roomId; } + const QString& senderId() const { return _senderId; } + + // "Static override" of the one in Event + static RoomEvent* fromJson(const QJsonObject& obj); + + private: + QString _id; + QDateTime _serverTimestamp; + QString _roomId; + QString _senderId; + }; + using RoomEvents = EventsBatch; +} // namespace QMatrixClient diff --git a/events/receiptevent.cpp b/events/receiptevent.cpp index c163424f..e3478cf1 100644 --- a/events/receiptevent.cpp +++ b/events/receiptevent.cpp @@ -41,34 +41,13 @@ Example of a Receipt Event: using namespace QMatrixClient; -class ReceiptEvent::Private +ReceiptEvent::ReceiptEvent(const QJsonObject& obj) + : Event(Type::Receipt, obj) { - public: - EventsToReceipts eventsToReceipts; -}; + Q_ASSERT(obj["type"].toString() == jsonType); -ReceiptEvent::ReceiptEvent() - : Event(EventType::Receipt) - , d(new Private) -{ -} - -ReceiptEvent::~ReceiptEvent() -{ - delete d; -} - -EventsToReceipts ReceiptEvent::events() const -{ - return d->eventsToReceipts; -} - -ReceiptEvent* ReceiptEvent::fromJson(const QJsonObject& obj) -{ - ReceiptEvent* e = new ReceiptEvent(); - e->parseJson(obj); const QJsonObject contents = obj["content"].toObject(); - e->d->eventsToReceipts.reserve(contents.size()); + _eventsWithReceipts.reserve(static_cast(contents.size())); for( auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt ) { if (eventIt.key().isEmpty()) @@ -78,15 +57,14 @@ ReceiptEvent* ReceiptEvent::fromJson(const QJsonObject& obj) continue; } const QJsonObject reads = eventIt.value().toObject().value("m.read").toObject(); - Receipts receipts; receipts.reserve(reads.size()); + std::vector receipts; + receipts.reserve(static_cast(reads.size())); for( auto userIt = reads.begin(); userIt != reads.end(); ++userIt ) { const QJsonObject user = userIt.value().toObject(); - const auto time = QDateTime::fromMSecsSinceEpoch( - static_cast(user["ts"].toDouble()), Qt::UTC ); - receipts.push_back({ userIt.key(), time }); + receipts.push_back({userIt.key(), toTimestamp(user["ts"])}); } - e->d->eventsToReceipts.push_back({ eventIt.key(), receipts }); + _eventsWithReceipts.push_back({eventIt.key(), receipts}); } - return e; } + diff --git a/events/receiptevent.h b/events/receiptevent.h index 40c0384f..1d280822 100644 --- a/events/receiptevent.h +++ b/events/receiptevent.h @@ -22,32 +22,29 @@ namespace QMatrixClient { - class Receipt + struct Receipt { - public: - QString userId; - QDateTime timestamp; + QString userId; + QDateTime timestamp; }; -} -Q_DECLARE_TYPEINFO(QMatrixClient::Receipt, Q_MOVABLE_TYPE); - -namespace QMatrixClient -{ - using Receipts = QVector; - using EventsToReceipts = QVector< QPair >; + struct ReceiptsForEvent + { + QString evtId; + std::vector receipts; + }; + using EventsWithReceipts = std::vector; class ReceiptEvent: public Event { public: - ReceiptEvent(); - virtual ~ReceiptEvent(); + explicit ReceiptEvent(const QJsonObject& obj); - EventsToReceipts events() const; - - static ReceiptEvent* fromJson(const QJsonObject& obj); + EventsWithReceipts eventsWithReceipts() const + { return _eventsWithReceipts; } private: - class Private; - Private* d; + EventsWithReceipts _eventsWithReceipts; + + static constexpr const char * jsonType = "m.receipt"; }; -} +} // namespace QMatrixClient diff --git a/events/roomaliasesevent.cpp b/events/roomaliasesevent.cpp index ab414498..344b4367 100644 --- a/events/roomaliasesevent.cpp +++ b/events/roomaliasesevent.cpp @@ -34,44 +34,10 @@ #include "roomaliasesevent.h" -#include "logging.h" - -#include - using namespace QMatrixClient; -class RoomAliasesEvent::Private -{ - public: - QStringList aliases; -}; - -RoomAliasesEvent::RoomAliasesEvent() - : Event(EventType::RoomAliases) - , d(new Private) -{ -} - -RoomAliasesEvent::~RoomAliasesEvent() -{ - delete d; -} - -QStringList RoomAliasesEvent::aliases() const -{ - return d->aliases; -} +RoomAliasesEvent::RoomAliasesEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomAliases, obj) + , _aliases(toStringList(contentJson()["aliases"])) +{ } -RoomAliasesEvent* RoomAliasesEvent::fromJson(const QJsonObject& obj) -{ - RoomAliasesEvent* e = new RoomAliasesEvent(); - e->parseJson(obj); - const QJsonObject contents = obj.value("content").toObject(); - const QJsonArray aliases = contents.value("aliases").toArray(); - for( const QJsonValue& alias : aliases ) - { - e->d->aliases << alias.toString(); - } - qCDebug(EVENTS) << "RoomAliasesEvent:" << e->d->aliases; - return e; -} diff --git a/events/roomaliasesevent.h b/events/roomaliasesevent.h index 8f638be2..efafcb30 100644 --- a/events/roomaliasesevent.h +++ b/events/roomaliasesevent.h @@ -24,18 +24,14 @@ namespace QMatrixClient { - class RoomAliasesEvent: public Event + class RoomAliasesEvent: public RoomEvent { public: - RoomAliasesEvent(); - virtual ~RoomAliasesEvent(); + explicit RoomAliasesEvent(const QJsonObject& obj); - QStringList aliases() const; - - static RoomAliasesEvent* fromJson(const QJsonObject& obj); + QStringList aliases() const { return _aliases; } private: - class Private; - Private* d; + QStringList _aliases; }; -} +} // namespace QMatrixClient diff --git a/events/roomcanonicalaliasevent.cpp b/events/roomcanonicalaliasevent.cpp index d84c07fc..6884bc15 100644 --- a/events/roomcanonicalaliasevent.cpp +++ b/events/roomcanonicalaliasevent.cpp @@ -19,35 +19,3 @@ #include "roomcanonicalaliasevent.h" using namespace QMatrixClient; - -class RoomCanonicalAliasEvent::Private -{ - public: - QString alias; -}; - -RoomCanonicalAliasEvent::RoomCanonicalAliasEvent() - : Event(EventType::RoomCanonicalAlias) - , d(new Private) -{ -} - -RoomCanonicalAliasEvent::~RoomCanonicalAliasEvent() -{ - delete d; -} - -QString RoomCanonicalAliasEvent::alias() -{ - return d->alias; -} - -RoomCanonicalAliasEvent* RoomCanonicalAliasEvent::fromJson(const QJsonObject& obj) -{ - RoomCanonicalAliasEvent* e = new RoomCanonicalAliasEvent(); - e->parseJson(obj); - const QJsonObject contents = obj.value("content").toObject(); - e->d->alias = contents.value("alias").toString(); - return e; -} - diff --git a/events/roomcanonicalaliasevent.h b/events/roomcanonicalaliasevent.h index 87219be6..72620d74 100644 --- a/events/roomcanonicalaliasevent.h +++ b/events/roomcanonicalaliasevent.h @@ -22,18 +22,17 @@ namespace QMatrixClient { - class RoomCanonicalAliasEvent: public Event + class RoomCanonicalAliasEvent : public RoomEvent { public: - RoomCanonicalAliasEvent(); - virtual ~RoomCanonicalAliasEvent(); + explicit RoomCanonicalAliasEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomCanonicalAlias, obj) + , _canonicalAlias(contentJson()["alias"].toString()) + { } - QString alias(); - - static RoomCanonicalAliasEvent* fromJson(const QJsonObject& obj); + QString alias() const { return _canonicalAlias; } private: - class Private; - Private* d; + QString _canonicalAlias; }; -} +} // namespace QMatrixClient diff --git a/events/roommemberevent.cpp b/events/roommemberevent.cpp index 51dbbbab..5973acc7 100644 --- a/events/roommemberevent.cpp +++ b/events/roommemberevent.cpp @@ -22,66 +22,19 @@ using namespace QMatrixClient; -class RoomMemberEvent::Private -{ - public: - MembershipType membership; - QString userId; - QString displayname; - QUrl avatarUrl; -}; - -RoomMemberEvent::RoomMemberEvent() - : Event(EventType::RoomMember) - , d(new Private) -{ -} - -RoomMemberEvent::~RoomMemberEvent() -{ - delete d; -} - -MembershipType RoomMemberEvent::membership() const -{ - return d->membership; -} - -QString RoomMemberEvent::userId() const -{ - return d->userId; -} - -QString RoomMemberEvent::displayName() const -{ - return d->displayname; -} - -QUrl RoomMemberEvent::avatarUrl() const -{ - return d->avatarUrl; -} - -RoomMemberEvent* RoomMemberEvent::fromJson(const QJsonObject& obj) -{ - RoomMemberEvent* e = new RoomMemberEvent(); - e->parseJson(obj); - e->d->userId = obj.value("state_key").toString(); - QJsonObject content = obj.value("content").toObject(); - e->d->displayname = content.value("displayname").toString(); - QString membershipString = content.value("membership").toString(); - if( membershipString == "invite" ) - e->d->membership = MembershipType::Invite; - else if( membershipString == "join" ) - e->d->membership = MembershipType::Join; - else if( membershipString == "knock" ) - e->d->membership = MembershipType::Knock; - else if( membershipString == "leave" ) - e->d->membership = MembershipType::Leave; - else if( membershipString == "ban" ) - e->d->membership = MembershipType::Ban; - else - qCDebug(EVENTS) << "Unknown MembershipType: " << membershipString; - e->d->avatarUrl = QUrl(content.value("avatar_url").toString()); - return e; +RoomMemberEvent::RoomMemberEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomMember, obj), _userId(obj["state_key"].toString()) +{ + const auto contentObj = contentJson(); + _displayName = contentObj["displayname"].toString(); + _avatarUrl = contentObj["avatar_url"].toString(); + QString membershipString = contentObj["membership"].toString(); + const auto supportedStrings = { "invite", "join", "knock", "leave", "ban" }; + for (auto it = supportedStrings.begin(); it != supportedStrings.end(); ++it) + if (membershipString == *it) + { + _membership = MembershipType(it - supportedStrings.begin()); + return; + } + qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString; } diff --git a/events/roommemberevent.h b/events/roommemberevent.h index a33c2982..9ebb75ee 100644 --- a/events/roommemberevent.h +++ b/events/roommemberevent.h @@ -24,23 +24,26 @@ namespace QMatrixClient { - enum class MembershipType {Invite, Join, Knock, Leave, Ban}; - - class RoomMemberEvent: public Event + class RoomMemberEvent: public RoomEvent { + Q_GADGET public: - RoomMemberEvent(); - virtual ~RoomMemberEvent(); + enum MembershipType : int {Invite = 0, Join, Knock, Leave, Ban}; - MembershipType membership() const; - QString userId() const; - QString displayName() const; - QUrl avatarUrl() const; + explicit RoomMemberEvent(const QJsonObject& obj); - static RoomMemberEvent* fromJson(const QJsonObject& obj); + MembershipType membership() const { return _membership; } + const QString& userId() const { return _userId; } + const QString& displayName() const { return _displayName; } + const QUrl& avatarUrl() const { return _avatarUrl; } private: - class Private; - Private* d; + MembershipType _membership; + QString _userId; + QString _displayName; + QUrl _avatarUrl; + + REGISTER_ENUM(MembershipType) }; -} + using MembershipType = RoomMemberEvent::MembershipType; +} // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index d5e960a1..49bb4053 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -19,64 +19,15 @@ #include "roommessageevent.h" #include "logging.h" -#include "util.h" #include using namespace QMatrixClient; - -class RoomMessageEvent::Private -{ - public: - Private() : msgtype(MessageEventType::Unknown), content(nullptr) {} - ~Private() { if (content) delete content; } - - QString userId; - MessageEventType msgtype; - QString plainBody; - MessageEventContent::Base* content; -}; - -RoomMessageEvent::RoomMessageEvent() - : Event(EventType::RoomMessage) - , d(new Private) -{ } - -RoomMessageEvent::~RoomMessageEvent() -{ - delete d; -} - -QString RoomMessageEvent::userId() const -{ - return d->userId; -} - -MessageEventType RoomMessageEvent::msgtype() const -{ - return d->msgtype; -} - -QString RoomMessageEvent::plainBody() const -{ - return d->plainBody; -} - -QString RoomMessageEvent::body() const -{ - return plainBody(); -} - using namespace MessageEventContent; -Base* RoomMessageEvent::content() const -{ - return d->content; -} - -using ContentPair = std::pair; +using ContentPair = std::pair; -template +template ContentPair make(const QJsonObject& json) { return { EnumType, new ContentT(json) }; @@ -91,59 +42,53 @@ ContentPair makeVideo(const QJsonObject& json) if (infoJson.contains("thumbnail_url")) { c->thumbnail = ImageInfo(infoJson["thumbnail_url"].toString(), - infoJson["thumbnail_info"].toObject()); + infoJson["thumbnail_info"].toObject()); } - return { MessageEventType::Video, c }; + return { CType::Video, c }; }; ContentPair makeUnknown(const QJsonObject& json) { qCDebug(EVENTS) << "RoomMessageEvent: couldn't resolve msgtype, JSON follows:"; qCDebug(EVENTS) << json; - return { MessageEventType::Unknown, new Base }; + return { CType::Unknown, new Base() }; } -RoomMessageEvent* RoomMessageEvent::fromJson(const QJsonObject& obj) +RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomMessage, obj), _msgtype(CType::Unknown) + , _content(nullptr) { - RoomMessageEvent* e = new RoomMessageEvent(); - e->parseJson(obj); - if( obj.contains("sender") ) + const QJsonObject content = contentJson(); + if ( content.contains("msgtype") && content.contains("body") ) { - e->d->userId = obj.value("sender").toString(); - } else { - qCDebug(EVENTS) << "RoomMessageEvent: user_id not found"; + _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); } - if( obj.contains("content") ) + else { - const QJsonObject content = obj["content"].toObject(); - if ( content.contains("msgtype") && content.contains("body") ) - { - e->d->plainBody = content["body"].toString(); - - auto delegate = 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(e->d->msgtype, e->d->content) = delegate(content); - } - else - { - qCWarning(EVENTS) << "RoomMessageEvent(" << e->id() << "): no body or msgtype"; - qCDebug(EVENTS) << obj; - } + qCWarning(EVENTS) << "No body or msgtype in room message event"; + qCWarning(EVENTS) << formatJson << obj; } - return e; } -using namespace MessageEventContent; +RoomMessageEvent::~RoomMessageEvent() +{ + if (_content) + delete _content; +} TextContent::TextContent(const QJsonObject& json) { diff --git a/events/roommessageevent.h b/events/roommessageevent.h index 5d5336aa..6acaad6f 100644 --- a/events/roommessageevent.h +++ b/events/roommessageevent.h @@ -24,44 +24,45 @@ #include #include +#include + namespace QMatrixClient { - enum class MessageEventType - { - Text, Emote, Notice, Image, File, Location, Video, Audio, Unknown - }; - namespace MessageEventContent { - class Base { }; - } - - class RoomMessageEvent: public Event - { - public: - RoomMessageEvent(); - virtual ~RoomMessageEvent(); - - QString userId() const; - MessageEventType msgtype() const; + class Base + { + Q_GADGET + public: + enum class Type + { + Text, Emote, Notice, Image, File, Location, Video, Audio, Unknown + }; - QString plainBody() const; + virtual ~Base() = default; - /** - * Same as plainBody() for now; might change for "best-looking body" - * in the future. For richer contents, use content-specific data. - * - * @deprecated - */ - QString body() const; + REGISTER_ENUM(Type) + }; + using CType = Base::Type; + } // namespace MessageEventContent + using MessageEventType = MessageEventContent::CType; - MessageEventContent::Base* content() const; + class RoomMessageEvent: public RoomEvent + { + public: + explicit RoomMessageEvent(const QJsonObject& obj); + ~RoomMessageEvent(); - static RoomMessageEvent* fromJson( const QJsonObject& obj ); + const QString& userId() const { return _userId; } + MessageEventType msgtype() const { return _msgtype; } + const QString& plainBody() const { return _plainBody; } + const MessageEventContent::Base* content() const { return _content; } private: - class Private; - Private* d; + QString _userId; + MessageEventType _msgtype; + QString _plainBody; + MessageEventContent::Base* _content; }; namespace MessageEventContent @@ -73,7 +74,7 @@ namespace QMatrixClient class TextContent: public Base { public: - TextContent(const QJsonObject& json); + explicit TextContent(const QJsonObject& json); QMimeType mimeType; QString body; @@ -103,7 +104,7 @@ namespace QMatrixClient class ThumbnailedContent: public ContentInfoT { public: - ThumbnailedContent(const QJsonObject& json) + explicit ThumbnailedContent(const QJsonObject& json) : ContentInfoT(json["url"].toString(), json["info"].toObject()) , thumbnail(json["thumbnail_url"].toString(), json["thumbnail_info"].toObject()) @@ -118,7 +119,7 @@ namespace QMatrixClient class LocationContent: public Base { public: - LocationContent(const QJsonObject& json); + explicit LocationContent(const QJsonObject& json); QString geoUri; ImageInfo thumbnail; @@ -142,5 +143,5 @@ namespace QMatrixClient int duration; }; using AudioContent = ThumbnailedContent; - } -} + } // namespace MessageEventContent +} // namespace QMatrixClient diff --git a/events/roomnameevent.cpp b/events/roomnameevent.cpp index c94cb2c3..c202d17a 100644 --- a/events/roomnameevent.cpp +++ b/events/roomnameevent.cpp @@ -20,33 +20,3 @@ using namespace QMatrixClient; -class RoomNameEvent::Private -{ - public: - QString name; -}; - -RoomNameEvent::RoomNameEvent() : - Event(EventType::RoomName), - d(new Private) -{ -} - -RoomNameEvent::~RoomNameEvent() -{ - delete d; -} - -QString RoomNameEvent::name() const -{ - return d->name; -} - -RoomNameEvent* RoomNameEvent::fromJson(const QJsonObject& obj) -{ - RoomNameEvent* e = new RoomNameEvent(); - e->parseJson(obj); - const QJsonObject contents = obj.value("content").toObject(); - e->d->name = contents.value("name").toString(); - return e; -} diff --git a/events/roomnameevent.h b/events/roomnameevent.h index 8748c4be..bb823933 100644 --- a/events/roomnameevent.h +++ b/events/roomnameevent.h @@ -22,18 +22,17 @@ namespace QMatrixClient { - class RoomNameEvent : public Event + class RoomNameEvent : public RoomEvent { public: - RoomNameEvent(); - virtual ~RoomNameEvent(); + explicit RoomNameEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomName, obj) + , _name(contentJson()["name"].toString()) + { } - QString name() const; - - static RoomNameEvent* fromJson(const QJsonObject& obj); + QString name() const { return _name; } private: - class Private; - Private *d; + QString _name{}; }; -} +} // namespace QMatrixClient diff --git a/events/roomtopicevent.cpp b/events/roomtopicevent.cpp index 2e186c4b..26677e78 100644 --- a/events/roomtopicevent.cpp +++ b/events/roomtopicevent.cpp @@ -20,32 +20,3 @@ using namespace QMatrixClient; -class RoomTopicEvent::Private -{ - public: - QString topic; -}; - -RoomTopicEvent::RoomTopicEvent() - : Event(EventType::RoomTopic) - , d(new Private) -{ -} - -RoomTopicEvent::~RoomTopicEvent() -{ - delete d; -} - -QString RoomTopicEvent::topic() const -{ - return d->topic; -} - -RoomTopicEvent* RoomTopicEvent::fromJson(const QJsonObject& obj) -{ - auto e = new RoomTopicEvent(); - e->parseJson(obj); - e->d->topic = obj.value("content").toObject().value("topic").toString(); - return e; -} diff --git a/events/roomtopicevent.h b/events/roomtopicevent.h index 4b0a24b0..fb849afe 100644 --- a/events/roomtopicevent.h +++ b/events/roomtopicevent.h @@ -22,18 +22,17 @@ namespace QMatrixClient { - class RoomTopicEvent: public Event + class RoomTopicEvent: public RoomEvent { public: - RoomTopicEvent(); - virtual ~RoomTopicEvent(); + explicit RoomTopicEvent(const QJsonObject& obj) + : RoomEvent(Type::RoomTopic, obj) + , _topic(contentJson()["topic"].toString()) + { } - QString topic() const; - - static RoomTopicEvent* fromJson(const QJsonObject& obj); + QString topic() const { return _topic; } private: - class Private; - Private* d; + QString _topic; }; -} +} // namespace QMatrixClient diff --git a/events/typingevent.cpp b/events/typingevent.cpp index 009059af..a4d3bae4 100644 --- a/events/typingevent.cpp +++ b/events/typingevent.cpp @@ -18,43 +18,15 @@ #include "typingevent.h" -#include "logging.h" - -#include - using namespace QMatrixClient; -class TypingEvent::Private -{ - public: - QStringList users; -}; - -TypingEvent::TypingEvent() - : Event(EventType::Typing) - , d( new Private ) -{ -} - -TypingEvent::~TypingEvent() +TypingEvent::TypingEvent(const QJsonObject& obj) + : Event(Type::Typing, obj) { - delete d; -} - -QStringList TypingEvent::users() -{ - return d->users; -} - -TypingEvent* TypingEvent::fromJson(const QJsonObject& obj) -{ - TypingEvent* e = new TypingEvent(); - e->parseJson(obj); - QJsonArray array = obj.value("content").toObject().value("user_ids").toArray(); + QJsonValue result; + result= contentJson()["user_ids"]; + QJsonArray array = result.toArray(); for( const QJsonValue& user: array ) - { - e->d->users << user.toString(); - } - qCDebug(EPHEMERAL) << "Typing:" << e->d->users; - return e; + _users.push_back(user.toString()); } + diff --git a/events/typingevent.h b/events/typingevent.h index da57a389..b12d224e 100644 --- a/events/typingevent.h +++ b/events/typingevent.h @@ -27,15 +27,11 @@ namespace QMatrixClient class TypingEvent: public Event { public: - TypingEvent(); - virtual ~TypingEvent(); + TypingEvent(const QJsonObject& obj); - QStringList users(); - - static TypingEvent* fromJson(const QJsonObject& obj); + QStringList users() const { return _users; } private: - class Private; - Private* d; + QStringList _users; }; -} +} // namespace QMatrixClient diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index 7b45a785..a6da6aba 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -1,12 +1,15 @@ -#include -#include #include "connection.h" #include "room.h" +#include +#include +#include + using namespace QMatrixClient; using std::cout; using std::endl; +using std::string; void onNewRoom(Room* r) { @@ -16,11 +19,11 @@ void onNewRoom(Room* r) cout << " Name: " << r->name().toStdString() << endl; cout << " Canonical alias: " << r->canonicalAlias().toStdString() << endl; }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (Events evs) { + QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEvents evs) { cout << "New events in room " << r->id().toStdString() << ":" << endl; for (auto e: evs) { - cout << e->originalJson().toStdString() << endl; + cout << string(e->originalJson()) << endl; } }); } diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp index a48403c8..3e603a50 100644 --- a/jobs/roommessagesjob.cpp +++ b/jobs/roommessagesjob.cpp @@ -17,16 +17,15 @@ */ #include "roommessagesjob.h" -#include "../util.h" -#include +#include "util.h" using namespace QMatrixClient; class RoomMessagesJob::Private { public: - Owning events; + Owning events; QString end; }; @@ -49,7 +48,7 @@ RoomMessagesJob::~RoomMessagesJob() delete d; } -Events RoomMessagesJob::releaseEvents() +RoomEvents RoomMessagesJob::releaseEvents() { return d->events.release(); } @@ -62,7 +61,7 @@ QString RoomMessagesJob::end() BaseJob::Status RoomMessagesJob::parseJson(const QJsonDocument& data) { QJsonObject obj = data.object(); - d->events.assign(eventsFromJson(obj.value("chunk").toArray())); + d->events.assign(makeEvents(obj.value("chunk").toArray())); d->end = obj.value("end").toString(); return Success; } diff --git a/jobs/roommessagesjob.h b/jobs/roommessagesjob.h index 2d15d9d4..a029c27c 100644 --- a/jobs/roommessagesjob.h +++ b/jobs/roommessagesjob.h @@ -34,7 +34,7 @@ namespace QMatrixClient FetchDirection dir = FetchDirection::Backward); virtual ~RoomMessagesJob(); - Events releaseEvents(); + RoomEvents releaseEvents(); QString end(); protected: diff --git a/jobs/syncjob.cpp b/jobs/syncjob.cpp index 5984128f..29ddc2e6 100644 --- a/jobs/syncjob.cpp +++ b/jobs/syncjob.cpp @@ -18,7 +18,6 @@ #include "syncjob.h" -#include #include using namespace QMatrixClient; @@ -96,11 +95,6 @@ BaseJob::Status SyncJob::parseJson(const QJsonDocument& data) return Success; } -void SyncRoomData::EventList::fromJson(const QJsonObject& roomContents) -{ - assign(eventsFromJson(roomContents[jsonKey].toObject()["events"].toArray())); -} - SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, const QJsonObject& room_) : roomId(roomId_) diff --git a/jobs/syncjob.h b/jobs/syncjob.h index 48be9423..07824e23 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -20,39 +20,46 @@ #include "basejob.h" -#include "../joinstate.h" -#include "../events/event.h" +#include "joinstate.h" +#include "events/event.h" #include "util.h" namespace QMatrixClient { class SyncRoomData { - public: - class EventList : public Owning - { - private: - QString jsonKey; - public: - explicit EventList(QString k) : jsonKey(std::move(k)) { } - void fromJson(const QJsonObject& roomContents); - }; + public: + template + class Batch : public Owning> + { + public: + explicit Batch(QString k) : jsonKey(std::move(k)) { } + void fromJson(const QJsonObject& roomContents) + { + this->assign(makeEvents( + roomContents[jsonKey].toObject()["events"].toArray())); + } + + + private: + QString jsonKey; + }; - QString roomId; - JoinState joinState; - EventList state; - EventList timeline; - EventList ephemeral; - EventList accountData; - EventList inviteState; + QString roomId; + JoinState joinState; + Batch state; + Batch timeline; + Batch ephemeral; + Batch accountData; + Batch inviteState; - bool timelineLimited; - QString timelinePrevBatch; - int highlightCount; - int notificationCount; + bool timelineLimited; + QString timelinePrevBatch; + int highlightCount; + int notificationCount; - SyncRoomData(const QString& roomId, JoinState joinState_, - const QJsonObject& room_); + SyncRoomData(const QString& roomId, JoinState joinState_, + const QJsonObject& room_); }; } // namespace QMatrixClient Q_DECLARE_TYPEINFO(QMatrixClient::SyncRoomData, Q_MOVABLE_TYPE); diff --git a/room.cpp b/room.cpp index 9f57f3fd..6c22b338 100644 --- a/room.cpp +++ b/room.cpp @@ -96,18 +96,18 @@ class Room::Private void getPreviousContent(int limit = 10); - bool isEventNotable(const Event* e) const + bool isEventNotable(const RoomEvent* e) const { return e->senderId() != connection->userId() && e->type() == EventType::RoomMessage; } - void appendEvent(Event* e) + void appendEvent(RoomEvent* e) { insertEvent(e, timeline.end(), timeline.empty() ? 0 : q->maxTimelineIndex() + 1); } - void prependEvent(Event* e) + void prependEvent(RoomEvent* e) { insertEvent(e, timeline.begin(), timeline.empty() ? 0 : q->minTimelineIndex() - 1); @@ -116,7 +116,7 @@ class Room::Private /** * Removes events from the passed container that are already in the timeline */ - void dropDuplicateEvents(Events* events) const; + void dropDuplicateEvents(RoomEvents* events) const; void setLastReadEvent(User* u, const QString& eventId); rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker); @@ -128,7 +128,7 @@ class Room::Private void insertMemberIntoMap(User* u); void removeMemberFromMap(const QString& username, User* u); - void insertEvent(Event* e, Timeline::iterator where, + void insertEvent(RoomEvent* e, Timeline::iterator where, TimelineItem::index_t index); }; @@ -394,10 +394,10 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) inline QByteArray makeErrorStr(const Event* e, const char* msg) { return QString("%1; event dump follows:\n%2") - .arg(msg, e->originalJson()).toUtf8(); + .arg(msg, QString(e->originalJson())).toUtf8(); } -void Room::Private::insertEvent(Event* e, Timeline::iterator where, +void Room::Private::insertEvent(RoomEvent* e, Timeline::iterator where, TimelineItem::index_t index) { Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); @@ -530,7 +530,7 @@ void Room::updateData(SyncRoomData&& data) qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et.elapsed() << "ms"; et.restart(); - for( Event* ephemeralEvent: data.ephemeral ) + for( auto ephemeralEvent: data.ephemeral ) { processEphemeralEvent(ephemeralEvent); } @@ -586,12 +586,12 @@ void Room::leaveRoom() const connection()->callApi(id()); } -void Room::Private::dropDuplicateEvents(Events* events) const +void Room::Private::dropDuplicateEvents(RoomEvents* events) const { // Collect all duplicate events at the end of the container auto dupsBegin = std::stable_partition(events->begin(), events->end(), - [&] (Event* e) { return !eventsIndex.contains(e->id()); }); + [&] (RoomEvent* e) { return !eventsIndex.contains(e->id()); }); // Dispose of those dups std::for_each(dupsBegin, events->end(), [] (Event* e) { delete e; }); events->erase(dupsBegin, events->end()); @@ -602,7 +602,7 @@ Connection* Room::connection() const return d->connection; } -void Room::addNewMessageEvents(Events events) +void Room::addNewMessageEvents(RoomEvents events) { d->dropDuplicateEvents(&events); if (events.empty()) @@ -612,9 +612,9 @@ void Room::addNewMessageEvents(Events events) emit addedMessages(); } -void Room::doAddNewMessageEvents(const Events& events) +void Room::doAddNewMessageEvents(const RoomEvents& events) { - Q_ASSERT(!events.isEmpty()); + Q_ASSERT(!events.empty()); Timeline::size_type newUnreadMessages = 0; for (auto e: events) @@ -647,7 +647,7 @@ void Room::doAddNewMessageEvents(const Events& events) } } -void Room::addHistoricalMessageEvents(Events events) +void Room::addHistoricalMessageEvents(RoomEvents events) { d->dropDuplicateEvents(&events); if (events.empty()) @@ -657,9 +657,9 @@ void Room::addHistoricalMessageEvents(Events events) emit addedMessages(); } -void Room::doAddHistoricalMessageEvents(const Events& events) +void Room::doAddHistoricalMessageEvents(const RoomEvents& events) { - Q_ASSERT(!events.isEmpty()); + Q_ASSERT(!events.empty()); // Historical messages arrive in newest-to-oldest order for (auto e: events) d->prependEvent(e); @@ -667,52 +667,57 @@ void Room::doAddHistoricalMessageEvents(const Events& events) << "past events; the oldest event is now" << d->timeline.front(); } -void Room::processStateEvents(const Events& events) +void Room::processStateEvents(const RoomEvents& events) { bool emitNamesChanged = false; for (auto event: events) { - if( event->type() == EventType::RoomName ) + switch (event->type()) { - auto nameEvent = static_cast(event); - d->name = nameEvent->name(); - qCDebug(MAIN) << "room name:" << d->name; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomAliases ) - { - auto aliasesEvent = static_cast(event); - d->aliases = aliasesEvent->aliases(); - qCDebug(MAIN) << "room aliases:" << d->aliases; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomCanonicalAlias ) - { - auto aliasEvent = static_cast(event); - d->canonicalAlias = aliasEvent->alias(); - qCDebug(MAIN) << "room canonical alias:" << d->canonicalAlias; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomTopic ) - { - auto topicEvent = static_cast(event); - d->topic = topicEvent->topic(); - emit topicChanged(); - } - if( event->type() == EventType::RoomMember ) - { - auto memberEvent = static_cast(event); - // Can't use d->member() below because the user may be not a member (yet) - User* u = d->connection->user(memberEvent->userId()); - u->processEvent(event); - if( memberEvent->membership() == MembershipType::Join ) - { - d->addMember(u); + case EventType::RoomName: { + auto nameEvent = static_cast(event); + d->name = nameEvent->name(); + qCDebug(MAIN) << "Room name updated:" << d->name; + emitNamesChanged = true; + break; } - else if( memberEvent->membership() == MembershipType::Leave ) - { - d->removeMember(u); + case EventType::RoomAliases: { + auto aliasesEvent = static_cast(event); + d->aliases = aliasesEvent->aliases(); + qCDebug(MAIN) << "Room aliases updated:" << d->aliases; + emitNamesChanged = true; + break; + } + case EventType::RoomCanonicalAlias: { + auto aliasEvent = static_cast(event); + d->canonicalAlias = aliasEvent->alias(); + qCDebug(MAIN) << "Room canonical alias updated:" << d->canonicalAlias; + emitNamesChanged = true; + break; } + case EventType::RoomTopic: { + auto topicEvent = static_cast(event); + d->topic = topicEvent->topic(); + qCDebug(MAIN) << "Room topic updated:" << d->topic; + emit topicChanged(); + break; + } + case EventType::RoomMember: { + auto memberEvent = static_cast(event); + // Can't use d->member() below because the user may be not a member (yet) + User* u = d->connection->user(memberEvent->userId()); + u->processEvent(event); + if( memberEvent->membership() == MembershipType::Join ) + { + d->addMember(u); + } + else if( memberEvent->membership() == MembershipType::Leave ) + { + d->removeMember(u); + } + break; + } + default: /* Ignore events of other types */; } } if (emitNamesChanged) { @@ -723,53 +728,57 @@ void Room::processStateEvents(const Events& events) void Room::processEphemeralEvent(Event* event) { - if( event->type() == EventType::Typing ) - { - auto typingEvent = static_cast(event); - d->usersTyping.clear(); - for( const QString& userId: typingEvent->users() ) - { - if (auto m = d->member(userId)) - d->usersTyping.append(m); - } - emit typingChanged(); - } - if( event->type() == EventType::Receipt ) + switch (event->type()) { - auto receiptEvent = static_cast(event); - for( const auto &eventReceiptPair: receiptEvent->events() ) - { - const auto& eventId = eventReceiptPair.first; - const auto& receipts = eventReceiptPair.second; + case EventType::Typing: { + auto typingEvent = static_cast(event); + d->usersTyping.clear(); + for( const QString& userId: typingEvent->users() ) { - if (receipts.size() == 1) - qCDebug(EPHEMERAL) << "Marking" << eventId - << "as read for" << receipts[0].userId; - else - qCDebug(EPHEMERAL) << "Marking" << eventId - << "as read for" - << receipts.size() << "users"; + if (auto m = d->member(userId)) + d->usersTyping.append(m); } - if (d->eventsIndex.contains(eventId)) - { - const auto newMarker = findInTimeline(eventId); - for( const Receipt& r: receipts ) - if (auto m = d->member(r.userId)) - d->promoteReadMarker(m, newMarker); - } else + emit typingChanged(); + break; + } + case EventType::Receipt: { + auto receiptEvent = static_cast(event); + for( const auto &p: receiptEvent->eventsWithReceipts() ) { - qCDebug(EPHEMERAL) << "Event" << eventId - << "not found; saving read receipts anyway"; - // If the event is not found (most likely, because it's too old - // and hasn't been fetched from the server yet), but there is - // a previous marker for a user, keep the previous marker. - // Otherwise, blindly store the event id for this user. - for( const Receipt& r: receipts ) - if (auto m = d->member(r.userId)) - if (readMarker(m) == timelineEdge()) - d->setLastReadEvent(m, eventId); + { + if (p.receipts.size() == 1) + qCDebug(EPHEMERAL) << "Marking" << p.evtId + << "as read for" << p.receipts[0].userId; + else + qCDebug(EPHEMERAL) << "Marking" << p.evtId + << "as read for" + << p.receipts.size() << "users"; + } + if (d->eventsIndex.contains(p.evtId)) + { + const auto newMarker = findInTimeline(p.evtId); + for( const Receipt& r: p.receipts ) + if (auto m = d->member(r.userId)) + d->promoteReadMarker(m, newMarker); + } else + { + qCDebug(EPHEMERAL) << "Event" << p.evtId + << "not found; saving read receipts anyway"; + // If the event is not found (most likely, because it's too old + // and hasn't been fetched from the server yet), but there is + // a previous marker for a user, keep the previous marker. + // Otherwise, blindly store the event id for this user. + for( const Receipt& r: p.receipts ) + if (auto m = d->member(r.userId)) + if (readMarker(m) == timelineEdge()) + d->setLastReadEvent(m, p.evtId); + } } + break; } + default: + qCWarning(EPHEMERAL) << "Unexpected event type in 'ephemeral' batch:" + << event->type(); } } diff --git a/room.h b/room.h index 60be33b9..4f7a3c97 100644 --- a/room.h +++ b/room.h @@ -43,14 +43,14 @@ namespace QMatrixClient // a std:: container now using index_t = int; - TimelineItem(Event* e, index_t number) : evt(e), idx(number) { } + TimelineItem(RoomEvent* e, index_t number) : evt(e), idx(number) { } - Event* event() const { return evt.get(); } - Event* operator->() const { return event(); } //< Synonym for event() + RoomEvent* event() const { return evt.get(); } + RoomEvent* operator->() const { return event(); } //< Synonym for event() index_t index() const { return idx; } private: - std::unique_ptr evt; + std::unique_ptr evt; index_t idx; }; inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) @@ -152,8 +152,8 @@ namespace QMatrixClient void userRenamed(User* user, QString oldName); signals: - void aboutToAddHistoricalMessages(const Events& events); - void aboutToAddNewMessages(const Events& events); + void aboutToAddHistoricalMessages(const RoomEvents& events); + void aboutToAddNewMessages(const RoomEvents& events); void addedMessages(); /** @@ -177,17 +177,17 @@ namespace QMatrixClient void unreadMessagesChanged(Room* room); protected: - virtual void doAddNewMessageEvents(const Events& events); - virtual void doAddHistoricalMessageEvents(const Events& events); - virtual void processStateEvents(const Events& events); + virtual void doAddNewMessageEvents(const RoomEvents& events); + virtual void doAddHistoricalMessageEvents(const RoomEvents& events); + virtual void processStateEvents(const RoomEvents& events); virtual void processEphemeralEvent(Event* event); private: class Private; Private* d; - void addNewMessageEvents(Events events); - void addHistoricalMessageEvents(Events events); + void addNewMessageEvents(RoomEvents events); + void addHistoricalMessageEvents(RoomEvents events); void markMessagesAsRead(rev_iter_t upToMarker); }; @@ -209,4 +209,4 @@ namespace QMatrixClient private: const Room* room; }; -} +} // namespace QMatrixClient -- cgit v1.2.3 From a5e14da86c3299ca8d36eb4a4eb58ce2a245dc4e Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Mon, 22 May 2017 16:55:41 +0900 Subject: Fixed building with CLang 3.5 --- events/roommessageevent.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index 49bb4053..19da8827 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -64,16 +64,16 @@ 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, + "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 + &makeUnknown ); std::tie(_msgtype, _content) = factory(content); } -- cgit v1.2.3