From de72fb7f9b6e36021c284c011f19714defe80713 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Fri, 3 Aug 2018 17:14:08 +0900 Subject: Refactor event items * TimelineItem and a newly introduced PendingEventItem are now inheriting from the common EventItemBase class * PendingEventItem has its own status and annotation, serving to track transition of the item through pending states --- lib/eventitem.cpp | 19 +++++++ lib/eventitem.h | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- lib/room.cpp | 84 ++++++++++++++++++++++--------- lib/room.h | 47 ++---------------- 4 files changed, 225 insertions(+), 71 deletions(-) (limited to 'lib') diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp index e69de29b..79ef769c 100644 --- a/lib/eventitem.cpp +++ b/lib/eventitem.cpp @@ -0,0 +1,19 @@ +/****************************************************************************** + * Copyright (C) 2018 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "eventitem.h" diff --git a/lib/eventitem.h b/lib/eventitem.h index 9e02fab0..6bec5a7f 100644 --- a/lib/eventitem.h +++ b/lib/eventitem.h @@ -1,4 +1,144 @@ -#ifndef EVENTITEM_H -#define EVENTITEM_H +/****************************************************************************** + * Copyright (C) 2018 Kitsune Ral + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ -#endif // EVENTITEM_H +#pragma once + +#include "events/stateevent.h" + +#include + +namespace QMatrixClient +{ + class StateEventBase; + + class EventStatus + { + Q_GADGET + public: + /** Special marks an event can assume + * + * This is used to hint at a special status of some events in UI. + * Most status values are mutually exclusive. + */ + enum Code { + Normal = 0x0, //< No special designation + Submitted = 0x01, //< The event has just been submitted for sending + Departed = 0x02, //< The event has left the client + ReachedServer = 0x03, //< The server has received the event + SendingFailed = 0x04, //< The server could not receive the event + Redacted = 0x08, //< The event has been redacted + Hidden = 0x10, //< The event should be hidden + }; + Q_DECLARE_FLAGS(Status, Code) + Q_FLAG(Status) + }; + + class EventItemBase + { + public: + explicit EventItemBase(RoomEventPtr&& e) + : evt(std::move(e)) + { + Q_ASSERT(evt); + } + + const RoomEvent* event() const { return rawPtr(evt); } + const RoomEvent* get() const { return event(); } + template + const EventT* viewAs() const { return eventCast(evt); } + const RoomEventPtr& operator->() const { return evt; } + const RoomEvent& operator*() const { return *evt; } + + // Used for event redaction + RoomEventPtr replaceEvent(RoomEventPtr&& other) + { + return std::exchange(evt, move(other)); + } + + private: + RoomEventPtr evt; + }; + + class TimelineItem : public EventItemBase + { + public: + // For compatibility with Qt containers, even though we use + // a std:: container now for the room timeline + using index_t = int; + + TimelineItem(RoomEventPtr&& e, index_t number) + : EventItemBase(std::move(e)), idx(number) + { } + + index_t index() const { return idx; } + + private: + index_t idx; + }; + + template<> + inline const StateEventBase* EventItemBase::viewAs() const + { + return evt->isStateEvent() ? weakPtrCast(evt) + : nullptr; + } + + class PendingEventItem : public EventItemBase + { + Q_GADGET + public: + using EventItemBase::EventItemBase; + + EventStatus::Code deliveryStatus() const { return _status; } + QDateTime lastUpdated() const { return _lastUpdated; } + QString annotation() const { return _annotation; } + + void setDeparted() { setStatus(EventStatus::Departed); } + void setReachedServer(const QString& eventId) + { + setStatus(EventStatus::ReachedServer); + (*this)->addId(eventId); + } + void setSendingFailed(QString errorText) + { + setStatus(EventStatus::SendingFailed); + _annotation = std::move(errorText); + } + void resetStatus() { setStatus(EventStatus::Submitted); } + + private: + EventStatus::Code _status = EventStatus::Submitted; + QDateTime _lastUpdated = QDateTime::currentDateTimeUtc(); + QString _annotation; + + void setStatus(EventStatus::Code status) + { + _status = status; + _lastUpdated = QDateTime::currentDateTimeUtc(); + _annotation.clear(); + } + }; + + inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) + { + QDebugStateSaver dss(d); + d.nospace() << "(" << ti.index() << "|" << ti->id() << ")"; + return d; + } +} +Q_DECLARE_METATYPE(QMatrixClient::EventStatus) diff --git a/lib/room.cpp b/lib/room.cpp index bfa6df68..e615060b 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -88,7 +88,7 @@ class Room::Private Connection* connection; Timeline timeline; - RoomEvents unsyncedEvents; + PendingEvents unsyncedEvents; QHash eventsIndex; QString id; QStringList aliases; @@ -207,6 +207,9 @@ class Room::Private return sendEvent(makeEvent(std::forward(eventArgs)...)); } + QString doSendEvent(const RoomEvent* pEvent); + PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr); + template auto requestSetState(const QString& stateKey, const EvT& event) { @@ -250,11 +253,6 @@ class Room::Private } }; -RoomEventPtr TimelineItem::replaceEvent(RoomEventPtr&& other) -{ - return std::exchange(evt, move(other)); -} - Room::Room(Connection* connection, QString id, JoinState initialJoinState) : QObject(connection), d(new Private(connection, id, initialJoinState)) { @@ -283,7 +281,7 @@ const Room::Timeline& Room::messageEvents() const return d->timeline; } -const RoomEvents& Room::pendingEvents() const +const Room::PendingEvents& Room::pendingEvents() const { return d->unsyncedEvents; } @@ -1128,30 +1126,68 @@ void Room::updateData(SyncRoomData&& data) QString Room::Private::sendEvent(RoomEventPtr&& event) { + if (event->transactionId().isEmpty()) + event->setTransactionId(connection->generateTxnId()); auto* pEvent = rawPtr(event); emit q->pendingEventAboutToAdd(); unsyncedEvents.emplace_back(move(event)); emit q->pendingEventAdded(); + return doSendEvent(pEvent); +} - if (pEvent->transactionId().isEmpty()) - pEvent->setTransactionId(connection->generateTxnId()); +QString Room::Private::doSendEvent(const RoomEvent* pEvent) +{ + auto txnId = pEvent->transactionId(); // TODO: Enqueue the job rather than immediately trigger it - auto call = connection->sendMessage(id, *pEvent); - Room::connect(call, &BaseJob::success, q, [this,call,pEvent] - { - const auto comparator = - [pEvent] (const auto& eptr) { return rawPtr(eptr) == pEvent; }; + auto call = connection->callApi(BackgroundRequest, + id, pEvent->matrixType(), txnId, pEvent->contentJson()); + Room::connect(call, &BaseJob::started, q, + [this,pEvent,txnId] { + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + { + qWarning(EVENTS) << "Pending event for transaction" << txnId + << "not found - got synced so soon?"; + return; + } + it->setDeparted(); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + }); + Room::connect(call, &BaseJob::failure, q, + [this,pEvent,txnId,call] { + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + { + qCritical(EVENTS) << "Pending event for transaction" << txnId + << "got lost without successful sending"; + return; + } + it->setSendingFailed( + call->statusCaption() % ": " % call->errorString()); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + + }); + Room::connect(call, &BaseJob::success, q, + [this,call,pEvent] { + // Find an event by the pointer saved in the lambda (the pointer + // may be dangling by now but we can still search by it). + auto it = findAsPending(pEvent); + if (it == unsyncedEvents.end()) + return; // The event is already synced, nothing to do + + it->setReachedServer(call->eventId()); + emit q->pendingEventChanged(it - unsyncedEvents.begin()); + }); + return txnId; +} - // Find an event by the pointer saved in the lambda - auto it = std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(), - comparator); - if (it == unsyncedEvents.end()) - return; // The event is already synced, nothing to do +Room::PendingEvents::iterator Room::Private::findAsPending( + const RoomEvent* rawEvtPtr) +{ + const auto comp = + [rawEvtPtr] (const auto& pe) { return pe.event() == rawEvtPtr; }; - pEvent->addId(call->eventId()); - emit q->pendingEventChanged(it - unsyncedEvents.begin()); - }); - return pEvent->transactionId(); + return std::find_if(unsyncedEvents.begin(), unsyncedEvents.end(), comp); } QString Room::postMessage(const QString& type, const QString& plainText) @@ -1202,7 +1238,7 @@ void Room::setTopic(const QString& newTopic) d->requestSetState(RoomTopicEvent(newTopic)); } -bool isEchoEvent(const RoomEventPtr& le, const RoomEventPtr& re) +bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re) { if (le->type() != re->type()) return false; diff --git a/lib/room.h b/lib/room.h index 599b7fbf..15e2146e 100644 --- a/lib/room.h +++ b/lib/room.h @@ -21,6 +21,7 @@ #include "jobs/syncjob.h" #include "events/roommessageevent.h" #include "events/accountdataevents.h" +#include "eventitem.h" #include "joinstate.h" #include @@ -40,49 +41,6 @@ namespace QMatrixClient class SetRoomStateWithKeyJob; class RedactEventJob; - class TimelineItem - { - public: - // For compatibility with Qt containers, even though we use - // a std:: container now for the room timeline - using index_t = int; - - TimelineItem(RoomEventPtr&& e, index_t number) - : evt(std::move(e)), idx(number) - { - Q_ASSERT(evt); - } - - const RoomEvent* event() const { return rawPtr(evt); } - const RoomEvent* get() const { return event(); } - template - const EventT* viewAs() const { return eventCast(evt); } - const RoomEventPtr& operator->() const { return evt; } - const RoomEvent& operator*() const { return *evt; } - index_t index() const { return idx; } - - // Used for event redaction - RoomEventPtr replaceEvent(RoomEventPtr&& other); - - private: - RoomEventPtr evt; - index_t idx; - }; - - template<> - inline const StateEventBase* TimelineItem::viewAs() const - { - return evt->isStateEvent() ? weakPtrCast(evt) - : nullptr; - } - - inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) - { - QDebugStateSaver dss(d); - d.nospace() << "(" << ti.index() << "|" << ti->id() << ")"; - return d; - } - class FileTransferInfo { Q_GADGET @@ -139,6 +97,7 @@ namespace QMatrixClient public: using Timeline = std::deque; + using PendingEvents = std::vector; using rev_iter_t = Timeline::const_reverse_iterator; using timeline_iter_t = Timeline::const_iterator; @@ -216,7 +175,7 @@ namespace QMatrixClient Q_INVOKABLE QString roomMembername(const QString& userId) const; const Timeline& messageEvents() const; - const RoomEvents& pendingEvents() const; + const PendingEvents& pendingEvents() const; /** * A convenience method returning the read marker to the before-oldest * message -- cgit v1.2.3