diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2017-01-11 07:41:53 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-11 07:41:53 +0900 |
commit | f37874c7e55239359d02b926a65cd62d17336522 (patch) | |
tree | c14ba3abd15c380dd6430240dba46c615fc5eee8 | |
parent | 78cfe3c102b686c71a1da5cbe91a2ae42dc03825 (diff) | |
parent | fb4127da9db32e5e18b14beaaabbee7adf9cc334 (diff) | |
download | libquotient-f37874c7e55239359d02b926a65cd62d17336522.tar.gz libquotient-f37874c7e55239359d02b926a65cd62d17336522.zip |
Merge pull request #51 from Fxrh/kitsune-traceable-events
Make events traceable
-rw-r--r-- | room.cpp | 127 | ||||
-rw-r--r-- | room.h | 7 |
2 files changed, 55 insertions, 79 deletions
@@ -18,12 +18,11 @@ #include "room.h" -#include <array> - #include <QtCore/QHash> #include <QtCore/QJsonArray> #include <QtCore/QStringBuilder> // for efficient string concats (operator%) #include <QtCore/QDebug> +#include <algorithm> #include "connection.h" #include "state.h" @@ -60,7 +59,8 @@ class Room::Private void updateDisplayname(); Connection* connection; - Timeline messageEvents; + Timeline timeline; + QHash<QString, Timeline::const_iterator> eventsIndex; QString id; QStringList aliases; QString canonicalAlias; @@ -74,7 +74,7 @@ class Room::Private members_map_t membersMap; QList<User*> usersTyping; QList<User*> membersLeft; - QHash<User*, QString> lastReadEvent; + QHash<const User*, QString> lastReadEventIds; QString prevBatch; RoomMessagesJob* roomMessagesJob; @@ -92,6 +92,9 @@ class Room::Private void getPreviousContent(); bool isEventNotable(const Event* e) const; + + Timeline::const_iterator readMarker(const User* user) const; + private: QString calculateDisplayname() const; QString roomNameFromMemberNames(const QList<User*>& userlist) const; @@ -123,7 +126,7 @@ QString Room::id() const const Room::Timeline& Room::messageEvents() const { - return d->messageEvents; + return d->timeline; } QString Room::name() const @@ -165,9 +168,9 @@ void Room::setJoinState(JoinState state) emit joinStateChanged(oldState, state); } -void Room::setLastReadEvent(User* user, QString eventId) +void Room::setLastReadEvent(User* user, Event* event) { - d->lastReadEvent.insert(user, eventId); + d->lastReadEventIds.insert(user, event->id()); emit lastReadEventChanged(user); if (user == d->connection->user()) emit readMarkerPromoted(); @@ -175,83 +178,64 @@ void Room::setLastReadEvent(User* user, QString eventId) Room::Timeline::const_iterator Room::promoteReadMarker(User* u, QString eventId) { - QString prevLastReadId = lastReadEvent(u); - int stillUnreadMessagesCount = 0; - auto it = d->messageEvents.end(); - Event* targetEvent = nullptr; - // Older Qt doesn't provide rbegin()/rend() for Qt containers - while (it != d->messageEvents.begin()) - { - --it; - // Check that the new read event is not before the previously set - only - // allow the read marker to move down the timeline, not up. - if (prevLastReadId == (*it)->id()) - break; + if (d->timeline.empty()) + return d->timeline.end(); - // Found the message to mark as read; if there are messages from - // that user right below this one, automatically promote the marker - // to them instead of this one; still return this one to save - // markMessagesAsRead() from going through local messages over again. - if (eventId == (*it)->id()) - { - setLastReadEvent(u, (targetEvent ? targetEvent : *it)->id()); - break; - } + auto newMarker = next(d->eventsIndex.value(eventId)); // After the event + { + auto prevMarker = d->readMarker(u); + if (prevMarker > newMarker) + return prevMarker; + } - // If we are on a message from that user (or a series thereof), - // remember it (or the end of the sequence) so that we could use it - // in case when the event to promote the marker to is immediately - // above the ones from that user. - if ((*it)->senderId() == u->id()) - { - if (!targetEvent) - targetEvent = *it; - } - else - targetEvent = nullptr; + using namespace std; - // Detect events "notable" for the local user so that we can properly - // set unreadMessages - if (u == connection()->user()) - stillUnreadMessagesCount += d->isEventNotable(*it); - } + // Try to auto-promote the read marker over the user's own messages. + auto eagerMarker = find_if(newMarker, d->timeline.cend(), + [=](Event* e) { return e->senderId() != u->id(); }); + setLastReadEvent(u, *(prev(eagerMarker))); - if( u == connection()->user() ) + if (u == connection()->user() && d->unreadMessages) { - if (d->unreadMessages && stillUnreadMessagesCount == 0) + auto stillUnreadMessagesCount = + count_if(eagerMarker, d->timeline.cend(), + [=](Event* e) { return d->isEventNotable(e); }); + + if (stillUnreadMessagesCount == 0) { d->unreadMessages = false; qDebug() << "Room" << displayName() << ": no more unread messages"; emit unreadMessagesChanged(this); } - if (stillUnreadMessagesCount > 0) + else qDebug() << "Room" << displayName() << ": still" << stillUnreadMessagesCount << "unread message(s)"; } - return it; + + // Return newMarker, rather than eagerMarker, to save markMessagesAsRead() + // that calls this method from going back through knowingly-local messages. + return newMarker; } void Room::markMessagesAsRead(QString uptoEventId) { - if (d->messageEvents.empty()) + if (d->timeline.empty()) return; User* localUser = connection()->user(); - QString prevLastReadId = lastReadEvent(localUser); + auto prevReadMarker = d->readMarker(localUser); auto last = promoteReadMarker(localUser, uptoEventId); - // We shouldn't send read receipts for messages from the local user - so + // We shouldn't send read receipts for the local user's own messages - so // shift back (if necessary) to the nearest message not from the local user // or the so far last read message, whichever comes first. - for (; (*last)->id() != prevLastReadId; --last) + while (last > prevReadMarker) { - if ((*last)->senderId() != connection()->userId()) + if ((*--last)->senderId() != connection()->userId()) { d->connection->postReceipt(this, *last); break; } - if (last == messageEvents().begin()) - break; } } @@ -266,14 +250,17 @@ bool Room::hasUnreadMessages() return d->unreadMessages; } -QString Room::lastReadEvent(User* user) const +Room::Timeline::const_iterator Room::Private::readMarker(const User* user) const { - return d->lastReadEvent.value(user); + auto lastReadId = lastReadEventIds.value(user); + return lastReadId.isEmpty() ? + eventsIndex.value(lastReadId) + 1 : + timeline.begin(); } QString Room::readMarkerEventId() const { - return lastReadEvent(d->connection->user()); + return d->lastReadEventIds.value(d->connection->user()); } int Room::notificationCount() const @@ -504,7 +491,6 @@ bool Room::Private::isEventNotable(const Event* e) const void Room::doAddNewMessageEvents(const Events& events) { - d->messageEvents.reserve(d->messageEvents.size() + events.size()); Timeline::size_type newUnreadMessages = 0; @@ -513,26 +499,15 @@ void Room::doAddNewMessageEvents(const Events& events) // from the server (or, for the local user, markMessagesAsRead() invocation) // to promote their read markers over the new message events. User* firstWriter = connection()->user(events.front()->senderId()); - bool canAutoPromote = d->messageEvents.empty() || - lastReadEvent(firstWriter) == d->messageEvents.back()->id(); - Event* firstWriterSeriesEnd = canAutoPromote ? events.front() : nullptr; + bool canAutoPromote = d->readMarker(firstWriter) == d->timeline.end(); for (auto e: events) { - d->messageEvents.push_back(e); - + d->eventsIndex.insert(e->id(), d->timeline.insert(d->timeline.end(), e)); newUnreadMessages += d->isEventNotable(e); - if (firstWriterSeriesEnd) - { - if (e->senderId() != firstWriter->id()) - firstWriterSeriesEnd = e; - else - { - setLastReadEvent(firstWriter, firstWriterSeriesEnd->id()); - firstWriterSeriesEnd = nullptr; - } - } } + if (canAutoPromote) + promoteReadMarker(firstWriter, events.front()->id()); if( !d->unreadMessages && newUnreadMessages > 0) { @@ -554,8 +529,8 @@ void Room::addHistoricalMessageEvents(const Events& events) void Room::doAddHistoricalMessageEvents(const Events& events) { // Historical messages arrive in newest-to-oldest order - d->messageEvents.reserve(d->messageEvents.size() + events.size()); - std::copy(events.begin(), events.end(), std::front_inserter(d->messageEvents)); + for (auto e: events) + d->eventsIndex.insert(e->id(), d->timeline.insert(d->timeline.begin(), e)); } void Room::processStateEvents(const Events& events) @@ -26,6 +26,8 @@ #include "jobs/syncjob.h" #include "joinstate.h" +#include <deque> + namespace QMatrixClient { class Event; @@ -38,7 +40,7 @@ namespace QMatrixClient Q_OBJECT Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE markMessagesAsRead NOTIFY readMarkerPromoted) public: - using Timeline = Owning<Events>; + using Timeline = Owning< std::deque<Event*> >; Room(Connection* connection, QString id); virtual ~Room(); @@ -70,7 +72,6 @@ namespace QMatrixClient Q_INVOKABLE void updateData(SyncRoomData& data ); Q_INVOKABLE void setJoinState( JoinState state ); - Q_INVOKABLE QString lastReadEvent(User* user) const; QString readMarkerEventId() const; /** * @brief Mark the event with uptoEventId as read @@ -140,7 +141,7 @@ namespace QMatrixClient void addNewMessageEvents(const Events& events); void addHistoricalMessageEvents(const Events& events); - void setLastReadEvent(User* user, QString eventId); + void setLastReadEvent(User* user, Event* event); }; class MemberSorter |