diff options
31 files changed, 617 insertions, 733 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<PostMessageJob>(room->id(), type, message); } -PostReceiptJob* Connection::postReceipt(Room* room, Event* event) const +PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { return callApi<PostReceiptJob>(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<PostReceiptJob>() 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<LeaveRoomJob>() or Room::leaveRoom() instead */ Q_INVOKABLE virtual void leaveRoom( Room* room ); diff --git a/events/event.cpp b/events/event.cpp index 07649b02..bd7e1b03 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -18,9 +18,6 @@ #include "event.h" -#include <QtCore/QJsonArray> -#include <QtCore/QJsonDocument> - #include "roommessageevent.h" #include "roomnameevent.h" #include "roomaliasesevent.h" @@ -31,121 +28,101 @@ #include "receiptevent.h" #include "unknownevent.h" #include "logging.h" -#include "util.h" -using namespace QMatrixClient; +#include <QtCore/QJsonDocument> -class Event::Private -{ - public: - EventType type; - QString id; - QDateTime timestamp; - QString roomId; - QString senderId; - QString originalJson; -}; +using namespace QMatrixClient; -Event::Event(EventType type) - : d(new Private) +Event::Event(Type type, const QJsonObject& rep) + : _type(type), _originalJson(rep) { - d->type = type; + if (!rep.contains("content")) + { + qCWarning(EVENTS) << "Event without 'content' node"; + qCWarning(EVENTS) << formatJson << rep; + } } -Event::~Event() +QByteArray Event::originalJson() const { - delete d; + return QJsonDocument(_originalJson).toJson(); } -EventType Event::type() const +QDateTime Event::toTimestamp(const QJsonValue& v) { - return d->type; + Q_ASSERT(v.isDouble()); + return QDateTime::fromMSecsSinceEpoch( + static_cast<long long int>(v.toDouble()), Qt::UTC); } -QString Event::id() const +QStringList Event::toStringList(const QJsonValue& v) { - return d->id; -} + Q_ASSERT(v.isArray()); -QDateTime Event::timestamp() const -{ - return d->timestamp; + QStringList l; + for( const QJsonValue& e : v.toArray() ) + l.push_back(e.toString()); + return l; } -QString Event::roomId() const +const QJsonObject Event::contentJson() const { - return d->roomId; + return _originalJson["content"].toObject(); } -QString Event::senderId() const +template <typename EventT> +EventT* make(const QJsonObject& o) { - return d->senderId; + return new EventT(o); } -QString Event::originalJson() const +Event* Event::fromJson(const QJsonObject& obj) { - return d->originalJson; + // Check more specific event types first + if (auto e = RoomEvent::fromJson(obj)) + return e; + + return dispatch<Event*>(obj).to(obj["type"].toString(), + "m.typing", make<TypingEvent>, + "m.receipt", make<ReceiptEvent>, + /* Insert new event types (except room events) BEFORE this line */ + nullptr + ); } -template <typename T> -Event* make(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()) { - return T::fromJson(obj); + if (_id.isEmpty()) + { + 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; + } } -Event* Event::fromJson(const QJsonObject& obj) +RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) { - auto delegate = lookup(obj.value("type").toString(), + return dispatch<RoomEvent*>(obj).to(obj["type"].toString(), "m.room.message", make<RoomMessageEvent>, "m.room.name", make<RoomNameEvent>, "m.room.aliases", make<RoomAliasesEvent>, "m.room.canonical_alias", make<RoomCanonicalAliasEvent>, "m.room.member", make<RoomMemberEvent>, "m.room.topic", make<RoomTopicEvent>, - "m.typing", make<TypingEvent>, - "m.receipt", make<ReceiptEvent>, - /* Insert new event types BEFORE this line */ - make<UnknownEvent> + /* Insert new ROOM event types BEFORE this line */ + nullptr ); - return delegate(obj); -} - -bool Event::parseJson(const QJsonObject& obj) -{ - 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 (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<qint64>(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; - } - } - return correct; -} - -Events QMatrixClient::eventsFromJson(const QJsonArray& json) -{ - Events evs; - evs.reserve(json.size()); - for (auto event: json) - evs.push_back(Event::fromJson(event.toObject())); - return evs; } 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 <QtCore/QString> #include <QtCore/QDateTime> #include <QtCore/QJsonObject> -#include <QtCore/QVector> +#include <QtCore/QJsonArray> -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<Event*>; + using EventType = Event::Type; + template <typename EventT> + using EventsBatch = std::vector<EventT*>; + using Events = EventsBatch<Event>; + + template <typename BaseEventT> + BaseEventT* makeEvent(const QJsonObject& obj) + { + if (auto e = BaseEventT::fromJson(obj)) + return e; + + return new BaseEventT(EventType::Unknown, obj); + } + + template <typename BaseEventT = Event, + typename BatchT = EventsBatch<BaseEventT> > + 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<typename BatchT::size_type>(objs.size())); + for (auto obj: objs) + evs.push_back(makeEvent<BaseEventT>(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<RoomEvent>; +} // 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<size_t>(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<Receipt> receipts; + receipts.reserve(static_cast<size_t>(reads.size())); for( auto userIt = reads.begin(); userIt != reads.end(); ++userIt ) { const QJsonObject user = userIt.value().toObject(); - const auto time = QDateTime::fromMSecsSinceEpoch( - static_cast<qint64>(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<Receipt>; - using EventsToReceipts = QVector< QPair<QString, Receipts> >; + struct ReceiptsForEvent + { + QString evtId; + std::vector<Receipt> receipts; + }; + using EventsWithReceipts = std::vector<ReceiptsForEvent>; 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 <QtCore/QJsonArray> - 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 677bb79f..19da8827 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -19,64 +19,15 @@ #include "roommessageevent.h" #include "logging.h" -#include "util.h" #include <QtCore/QMimeDatabase> 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<MessageEventType, MessageEventContent::Base*>; +using ContentPair = std::pair<CType, Base*>; -template <MessageEventType EnumType, typename ContentT> +template <CType EnumType, typename ContentT> 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<CType::Text, TextContent>, + "m.emote", &make<CType::Emote, TextContent>, + "m.notice", &make<CType::Notice, TextContent>, + "m.image", &make<CType::Image, ImageContent>, + "m.file", &make<CType::File, FileContent>, + "m.location", &make<CType::Location, LocationContent>, + "m.video", &makeVideo, + "m.audio", &make<CType::Audio, AudioContent>, + // 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.value("msgtype").toString(), - "m.text", make<MessageEventType::Text, TextContent>, - "m.emote", make<MessageEventType::Emote, TextContent>, - "m.notice", make<MessageEventType::Notice, TextContent>, - "m.image", make<MessageEventType::Image, ImageContent>, - "m.file", make<MessageEventType::File, FileContent>, - "m.location", make<MessageEventType::Location, LocationContent>, - "m.video", makeVideo, - "m.audio", make<MessageEventType::Audio, AudioContent>, - // 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 <QtCore/QMimeType> #include <QtCore/QSize> +#include <memory> + 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<AudioInfo>; - } -} + } // 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 <QtCore/QJsonArray> - 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 <iostream> -#include <QCoreApplication> #include "connection.h" #include "room.h" +#include <QCoreApplication> +#include <iostream> +#include <string> + 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/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/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 <QtCore/QJsonArray> +#include "util.h" using namespace QMatrixClient; class RoomMessagesJob::Private { public: - Owning<Events> events; + Owning<RoomEvents> 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<RoomEvent>(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 <QtCore/QJsonArray> #include <QtCore/QElapsedTimer> 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<Events> - { - private: - QString jsonKey; - public: - explicit EventList(QString k) : jsonKey(std::move(k)) { } - void fromJson(const QJsonObject& roomContents); - }; + public: + template <typename EventT> + class Batch : public Owning<EventsBatch<EventT>> + { + public: + explicit Batch(QString k) : jsonKey(std::move(k)) { } + void fromJson(const QJsonObject& roomContents) + { + this->assign(makeEvents<EventT>( + 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<RoomEvent> state; + Batch<RoomEvent> timeline; + Batch<Event> ephemeral; + Batch<Event> accountData; + Batch<Event> 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); @@ -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<LeaveRoomJob>(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<RoomNameEvent*>(event); - d->name = nameEvent->name(); - qCDebug(MAIN) << "room name:" << d->name; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomAliases ) - { - auto aliasesEvent = static_cast<RoomAliasesEvent*>(event); - d->aliases = aliasesEvent->aliases(); - qCDebug(MAIN) << "room aliases:" << d->aliases; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomCanonicalAlias ) - { - auto aliasEvent = static_cast<RoomCanonicalAliasEvent*>(event); - d->canonicalAlias = aliasEvent->alias(); - qCDebug(MAIN) << "room canonical alias:" << d->canonicalAlias; - emitNamesChanged = true; - } - if( event->type() == EventType::RoomTopic ) - { - auto topicEvent = static_cast<RoomTopicEvent*>(event); - d->topic = topicEvent->topic(); - emit topicChanged(); - } - if( event->type() == EventType::RoomMember ) - { - auto memberEvent = static_cast<RoomMemberEvent*>(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<RoomNameEvent*>(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<RoomAliasesEvent*>(event); + d->aliases = aliasesEvent->aliases(); + qCDebug(MAIN) << "Room aliases updated:" << d->aliases; + emitNamesChanged = true; + break; + } + case EventType::RoomCanonicalAlias: { + auto aliasEvent = static_cast<RoomCanonicalAliasEvent*>(event); + d->canonicalAlias = aliasEvent->alias(); + qCDebug(MAIN) << "Room canonical alias updated:" << d->canonicalAlias; + emitNamesChanged = true; + break; } + case EventType::RoomTopic: { + auto topicEvent = static_cast<RoomTopicEvent*>(event); + d->topic = topicEvent->topic(); + qCDebug(MAIN) << "Room topic updated:" << d->topic; + emit topicChanged(); + break; + } + case EventType::RoomMember: { + auto memberEvent = static_cast<RoomMemberEvent*>(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<TypingEvent*>(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<ReceiptEvent*>(event); - for( const auto &eventReceiptPair: receiptEvent->events() ) - { - const auto& eventId = eventReceiptPair.first; - const auto& receipts = eventReceiptPair.second; + case EventType::Typing: { + auto typingEvent = static_cast<TypingEvent*>(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<ReceiptEvent*>(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(); } } @@ -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<Event> evt; + std::unique_ptr<RoomEvent> 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 @@ -133,7 +133,7 @@ void User::processEvent(Event* event) { if( event->type() == EventType::RoomMember ) { - RoomMemberEvent* e = static_cast<RoomMemberEvent*>(event); + auto e = static_cast<RoomMemberEvent*>(event); if (e->membership() == MembershipType::Leave) return; @@ -18,6 +18,11 @@ #pragma once +#include <QtCore/QMetaEnum> +#include <QtCore/QDebug> + +#include <functional> + 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. <code>lookup<void*>(parameters...)</code>). 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,144 @@ namespace QMatrixClient * (the first parameter) - most likely it won't do what you expect because * of shallow comparison. */ + template <typename ValueT, typename SelectorT> + ValueT lookup(SelectorT/*unused*/, ValueT&& fallback) + { + return std::forward<ValueT>(fallback); + } + template <typename ValueT, typename SelectorT, typename KeyT, typename... Ts> - 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<ValueT>(value); // Drop the failed key-value pair and recurse with 2 arguments less. - return lookup(selector, remainingMapping...); + return lookup<ValueT>(std::forward<SelectorT>(selector), + std::forward<Ts>(remainder)...); } - template <typename SelectorT, typename ValueT> - 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<ResultT(ArgTs...). Without Dispatch<>, 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) + */ +#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 <typename ResultT, typename ArgT> +#else + template <typename ResultT, typename... ArgTs> +#endif + class Dispatch + { + // 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() + // 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. 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<ResultT(ArgT)>; + explicit Dispatch(ArgT&& arg) + : boundArgs([=](fn_t &&f) { return f(std::move(arg)); }) + { } +#else + using fn_t = std::function<ResultT(ArgTs...)>; + explicit Dispatch(ArgTs&&... args) + : boundArgs([=](fn_t &&f) { return f(std::move(args)...); }) + { } +#endif + + template <typename... LookupParamTs> + 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). + auto fn = lookup<fn_t>(std::forward<LookupParamTs>(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. + 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: + std::function<ResultT(fn_t&&)> boundArgs; + }; + + /** + * Dispatch a set of parameters to one of a set of functions, depending on + * a selector value + * + * Use <code>dispatch<CommonType>(parameters).to(lookup parameters)</code> + * 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 <typename ResultT, typename... ArgTs> + Dispatch<ResultT, ArgTs...> dispatch(ArgTs&& ... args) { - return fallback; + return Dispatch<ResultT, ArgTs...>(std::forward<ArgTs...>(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 |