Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F8159192
D241.1760161874.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
24 KB
Referenced Files
None
Subscribers
None
D241.1760161874.diff
View Options
diff --git a/src/base/kazvevents.hpp b/src/base/kazvevents.hpp
--- a/src/base/kazvevents.hpp
+++ b/src/base/kazvevents.hpp
@@ -324,6 +324,25 @@
std::string error;
};
+ /**
+ * Indicate that there are events to be saved.
+ */
+ struct SaveEventsRequested
+ {
+ /**
+ * The events to be saved in the timeline.
+ * The events saved in the timeline part of the storage can be considered continuous, i.e. they can be
+ * loaded to fully re-construct the timeline. There can be gaps,
+ * but all gaps are properly recorded in the client state so that
+ * it can be easily paginated.
+ */
+ immer::map<std::string /* roomId */, EventList> timelineEvents;
+ /**
+ * The events that should be saved, but not in the timeline.
+ */
+ immer::map<std::string /* roomId */, EventList> nonTimelineEvents;
+ };
+
struct UnrecognizedResponse
{
Response response;
@@ -382,6 +401,8 @@
UploadIdentityKeysSuccessful, UploadIdentityKeysFailed,
UploadOneTimeKeysSuccessful, UploadOneTimeKeysFailed,
ClaimKeysSuccessful, ClaimKeysFailed,
+ // storage
+ SaveEventsRequested,
// general
UnrecognizedResponse,
diff --git a/src/base/serialization/immer-set.hpp b/src/base/serialization/immer-set.hpp
new file mode 100644
--- /dev/null
+++ b/src/base/serialization/immer-set.hpp
@@ -0,0 +1,58 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2021-2025 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+
+#pragma once
+
+#include <libkazv-config.hpp>
+
+#include <boost/serialization/nvp.hpp>
+#include <boost/serialization/split_free.hpp>
+
+#include <immer/set.hpp>
+#include <immer/set_transient.hpp>
+
+namespace boost::serialization
+{
+
+ template <class Archive, class T, class H, class E, class MP>
+ void save(Archive &ar, const immer::set<T, H, E, MP> &s, const unsigned int /* version */)
+ {
+ auto size = s.size();
+ ar << BOOST_SERIALIZATION_NVP(size);
+ for (const auto &k : s) {
+ ar << k;
+ }
+ }
+
+ template <class Archive, class T, class H, class E, class MP>
+ void load(Archive &ar, immer::set<T, H, E, MP> &s, const unsigned int /* version */)
+ {
+ using TransientT = decltype(s.transient());
+ using SizeT = decltype(s.size());
+
+ TransientT t{};
+
+ SizeT size{};
+ ar >> BOOST_SERIALIZATION_NVP(size);
+
+ for (auto i = SizeT{}; i < size; ++i) {
+ T k;
+ ar >> k;
+ t.insert(std::move(k));
+ }
+
+ assert(size == t.size());
+ s = t.persistent();
+ }
+
+ template<class Archive, class T, class H, class E, class MP>
+ inline void serialize(Archive &ar, immer::set<T, H, E, MP> &s, const unsigned int version)
+ {
+ boost::serialization::split_free(ar, s, version);
+ }
+
+}
diff --git a/src/client/client-model.hpp b/src/client/client-model.hpp
--- a/src/client/client-model.hpp
+++ b/src/client/client-model.hpp
@@ -193,6 +193,8 @@
return triggers;
}
+ void maybeAddSaveEventsTrigger(const ClientModel &old);
+
using Action = ClientAction;
using Effect = ClientEffect;
using Result = ClientResult;
diff --git a/src/client/client-model.cpp b/src/client/client-model.cpp
--- a/src/client/client-model.cpp
+++ b/src/client/client-model.cpp
@@ -43,6 +43,8 @@
auto oldClient = m;
auto oldDeviceLists = m.deviceLists;
+ auto actionIsStorage = std::holds_alternative<LoadEventsFromStorageAction>(a) || std::holds_alternative<PurgeRoomTimelineAction>(a);
+
auto [newClient, effect] = lager::match(std::move(a))(
[&](RoomListAction a) -> Result {
m.roomList = RoomListModel::update(std::move(m.roomList), a);
@@ -120,6 +122,12 @@
newClient.maybeRotateSessions(oldClient);
+ // if it is an storage action, do not add it back because the things
+ // should be the same as the ones in storage
+ if (!actionIsStorage) {
+ newClient.maybeAddSaveEventsTrigger(oldClient);
+ }
+
return { std::move(newClient), std::move(effect) };
}
@@ -423,4 +431,64 @@
{
return crypto.value().get();
}
+
+ void ClientModel::maybeAddSaveEventsTrigger(const ClientModel &old)
+ {
+ SaveEventsRequested trigger;
+ using ELT = immer::flex_vector_transient<Event>;
+ auto addToTrigger = [&trigger](const std::string &roomId, ELT tl, ELT nonTl) {
+ if (!tl.empty()) {
+ trigger.timelineEvents = std::move(trigger.timelineEvents).set(roomId, std::move(tl).persistent());
+ }
+ if (!nonTl.empty()) {
+ trigger.nonTimelineEvents = std::move(trigger.nonTimelineEvents).set(roomId, std::move(nonTl).persistent());
+ }
+ };
+ immer::diff(old.roomList.rooms, roomList.rooms, immer::make_differ(
+ /* addedFn = */ [&addToTrigger](const auto &p) {
+ const auto &[roomId, room] = p;
+
+ ELT tl;
+ ELT nonTl;
+ for (const auto &[id, e] : room.messages) {
+ if (room.isInTimeline(id)) {
+ tl.push_back(e);
+ } else {
+ nonTl.push_back(e);
+ }
+ }
+
+ addToTrigger(roomId, std::move(tl), std::move(nonTl));
+ },
+ /* removedFn = */ [](auto &&) {},
+ /* changedFn = */ [&addToTrigger](const auto &p1, const auto &p2) {
+ const auto &roomId = p2.first;
+ const auto &newRoom = p2.second;
+ const auto &oldRoom = p1.second;
+
+ ELT tl;
+ ELT nonTl;
+ auto addEvent = [&tl, &nonTl, &newRoom](const auto &newPair) {
+ const auto &[eventId, event] = newPair;
+ if (newRoom.isInTimeline(eventId)) {
+ tl.push_back(event);
+ } else {
+ nonTl.push_back(event);
+ }
+ };
+ immer::diff(oldRoom.messages, newRoom.messages, immer::make_differ(
+ /* addedFn = */ addEvent,
+ /* removedFn = */ [](auto &&) {},
+ /* changedFn = */ [&addEvent](auto &&, auto &&newPair) {
+ addEvent(std::forward<decltype(newPair)>(newPair));
+ }
+ ));
+
+ addToTrigger(roomId, std::move(tl), std::move(nonTl));
+ }
+ ));
+ if (!trigger.timelineEvents.empty() || !trigger.nonTimelineEvents.empty()) {
+ nextTriggers = std::move(nextTriggers).push_back(trigger);
+ }
+ }
}
diff --git a/src/client/room/room-model.hpp b/src/client/room/room-model.hpp
--- a/src/client/room/room-model.hpp
+++ b/src/client/room/room-model.hpp
@@ -12,10 +12,12 @@
#include <variant>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
+#include <immer/set.hpp>
#include <serialization/immer-flex-vector.hpp>
#include <serialization/immer-box.hpp>
#include <serialization/immer-map.hpp>
+#include <serialization/immer-set.hpp>
#include <serialization/immer-array.hpp>
#include <csapi/sync.hpp>
@@ -78,6 +80,8 @@
struct AddMessagesAction
{
EventList events;
+ /// @internal only to be used by AddToTimelineAction
+ bool alsoInTimeline{false};
};
struct AddToTimelineAction
@@ -304,6 +308,11 @@
immer::flex_vector<std::string /* eventId */>> undecryptedEvents;
immer::flex_vector<std::string> unreadNotificationEventIds;
+ /// The set of event ids that are in `messages` but not in `timeline`.
+ /// The rationale is that non-timeline events are sparse, so
+ /// instead of recording events that are in the timeline, we
+ /// record those not in the timeline.
+ immer::set<std::string> nonTimelineEvents;
immer::flex_vector<std::string> joinedMemberIds() const;
immer::flex_vector<std::string> invitedMemberIds() const;
@@ -357,6 +366,15 @@
*/
bool checkInvariants() const;
+ /**
+ * Check if the event is in the timeline.
+ *
+ * This function takes constant time.
+ * @param eventId The id of the event to check.
+ * @return true iff the event is in the timeline.
+ */
+ bool isInTimeline(const std::string &eventId) const;
+
using Action = std::variant<
AddStateEventsAction,
MaybeAddStateEventsAction,
@@ -416,6 +434,7 @@
&& a.eventReadUsers == b.eventReadUsers
&& a.undecryptedEvents == b.undecryptedEvents
&& a.unreadNotificationEventIds == b.unreadNotificationEventIds
+ && a.nonTimelineEvents == b.nonTimelineEvents
;
}
@@ -511,6 +530,9 @@
if (version >= 8) {
ar & r.unreadNotificationEventIds;
}
+ if (version >= 9) {
+ ar & r.nonTimelineEvents;
+ }
}
template<class Archive>
@@ -522,5 +544,5 @@
BOOST_CLASS_VERSION(Kazv::PendingRoomKeyEvent, 1)
BOOST_CLASS_VERSION(Kazv::ReadReceipt, 0)
-BOOST_CLASS_VERSION(Kazv::RoomModel, 8)
+BOOST_CLASS_VERSION(Kazv::RoomModel, 9)
BOOST_CLASS_VERSION(Kazv::RoomListModel, 0)
diff --git a/src/client/room/room-model.cpp b/src/client/room/room-model.cpp
--- a/src/client/room/room-model.cpp
+++ b/src/client/room/room-model.cpp
@@ -106,6 +106,11 @@
},
[&](AddMessagesAction a) {
r.messages = merge(std::move(r.messages), a.events, keyOfTimeline);
+ if (!a.alsoInTimeline) {
+ for (const auto &e : a.events) {
+ r.nonTimelineEvents = std::move(r.nonTimelineEvents).insert(keyOfTimeline(e));
+ }
+ }
auto handleRedaction =
[&r](const auto &event) {
@@ -153,7 +158,7 @@
zug::map(keyOfTimeline), a.events);
auto oldMessages = r.messages;
- auto next = RoomModel::update(std::move(r), AddMessagesAction{a.events});
+ auto next = RoomModel::update(std::move(r), AddMessagesAction{a.events, /* alsoInTimeline = */ true});
r = std::move(next);
auto key =
[=](auto eventId) {
@@ -165,6 +170,10 @@
// Let `exists` function to always return false, so that it is checked for duplicates automatically.
r.timeline = sortedUniqueMerge(r.timeline, eventIds, [](auto &&) { return false; }, key);
+ for (const auto &e : eventIds) {
+ r.nonTimelineEvents = std::move(r.nonTimelineEvents).erase(e);
+ }
+
// We have 3 possibilities for the source of calling this action:
// pagination, sync, or load from storage.
//
@@ -767,4 +776,9 @@
});
});
}
+
+ bool RoomModel::isInTimeline(const std::string &eventId) const
+ {
+ return !nonTimelineEvents.count(eventId) && messages.count(eventId);
+ }
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -91,6 +91,7 @@
client/get-versions-test.cpp
client/alias-test.cpp
client/encode-test.cpp
+ client/maybe-add-save-events-trigger-benchmark-test.cpp
EXTRA_LINK_LIBRARIES kazvclient kazveventemitter kazvjob client-test-lib kazvtestfixtures
EXTRA_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/client
)
diff --git a/src/tests/base/serialization-test.cpp b/src/tests/base/serialization-test.cpp
--- a/src/tests/base/serialization-test.cpp
+++ b/src/tests/base/serialization-test.cpp
@@ -14,6 +14,7 @@
#include <serialization/immer-flex-vector.hpp>
#include <serialization/immer-map.hpp>
+#include <serialization/immer-set.hpp>
#include <serialization/immer-box.hpp>
#include <serialization/immer-array.hpp>
#include <serialization/std-optional.hpp>
@@ -72,6 +73,14 @@
serializeTest(v, v2);
}
+TEST_CASE("Serialize immer::set", "[base][serialization]")
+{
+ auto v = immer::set<int>{1, 2, 3, 4, 5};
+ auto v2 = immer::set<int>{}.insert(10);
+
+ serializeTest(v, v2);
+}
+
TEST_CASE("Serialize immer::box", "[base][serialization]")
{
auto v = immer::box<int>{42};
diff --git a/src/tests/client/maybe-add-save-events-trigger-benchmark-test.cpp b/src/tests/client/maybe-add-save-events-trigger-benchmark-test.cpp
new file mode 100644
--- /dev/null
+++ b/src/tests/client/maybe-add-save-events-trigger-benchmark-test.cpp
@@ -0,0 +1,121 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2025 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+#include "factory.hpp"
+#include <client-model.hpp>
+#include <zug/transducer/repeat.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/benchmark/catch_benchmark.hpp>
+#include <iostream>
+
+using namespace Kazv;
+using namespace Kazv::Factory;
+
+static auto generateChanges(std::size_t newCount, std::size_t changedCount, const EventList &events)
+{
+ return intoImmer(
+ EventList{},
+ zug::repeatn(newCount, 0)
+ | zug::map([](auto &&) {
+ return makeEvent();
+ })
+ ) + intoImmer(
+ EventList{},
+ zug::take(changedCount)
+ | zug::map([](auto &&e) {
+ return makeEvent(withEventId(e.id()) | withEventContent(json{{"mew", "mew"}}));
+ }),
+ events
+ );
+}
+
+// Assumption is that new/changed messages take only a small part in the whole room.
+static std::pair<ClientModel, ClientModel> data(
+ std::size_t roomCount,
+ std::size_t timelineMessagesPerRoom,
+ std::size_t nonTimelineMessagesPerRoom,
+ std::size_t newTimelineMessagesPerRoom,
+ std::size_t newNonTimelineMessagesPerRoom,
+ std::size_t changedTimelineMessagesPerRoom,
+ std::size_t changedNonTimelineMessagesPerRoom
+)
+{
+ std::cerr << "Generating data" << std::endl;
+ auto old = makeClient();
+ auto client = makeClient();
+ std::size_t generated = 0;
+
+ for (std::size_t r = 0; r < roomCount; ++r) {
+ auto room = makeRoom();
+ auto events = EventList{};
+ for (std::size_t m = 0; m < timelineMessagesPerRoom; ++m) {
+ auto event = makeEvent();
+ events = std::move(events).push_back(event);
+ ++generated;
+ if (generated % 10000 == 0) {
+ std::cerr << "Generated " << generated << " events" << std::endl;
+ }
+ }
+ withRoomTimeline(events)(room);
+
+ auto origNonTl = EventList{};
+ for (std::size_t m = 0; m < nonTimelineMessagesPerRoom; ++m) {
+ auto event = makeEvent();
+ events = std::move(events).push_back(event);
+ ++generated;
+ if (generated % 10000 == 0) {
+ std::cerr << "Generated " << generated << " events" << std::endl;
+ }
+ }
+
+ room = RoomModel::update(std::move(room), AddMessagesAction{origNonTl});
+ withRoom(room)(old);
+
+ auto tl = generateChanges(newTimelineMessagesPerRoom, changedTimelineMessagesPerRoom, events);
+
+ auto nonTl = generateChanges(newNonTimelineMessagesPerRoom, changedNonTimelineMessagesPerRoom, origNonTl);
+
+ withRoomTimeline(tl)(room);
+ room = RoomModel::update(std::move(room), AddMessagesAction{nonTl});
+ withRoom(room)(client);
+ }
+ return {old, client};
+}
+
+TEST_CASE("maybeAddSaveEventsTrigger() benchmark: small", "[client][storage-actions][!benchmark]")
+{
+ auto [old, client] = data(10, 200, 100, 5, 5, 2, 2);
+ BENCHMARK("maybeAddSaveEventsTrigger()") {
+ return client.maybeAddSaveEventsTrigger(old);
+ };
+}
+
+TEST_CASE("maybeAddSaveEventsTrigger() benchmark: medium", "[client][storage-actions][!benchmark]")
+{
+ auto [old, client] = data(50, 1000, 200, 10, 10, 5, 5);
+ BENCHMARK("maybeAddSaveEventsTrigger()") {
+ return client.maybeAddSaveEventsTrigger(old);
+ };
+}
+
+// unprobable case: all 100 rooms have 80 events added/changed during 1 sync
+TEST_CASE("maybeAddSaveEventsTrigger() benchmark: large", "[client][storage-actions][!benchmark]")
+{
+ auto [old, client] = data(100, 4000, 400, 20, 20, 20, 20);
+ BENCHMARK("maybeAddSaveEventsTrigger()") {
+ return client.maybeAddSaveEventsTrigger(old);
+ };
+}
+
+// unprobable case: all 500 rooms have 20 events added/changed during 1 sync
+TEST_CASE("maybeAddSaveEventsTrigger() benchmark: many rooms with few events", "[client][storage-actions][!benchmark]")
+{
+ auto [old, client] = data(500, 200, 100, 5, 5, 5, 5);
+ BENCHMARK("maybeAddSaveEventsTrigger()") {
+ return client.maybeAddSaveEventsTrigger(old);
+ };
+}
diff --git a/src/tests/client/room/purge-test.cpp b/src/tests/client/room/purge-test.cpp
--- a/src/tests/client/room/purge-test.cpp
+++ b/src/tests/client/room/purge-test.cpp
@@ -153,6 +153,11 @@
auto u = makeMockSdkUtil(m);
auto d = u.getMockDispatcher(passDown<PurgeRoomTimelineAction>());
auto c = u.getClient(d);
+ bool saveEventsRequested = false;
+ auto watchable = u.ee.watchable();
+ watchable.after<SaveEventsRequested>([&saveEventsRequested](auto &&) {
+ saveEventsRequested = true;
+ });
c.purgeRoomEvents({
{r1.roomId, 80},
@@ -166,4 +171,5 @@
REQUIRE(c.room(r1.roomId).timelineEventIds().get().size() == 80);
REQUIRE(c.room(r2.roomId).timelineEventIds().get().size() == 50);
REQUIRE(c.room(r3.roomId).timelineEventIds().get().size() == 100);
+ REQUIRE(!saveEventsRequested);
}
diff --git a/src/tests/client/storage-actions-test.cpp b/src/tests/client/storage-actions-test.cpp
--- a/src/tests/client/storage-actions-test.cpp
+++ b/src/tests/client/storage-actions-test.cpp
@@ -8,13 +8,16 @@
#include "actions/storage.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_quantifiers.hpp>
+#include <catch2/matchers/catch_matchers_range_equals.hpp>
#include <catch2/matchers/catch_matchers_predicate.hpp>
#include <factory.hpp>
using namespace Kazv;
using namespace Kazv::Factory;
using Catch::Matchers::AllMatch;
+using Catch::Matchers::NoneMatch;
using Catch::Matchers::Predicate;
+using Catch::Matchers::UnorderedRangeEquals;
static const std::string roomId = "!test:example.com";
@@ -61,6 +64,11 @@
REQUIRE(room.reverseEventRelationships.count(timelineEvents[0].id()));
REQUIRE(room.reverseEventRelationships.count(existingEvents[0].id()));
+
+ // should not give out any SaveEventsRequested triggers
+ REQUIRE_THAT(next.nextTriggers, NoneMatch(Predicate<KazvEvent>([](const auto &t) {
+ return std::holds_alternative<SaveEventsRequested>(t);
+ })));
}
// Verify the load works when loading into timeline an event in
// messages but not in timeline
@@ -76,3 +84,113 @@
REQUIRE(room.timeline == intoImmer(immer::flex_vector<std::string>{}, zug::map(&Event::id), existingEvents + timelineEvents + nextTimelineEvents));
}
}
+
+TEST_CASE("maybeAddSaveEventsTrigger", "[client][storage-actions]")
+{
+ WHEN("compare with self") {
+ auto c = makeClient(withRoom(makeRoom(withRoomTimeline({
+ makeEvent(),
+ makeEvent(),
+ makeEvent(),
+ }))));
+
+ c.maybeAddSaveEventsTrigger(c);
+ REQUIRE(c.nextTriggers.empty());
+ }
+
+ WHEN("new room, no events") {
+ auto old = makeClient(withRoom(makeRoom(withRoomTimeline({
+ makeEvent(),
+ makeEvent(),
+ makeEvent(),
+ }))));
+ auto c = old;
+ withRoom(makeRoom())(c);
+
+ c.maybeAddSaveEventsTrigger(old);
+ REQUIRE(c.nextTriggers.empty());
+ }
+
+ WHEN("new room, with events") {
+ auto old = makeClient(withRoom(makeRoom(withRoomTimeline({
+ makeEvent(),
+ makeEvent(),
+ makeEvent(),
+ }))));
+ auto c = old;
+ auto tl = EventList{
+ makeEvent(),
+ makeEvent(),
+ };
+ auto room = makeRoom(withRoomTimeline(tl));
+ withRoom(room)(c);
+
+ c.maybeAddSaveEventsTrigger(old);
+ REQUIRE(c.nextTriggers.size() == 1);
+ REQUIRE(std::holds_alternative<SaveEventsRequested>(c.nextTriggers[0]));
+ auto t = std::get<SaveEventsRequested>(c.nextTriggers[0]);
+ REQUIRE(t.nonTimelineEvents.empty());
+ REQUIRE(t.timelineEvents.size() == 1);
+ REQUIRE_THAT(t.timelineEvents[room.roomId], UnorderedRangeEquals(tl));
+ }
+
+ WHEN("new room, with tl and non-tl events") {
+ auto old = makeClient(withRoom(makeRoom(withRoomTimeline({
+ makeEvent(),
+ makeEvent(),
+ makeEvent(),
+ }))));
+ auto c = old;
+ auto tl = EventList{
+ makeEvent(),
+ makeEvent(),
+ };
+ auto nonTl = EventList{makeEvent(), makeEvent()};
+ auto room = makeRoom(withRoomTimeline(tl));
+ room = RoomModel::update(std::move(room), AddMessagesAction{nonTl});
+ withRoom(room)(c);
+
+ c.maybeAddSaveEventsTrigger(old);
+ REQUIRE(c.nextTriggers.size() == 1);
+ REQUIRE(std::holds_alternative<SaveEventsRequested>(c.nextTriggers[0]));
+ auto t = std::get<SaveEventsRequested>(c.nextTriggers[0]);
+ REQUIRE(t.nonTimelineEvents.size() == 1);
+ REQUIRE_THAT(t.nonTimelineEvents[room.roomId], UnorderedRangeEquals(nonTl));
+ REQUIRE(t.timelineEvents.size() == 1);
+ REQUIRE_THAT(t.timelineEvents[room.roomId], UnorderedRangeEquals(tl));
+ }
+
+ WHEN("existing room with new and changed events") {
+ auto origTl = EventList{
+ makeEvent(),
+ makeEvent(),
+ makeEvent(),
+ };
+ auto origNonTl = EventList{makeEvent(), makeEvent()};
+ auto room = makeRoom(withRoomTimeline(origTl));
+ room = RoomModel::update(std::move(room), AddMessagesAction{origNonTl});
+ auto old = makeClient(withRoom(room));
+ auto c = old;
+ auto tl = EventList{
+ makeEvent(withEventId(origTl[0].id()) | withEventContent(json{{"mew", "mew"}})),
+ makeEvent(),
+ makeEvent(),
+ };
+ withRoomTimeline(tl)(room);
+ auto nonTl = EventList{
+ makeEvent(withEventId(origNonTl[0].id()) | withEventContent(json{{"mew", "mew"}})),
+ makeEvent(),
+ };
+ room = RoomModel::update(std::move(room), AddMessagesAction{nonTl});
+ withRoom(room)(c);
+
+ c.maybeAddSaveEventsTrigger(old);
+ REQUIRE(c.nextTriggers.size() == 1);
+ REQUIRE(std::holds_alternative<SaveEventsRequested>(c.nextTriggers[0]));
+ auto t = std::get<SaveEventsRequested>(c.nextTriggers[0]);
+ REQUIRE(t.timelineEvents.size() == 1);
+ REQUIRE_THAT(t.timelineEvents[room.roomId], UnorderedRangeEquals(tl));
+ REQUIRE(t.nonTimelineEvents.size() == 1);
+ REQUIRE_THAT(t.nonTimelineEvents[room.roomId], UnorderedRangeEquals(nonTl));
+ }
+}
diff --git a/src/tests/client/sync-test.cpp b/src/tests/client/sync-test.cpp
--- a/src/tests/client/sync-test.cpp
+++ b/src/tests/client/sync-test.cpp
@@ -743,3 +743,24 @@
REQUIRE(!event.decrypted());
REQUIRE(event.content().get().at("moe.kazv.mxc.errcode") == exceptedErrorCode);
}
+
+TEST_CASE("emit SaveEventsRequested", "[client][sync]")
+{
+ ClientModel m = makeClient(
+ withRoom(makeRoom(
+ withRoomId("!exampleroomid:example.com"))));
+
+ auto resp = makeResponse(
+ "Sync",
+ withResponseJsonBody(syncResponseJson)
+ | withResponseDataKV("is", "incremental")
+ );
+ auto [next, _] = ClientModel::update(m, ProcessResponseAction{SyncResponse{resp}});
+
+ auto it = std::find_if(next.nextTriggers.begin(), next.nextTriggers.end(), [](const auto &trigger) {
+ return std::holds_alternative<SaveEventsRequested>(trigger);
+ });
+ REQUIRE(it != next.nextTriggers.end());
+ auto t = std::get<SaveEventsRequested>(*it);
+ REQUIRE(t.timelineEvents["!726s6s6q:example.com"].size() == 2);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Oct 10, 10:51 PM (15 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
509868
Default Alt Text
D241.1760161874.diff (24 KB)
Attached To
Mode
D241: Add triggers for saving events
Attached
Detach File
Event Timeline
Log In to Comment