aboutsummaryrefslogtreecommitdiff
path: root/lib/syncdata.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/syncdata.cpp')
-rw-r--r--lib/syncdata.cpp243
1 files changed, 243 insertions, 0 deletions
diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp
new file mode 100644
index 00000000..ec7203af
--- /dev/null
+++ b/lib/syncdata.cpp
@@ -0,0 +1,243 @@
+// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "syncdata.h"
+
+#include "logging.h"
+
+#include <QtCore/QFile>
+#include <QtCore/QFileInfo>
+
+using namespace Quotient;
+
+bool RoomSummary::isEmpty() const
+{
+ return !joinedMemberCount && !invitedMemberCount && !heroes;
+}
+
+bool RoomSummary::merge(const RoomSummary& other)
+{
+ // Using bitwise OR to prevent computation shortcut.
+ return static_cast<bool>(
+ static_cast<int>(joinedMemberCount.merge(other.joinedMemberCount))
+ | static_cast<int>(invitedMemberCount.merge(other.invitedMemberCount))
+ | static_cast<int>(heroes.merge(other.heroes)));
+}
+
+QDebug Quotient::operator<<(QDebug dbg, const RoomSummary& rs)
+{
+ QDebugStateSaver _(dbg);
+ QStringList sl;
+ if (rs.joinedMemberCount)
+ sl << QStringLiteral("joined: %1").arg(*rs.joinedMemberCount);
+ if (rs.invitedMemberCount)
+ sl << QStringLiteral("invited: %1").arg(*rs.invitedMemberCount);
+ if (rs.heroes)
+ sl << QStringLiteral("heroes: [%1]").arg(rs.heroes->join(','));
+ dbg.nospace().noquote() << sl.join(QStringLiteral("; "));
+ return dbg;
+}
+
+void JsonObjectConverter<RoomSummary>::dumpTo(QJsonObject& jo,
+ const RoomSummary& rs)
+{
+ addParam<IfNotEmpty>(jo, QStringLiteral("m.joined_member_count"),
+ rs.joinedMemberCount);
+ addParam<IfNotEmpty>(jo, QStringLiteral("m.invited_member_count"),
+ rs.invitedMemberCount);
+ addParam<IfNotEmpty>(jo, QStringLiteral("m.heroes"), rs.heroes);
+}
+
+void JsonObjectConverter<RoomSummary>::fillFrom(const QJsonObject& jo,
+ RoomSummary& rs)
+{
+ fromJson(jo["m.joined_member_count"_ls], rs.joinedMemberCount);
+ fromJson(jo["m.invited_member_count"_ls], rs.invitedMemberCount);
+ fromJson(jo["m.heroes"_ls], rs.heroes);
+}
+
+template <typename EventsArrayT, typename StrT>
+inline EventsArrayT load(const QJsonObject& batches, StrT keyName)
+{
+ return fromJson<EventsArrayT>(batches[keyName].toObject().value("events"_ls));
+}
+
+SyncRoomData::SyncRoomData(QString roomId_, JoinState joinState,
+ const QJsonObject& roomJson)
+ : roomId(std::move(roomId_))
+ , joinState(joinState)
+ , summary(fromJson<RoomSummary>(roomJson["summary"_ls]))
+ , state(load<StateEvents>(roomJson, joinState == JoinState::Invite
+ ? "invite_state"_ls
+ : "state"_ls))
+{
+ switch (joinState) {
+ case JoinState::Join:
+ ephemeral = load<Events>(roomJson, "ephemeral"_ls);
+ [[fallthrough]];
+ case JoinState::Leave: {
+ accountData = load<Events>(roomJson, "account_data"_ls);
+ timeline = load<RoomEvents>(roomJson, "timeline"_ls);
+ const auto timelineJson = roomJson.value("timeline"_ls).toObject();
+ timelineLimited = timelineJson.value("limited"_ls).toBool();
+ timelinePrevBatch = timelineJson.value("prev_batch"_ls).toString();
+
+ break;
+ }
+ default: /* nothing on top of state */;
+ }
+
+ const auto unreadJson = roomJson.value(UnreadNotificationsKey).toObject();
+
+ fromJson(unreadJson.value(PartiallyReadCountKey), partiallyReadCount);
+ if (!partiallyReadCount.has_value())
+ fromJson(unreadJson.value("x-quotient.unread_count"_ls),
+ partiallyReadCount);
+
+ fromJson(roomJson.value(NewUnreadCountKey), unreadCount);
+ if (!unreadCount.has_value())
+ fromJson(unreadJson.value("notification_count"_ls), unreadCount);
+ fromJson(unreadJson.value(HighlightCountKey), highlightCount);
+}
+
+QDebug Quotient::operator<<(QDebug dbg, const DevicesList& devicesList)
+{
+ QDebugStateSaver _(dbg);
+ QStringList sl;
+ if (!devicesList.changed.isEmpty())
+ sl << QStringLiteral("changed: %1").arg(devicesList.changed.join(", "));
+ if (!devicesList.left.isEmpty())
+ sl << QStringLiteral("left %1").arg(devicesList.left.join(", "));
+ dbg.nospace().noquote() << sl.join(QStringLiteral("; "));
+ return dbg;
+}
+
+void JsonObjectConverter<DevicesList>::dumpTo(QJsonObject& jo,
+ const DevicesList& rs)
+{
+ addParam<IfNotEmpty>(jo, QStringLiteral("changed"),
+ rs.changed);
+ addParam<IfNotEmpty>(jo, QStringLiteral("left"),
+ rs.left);
+}
+
+void JsonObjectConverter<DevicesList>::fillFrom(const QJsonObject& jo,
+ DevicesList& rs)
+{
+ fromJson(jo["changed"_ls], rs.changed);
+ fromJson(jo["left"_ls], rs.left);
+}
+
+SyncData::SyncData(const QString& cacheFileName)
+{
+ QFileInfo cacheFileInfo { cacheFileName };
+ auto json = loadJson(cacheFileName);
+ auto requiredVersion = MajorCacheVersion;
+ auto actualVersion =
+ json.value("cache_version"_ls).toObject().value("major"_ls).toInt();
+ if (actualVersion == requiredVersion)
+ parseJson(json, cacheFileInfo.absolutePath() + '/');
+ else
+ qCWarning(MAIN) << "Major version of the cache file is" << actualVersion
+ << "but" << requiredVersion
+ << "is required; discarding the cache";
+}
+
+SyncDataList SyncData::takeRoomData() { return move(roomData); }
+
+QString SyncData::fileNameForRoom(QString roomId)
+{
+ roomId.replace(':', '_');
+ return roomId + ".json";
+}
+
+Events SyncData::takePresenceData() { return std::move(presenceData); }
+
+Events SyncData::takeAccountData() { return std::move(accountData); }
+
+Events SyncData::takeToDeviceEvents() { return std::move(toDeviceEvents); }
+
+std::pair<int, int> SyncData::cacheVersion()
+{
+ return { MajorCacheVersion, 2 };
+}
+
+DevicesList SyncData::takeDevicesList() { return std::move(devicesList); }
+
+QJsonObject SyncData::loadJson(const QString& fileName)
+{
+ QFile roomFile { fileName };
+ if (!roomFile.exists()) {
+ qCWarning(MAIN) << "No state cache file" << fileName;
+ return {};
+ }
+ if (!roomFile.open(QIODevice::ReadOnly)) {
+ qCWarning(MAIN) << "Failed to open state cache file"
+ << roomFile.fileName();
+ return {};
+ }
+ auto data = roomFile.readAll();
+
+ const auto json = data.startsWith('{')
+ ? QJsonDocument::fromJson(data).object()
+ : QCborValue::fromCbor(data).toJsonValue().toObject();
+ if (json.isEmpty()) {
+ qCWarning(MAIN) << "State cache in" << fileName
+ << "is broken or empty, discarding";
+ }
+ return json;
+}
+
+void SyncData::parseJson(const QJsonObject& json, const QString& baseDir)
+{
+ QElapsedTimer et;
+ et.start();
+
+ nextBatch_ = json.value("next_batch"_ls).toString();
+ presenceData = load<Events>(json, "presence"_ls);
+ accountData = load<Events>(json, "account_data"_ls);
+ toDeviceEvents = load<Events>(json, "to_device"_ls);
+
+ fromJson(json.value("device_one_time_keys_count"_ls),
+ deviceOneTimeKeysCount_);
+
+ if(json.contains("device_lists")) {
+ fromJson(json.value("device_lists"), devicesList);
+ }
+
+ auto rooms = json.value("rooms"_ls).toObject();
+ auto totalRooms = 0;
+ auto totalEvents = 0;
+ for (size_t i = 0; i < JoinStateStrings.size(); ++i) {
+ // This assumes that MemberState values go over powers of 2: 1,2,4,...
+ const auto joinState = JoinState(1U << i);
+ const auto rs = rooms.value(JoinStateStrings[i]).toObject();
+ // We have a Qt container on the right and an STL one on the left
+ roomData.reserve(roomData.size() + static_cast<size_t>(rs.size()));
+ for (auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt) {
+ QJsonObject roomJson;
+ if (!baseDir.isEmpty()) {
+ // Loading data from the local cache, with room objects saved in
+ // individual files rather than inline
+ roomJson = loadJson(baseDir + fileNameForRoom(roomIt.key()));
+ if (roomJson.isEmpty()) {
+ unresolvedRoomIds.push_back(roomIt.key());
+ continue;
+ }
+ } else // When loading from /sync response, everything is inline
+ roomJson = roomIt->toObject();
+
+ roomData.emplace_back(roomIt.key(), joinState, roomJson);
+ const auto& r = roomData.back();
+ totalEvents += r.state.size() + r.ephemeral.size()
+ + r.accountData.size() + r.timeline.size();
+ }
+ totalRooms += rs.size();
+ }
+ if (!unresolvedRoomIds.empty())
+ qCWarning(MAIN) << "Unresolved rooms:" << unresolvedRoomIds.join(',');
+ if (totalRooms > 9 || et.nsecsElapsed() >= profilerMinNsecs())
+ qCDebug(PROFILER) << "*** SyncData::parseJson(): batch with"
+ << totalRooms << "room(s)," << totalEvents
+ << "event(s) in" << et;
+}