From 9a1453bbd56fb7912c73845fc8580ce79e694286 Mon Sep 17 00:00:00 2001
From: Kitsune Ral <Kitsune-Ral@users.sf.net>
Date: Sun, 9 Dec 2018 19:55:14 +0900
Subject: Room: track more changes; fix cache smashing upon restart

Commit fd52459 introduced a regression rendering the cache unusable
after a client restart (an empty state overwrites whatever state was in
the cache). This commit contains the fix, along with more room change
tracking.

# Conflicts:
#	lib/room.h
---
 lib/room.cpp | 59 +++++++++++++++++++++++++++++++++++------------------------
 lib/room.h   |  7 ++++---
 2 files changed, 39 insertions(+), 27 deletions(-)

diff --git a/lib/room.cpp b/lib/room.cpp
index 8b81bfb2..cdc7572a 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -212,12 +212,12 @@ class Room::Private
          */
         void dropDuplicateEvents(RoomEvents& events) const;
 
-        void setLastReadEvent(User* u, QString eventId);
+        Changes setLastReadEvent(User* u, QString eventId);
         void updateUnreadCount(rev_iter_t from, rev_iter_t to);
-        void promoteReadMarker(User* u, rev_iter_t newMarker,
-                                          bool force = false);
+        Changes promoteReadMarker(User* u, rev_iter_t newMarker,
+                                  bool force = false);
 
-        void markMessagesAsRead(rev_iter_t upToMarker);
+        Changes markMessagesAsRead(rev_iter_t upToMarker);
 
         QString sendEvent(RoomEventPtr&& event);
 
@@ -386,11 +386,11 @@ void Room::setJoinState(JoinState state)
     emit joinStateChanged(oldState, state);
 }
 
-void Room::Private::setLastReadEvent(User* u, QString eventId)
+Room::Changes Room::Private::setLastReadEvent(User* u, QString eventId)
 {
     auto& storedId = lastReadEventIds[u];
     if (storedId == eventId)
-        return;
+        return Change::NoChange;
     eventIdReadUsers.remove(storedId, u);
     eventIdReadUsers.insert(eventId, u);
     swap(storedId, eventId);
@@ -401,8 +401,9 @@ void Room::Private::setLastReadEvent(User* u, QString eventId)
         if (storedId != serverReadMarker)
             connection->callApi<PostReadMarkersJob>(id, storedId);
         emit q->readMarkerMoved(eventId, storedId);
-        connection->saveRoomState(q);
+        return Change::ReadMarkerChange;
     }
+    return Change::NoChange;
 }
 
 void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to)
@@ -445,14 +446,15 @@ void Room::Private::updateUnreadCount(rev_iter_t from, rev_iter_t to)
     }
 }
 
-void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force)
+Room::Changes Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker,
+                                               bool force)
 {
     Q_ASSERT_X(u, __FUNCTION__, "User* should not be nullptr");
     Q_ASSERT(newMarker >= timeline.crbegin() && newMarker <= timeline.crend());
 
     const auto prevMarker = q->readMarker(u);
     if (!force && prevMarker <= newMarker) // Remember, we deal with reverse iterators
-        return;
+        return Change::NoChange;
 
     Q_ASSERT(newMarker < timeline.crend());
 
@@ -461,7 +463,7 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force)
     auto eagerMarker = find_if(newMarker.base(), timeline.cend(),
           [=](const TimelineItem& ti) { return ti->senderId() != u->id(); });
 
-    setLastReadEvent(u, (*(eagerMarker - 1))->id());
+    auto changes = setLastReadEvent(u, (*(eagerMarker - 1))->id());
     if (isLocalUser(u))
     {
         const auto oldUnreadCount = unreadMessages;
@@ -485,14 +487,16 @@ void Room::Private::promoteReadMarker(User* u, rev_iter_t newMarker, bool force)
                 qCDebug(MAIN) << "Room" << displayname << "still has"
                               << unreadMessages << "unread message(s)";
             emit q->unreadMessagesChanged(q);
+            changes |= Change::UnreadNotifsChange;
         }
     }
+    return changes;
 }
 
-void Room::Private::markMessagesAsRead(rev_iter_t upToMarker)
+Room::Changes Room::Private::markMessagesAsRead(rev_iter_t upToMarker)
 {
     const auto prevMarker = q->readMarker();
-    promoteReadMarker(q->localUser(), upToMarker);
+    auto changes = promoteReadMarker(q->localUser(), upToMarker);
     if (prevMarker != upToMarker)
         qCDebug(MAIN) << "Marked messages as read until" << *q->readMarker();
 
@@ -508,6 +512,7 @@ void Room::Private::markMessagesAsRead(rev_iter_t upToMarker)
             break;
         }
     }
+    return changes;
 }
 
 void Room::markMessagesAsRead(QString uptoEventId)
@@ -1151,7 +1156,7 @@ void Room::updateData(SyncRoomData&& data, bool fromCache)
     d->updateDisplayname();
 
     for( auto&& ephemeralEvent: data.ephemeral )
