diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/eventitem.cpp | 19 | ||||
-rw-r--r-- | lib/eventitem.h | 146 | ||||
-rw-r--r-- | lib/room.cpp | 84 | ||||
-rw-r--r-- | lib/room.h | 47 | ||||
-rw-r--r-- | libqmatrixclient.pri | 2 |
6 files changed, 228 insertions, 71 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7be054e8..fa48db03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ set(libqmatrixclient_SRCS lib/networksettings.cpp lib/converters.cpp lib/util.cpp + lib/eventitem.cpp lib/events/event.cpp lib/events/roomevent.cpp lib/events/stateevent.cpp 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 <kitsune-ral@users.sf.net> + * + * 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 <kitsune-ral@users.sf.net> + * + * 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 <utility> + +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 <typename EventT> + const EventT* viewAs() const { return eventCast<const EventT>(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<StateEventBase>() const + { + return evt->isStateEvent() ? weakPtrCast<const StateEventBase>(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<QString, TimelineItem::index_t> eventsIndex; QString id; QStringList aliases; @@ -207,6 +207,9 @@ class Room::Private return sendEvent(makeEvent<EventT>(std::forward<ArgTs>(eventArgs)...)); } + QString doSendEvent(const RoomEvent* pEvent); + PendingEvents::iterator findAsPending(const RoomEvent* rawEvtPtr); + template <typename EvT> 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<SendMessageJob>(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; @@ -21,6 +21,7 @@ #include "jobs/syncjob.h" #include "events/roommessageevent.h" #include "events/accountdataevents.h" +#include "eventitem.h" #include "joinstate.h" #include <QtGui/QPixmap> @@ -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 <typename EventT> - const EventT* viewAs() const { return eventCast<const EventT>(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<StateEventBase>() const - { - return evt->isStateEvent() ? weakPtrCast<const StateEventBase>(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<TimelineItem>; + using PendingEvents = std::vector<PendingEventItem>; 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 diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 2f6a701a..f931be32 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -13,6 +13,7 @@ INCLUDEPATH += $$SRCPATH HEADERS += \ $$SRCPATH/connectiondata.h \ $$SRCPATH/connection.h \ + $$SRCPATH/eventitem.h \ $$SRCPATH/room.h \ $$SRCPATH/user.h \ $$SRCPATH/avatar.h \ @@ -49,6 +50,7 @@ HEADERS += \ SOURCES += \ $$SRCPATH/connectiondata.cpp \ $$SRCPATH/connection.cpp \ + $$SRCPATH/eventitem.cpp \ $$SRCPATH/room.cpp \ $$SRCPATH/user.cpp \ $$SRCPATH/avatar.cpp \ |