Skip to content

Commit

Permalink
Merge pull request #51 from Fxrh/kitsune-traceable-events
Browse files Browse the repository at this point in the history
Make events traceable
  • Loading branch information
KitsuneRal authored Jan 10, 2017
2 parents 78cfe3c + fb4127d commit f37874c
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 79 deletions.
127 changes: 51 additions & 76 deletions room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -123,7 +126,7 @@ QString Room::id() const

const Room::Timeline& Room::messageEvents() const
{
return d->messageEvents;
return d->timeline;
}

QString Room::name() const
Expand Down Expand Up @@ -165,93 +168,74 @@ 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();
}

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;
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand All @@ -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)
{
Expand All @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions room.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include "jobs/syncjob.h"
#include "joinstate.h"

#include <deque>

namespace QMatrixClient
{
class Event;
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f37874c

Please sign in to comment.