-        processEphemeralEvent(move(ephemeralEvent));
+        roomChanges |= processEphemeralEvent(move(ephemeralEvent));
 
     // See https://github.com/QMatrixClient/libqmatrixclient/wiki/unread_count
     if (data.unreadCount != -2 && data.unreadCount != d->unreadMessages)
@@ -1716,9 +1721,9 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
     // clients historically expect. This may eventually change though if we
     // postulate that the current state is only current between syncs but not
     // within a sync.
-    Changes stateChanges = Change::NoChange;
+    Changes roomChanges = Change::NoChange;
     for (const auto& eptr: events)
-        stateChanges |= q->processStateEvent(*eptr);
+        roomChanges |= q->processStateEvent(*eptr);
 
     auto timelineSize = timeline.size();
     auto totalInserted = 0;
@@ -1778,16 +1783,17 @@ Room::Changes Room::Private::addNewMessageEvents(RoomEvents&& events)
         auto firstWriter = q->user((*from)->senderId());
         if (q->readMarker(firstWriter) != timeline.crend())
         {
-            promoteReadMarker(firstWriter, rev_iter_t(from) - 1);
+            roomChanges |= promoteReadMarker(firstWriter, rev_iter_t(from) - 1);
             qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id()
                           << "to" << *q->readMarker(firstWriter);
         }
 
         updateUnreadCount(timeline.crbegin(), rev_iter_t(from));
+        roomChanges |= Change::UnreadNotifsChange;
     }
 
     Q_ASSERT(timeline.size() == timelineSize + totalInserted);
-    return stateChanges;
+    return roomChanges;
 }
 
 void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
@@ -1906,8 +1912,9 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
     );
 }
 
-void Room::processEphemeralEvent(EventPtr&& event)
+Room::Changes Room::processEphemeralEvent(EventPtr&& event)
 {
+    Changes changes = NoChange;
     QElapsedTimer et; et.start();
     if (auto* evt = eventCast<TypingEvent>(event))
     {
@@ -1946,7 +1953,7 @@ void Room::processEphemeralEvent(EventPtr&& event)
                         continue; // FIXME, #185
                     auto u = user(r.userId);
                     if (memberJoinState(u) == JoinState::Join)
-                        d->promoteReadMarker(u, newMarker);
+                        changes |= d->promoteReadMarker(u, newMarker);
                 }
             } else
             {
@@ -1963,7 +1970,7 @@ void Room::processEphemeralEvent(EventPtr&& event)
                     auto u = user(r.userId);
                     if (memberJoinState(u) == JoinState::Join &&
                             readMarker(u) == timelineEdge())
-                        d->setLastReadEvent(u, p.evtId);
+                        changes |= d->setLastReadEvent(u, p.evtId);
                 }
             }
         }
@@ -1973,12 +1980,17 @@ void Room::processEphemeralEvent(EventPtr&& event)
                 << evt->eventsWithReceipts().size()
                 << "event(s) with" << totalReceipts << "receipt(s)," << et;
     }
+    return changes;
 }
 
 Room::Changes Room::processAccountDataEvent(EventPtr&& event)
 {
+    Changes changes = NoChange;
     if (auto* evt = eventCast<TagEvent>(event))
+    {
         d->setTags(evt->tags());
+        changes |= Change::TagsChange;
+    }
 
     if (auto* evt = eventCast<ReadMarkerEvent>(event))
     {
@@ -1986,10 +1998,9 @@ Room::Changes Room::processAccountDataEvent(EventPtr&& event)
         qCDebug(MAIN) << "Server-side read marker at" << readEventId;
         d->serverReadMarker = readEventId;
         const auto newMarker = findInTimeline(readEventId);
-        if (newMarker != timelineEdge())
-            d->markMessagesAsRead(newMarker);
-        else
-            d->setLastReadEvent(localUser(), readEventId);
+        changes |= newMarker != timelineEdge()
+            ? d->markMessagesAsRead(newMarker)
+            : d->setLastReadEvent(localUser(), readEventId);
     }
     // For all account data events
     auto& currentData = d->accountData[event->matrixType()];
diff --git a/lib/room.h b/lib/room.h
index 9d4561e5..7533c599 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -116,8 +116,9 @@ namespace QMatrixClient
                 MembersChange = 0x80,
                 EncryptionOn = 0x100,
                 AccountDataChange = 0x200,
-                OtherChange = 0x1000,
-                AnyChange = 0x1FFF
+                ReadMarkerChange = 0x800,
+                OtherChange = 0x8000,
+                AnyChange = 0xFFFF
             };
             Q_DECLARE_FLAGS(Changes, Change)
             Q_FLAG(Changes)
@@ -459,7 +460,7 @@ namespace QMatrixClient
         protected:
             /// Returns true if any of room names/aliases has changed
             virtual Changes processStateEvent(const RoomEvent& e);
-            virtual void processEphemeralEvent(EventPtr&& event);
+            virtual Changes processEphemeralEvent(EventPtr&& event);
             virtual Changes processAccountDataEvent(EventPtr&& event);
             virtual void onAddNewTimelineEvents(timeline_iter_t /*from*/) { }
             virtual void onAddHistoricalTimelineEvents(rev_iter_t /*from*/) { }
-- 
cgit v1.2.3