diff --git a/src/Cache.cpp b/src/Cache.cpp
index 852d45ec..d2c790dd 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1272,7 +1272,10 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, int64_t i
int counter = 0;
bool ret;
- while ((ret = cursor.get(indexVal, event_id, forward ? MDB_NEXT : MDB_LAST)) &&
+ while ((ret = cursor.get(indexVal,
+ event_id,
+ counter == 0 ? (forward ? MDB_FIRST : MDB_LAST)
+ : (forward ? MDB_NEXT : MDB_PREV))) &&
counter++ < BATCH_SIZE) {
lmdb::val event;
bool success = lmdb::dbi_get(txn, eventsDb, event_id, event);
@@ -1280,8 +1283,13 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, int64_t i
continue;
mtx::events::collections::TimelineEvent te;
- mtx::events::collections::from_json(
- json::parse(std::string_view(event.data(), event.size())), te);
+ try {
+ mtx::events::collections::from_json(
+ json::parse(std::string_view(event.data(), event.size())), te);
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to parse message from cache {}", e.what());
+ continue;
+ }
messages.timeline.events.push_back(std::move(te.data));
}
@@ -1306,8 +1314,13 @@ Cache::getEvent(const std::string &room_id, const std::string &event_id)
return {};
mtx::events::collections::TimelineEvent te;
- mtx::events::collections::from_json(
- json::parse(std::string_view(event.data(), event.size())), te);
+ try {
+ mtx::events::collections::from_json(
+ json::parse(std::string_view(event.data(), event.size())), te);
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to parse message from cache {}", e.what());
+ return std::nullopt;
+ }
return te;
}
@@ -1364,6 +1377,61 @@ Cache::getLastEventId(lmdb::txn &txn, const std::string &room_id)
return std::string(val.data(), val.size());
}
+std::optional<Cache::TimelineRange>
+Cache::getTimelineRange(const std::string &room_id)
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto orderDb = getOrderToMessageDb(txn, room_id);
+
+ lmdb::val indexVal, val;
+
+ auto cursor = lmdb::cursor::open(txn, orderDb);
+ if (!cursor.get(indexVal, val, MDB_LAST)) {
+ return {};
+ }
+
+ TimelineRange range{};
+ range.last = *indexVal.data<int64_t>();
+
+ if (!cursor.get(indexVal, val, MDB_FIRST)) {
+ return {};
+ }
+ range.first = *indexVal.data<int64_t>();
+
+ return range;
+}
+std::optional<int64_t>
+Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id)
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto orderDb = getMessageToOrderDb(txn, room_id);
+
+ lmdb::val indexVal{event_id.data(), event_id.size()}, val;
+
+ bool success = lmdb::dbi_get(txn, orderDb, indexVal, val);
+ if (!success) {
+ return {};
+ }
+
+ return *val.data<int64_t>();
+}
+
+std::optional<std::string>
+Cache::getTimelineEventId(const std::string &room_id, int64_t index)
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto orderDb = getOrderToMessageDb(txn, room_id);
+
+ lmdb::val indexVal{&index, sizeof(index)}, val;
+
+ bool success = lmdb::dbi_get(txn, orderDb, indexVal, val);
+ if (!success) {
+ return {};
+ }
+
+ return std::string(val.data(), val.size());
+}
+
DescInfo
Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
{
@@ -1379,8 +1447,10 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
lmdb::val indexVal, event_id;
auto cursor = lmdb::cursor::open(txn, orderDb);
- cursor.get(indexVal, event_id, MDB_LAST);
- while (cursor.get(indexVal, event_id, MDB_PREV)) {
+ bool first = true;
+ while (cursor.get(indexVal, event_id, first ? MDB_LAST : MDB_PREV)) {
+ first = false;
+
lmdb::val event;
bool success = lmdb::dbi_get(txn, eventsDb, event_id, event);
if (!success)
@@ -2026,8 +2096,43 @@ Cache::saveTimelineMessages(lmdb::txn &txn,
auto event = mtx::accessors::serialize_event(e);
if (auto redaction =
std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
- lmdb::dbi_put(
- txn, eventsDb, lmdb::val(redaction->redacts), lmdb::val(event.dump()));
+ if (redaction->redacts.empty())
+ continue;
+
+ lmdb::val ev{};
+ bool success =
+ lmdb::dbi_get(txn, eventsDb, lmdb::val(redaction->redacts), ev);
+ if (!success)
+ continue;
+
+ mtx::events::collections::TimelineEvent te;
+
+ try {
+ mtx::events::collections::from_json(
+ json::parse(std::string_view(ev.data(), ev.size())), te);
+ } catch (std::exception &e) {
+ nhlog::db()->error("Failed to parse message from cache {}",
+ e.what());
+ continue;
+ }
+
+ auto redactedEvent = std::visit(
+ [](const auto &ev) -> mtx::events::RoomEvent<mtx::events::msg::Redacted> {
+ mtx::events::RoomEvent<mtx::events::msg::Redacted> replacement =
+ {};
+ replacement.event_id = ev.event_id;
+ replacement.room_id = ev.room_id;
+ replacement.sender = ev.sender;
+ replacement.origin_server_ts = ev.origin_server_ts;
+ replacement.type = ev.type;
+ return replacement;
+ },
+ te.data);
+
+ lmdb::dbi_put(txn,
+ eventsDb,
+ lmdb::val(redaction->redacts),
+ lmdb::val(json(redactedEvent).dump()));
} else {
std::string event_id_val = event["event_id"].get<std::string>();
lmdb::val event_id = event_id_val;
@@ -2237,8 +2342,10 @@ Cache::deleteOldMessages()
if (message_count < MAX_RESTORED_MESSAGES)
continue;
- while (cursor.get(indexVal, val, MDB_NEXT) &&
+ bool start = true;
+ while (cursor.get(indexVal, val, start ? MDB_FIRST : MDB_NEXT) &&
message_count-- < MAX_RESTORED_MESSAGES) {
+ start = false;
auto obj = json::parse(std::string_view(val.data(), val.size()));
if (obj.count("event_id") != 0) {
@@ -2394,6 +2501,9 @@ Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
mtx::presence::PresenceState
Cache::presenceState(const std::string &user_id)
{
+ if (user_id.empty())
+ return {};
+
lmdb::val presenceVal;
auto txn = lmdb::txn::begin(env_);
@@ -2416,6 +2526,9 @@ Cache::presenceState(const std::string &user_id)
std::string
Cache::statusMessage(const std::string &user_id)
{
+ if (user_id.empty())
+ return {};
+
lmdb::val presenceVal;
auto txn = lmdb::txn::begin(env_);
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 3f7b592d..40c8e98b 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -170,6 +170,30 @@ public:
//! Add all notifications containing a user mention to the db.
void saveTimelineMentions(const mtx::responses::Notifications &res);
+ //! retrieve events in timeline and related functions
+ struct Messages
+ {
+ mtx::responses::Timeline timeline;
+ uint64_t next_index;
+ bool end_of_cache = false;
+ };
+ Messages getTimelineMessages(lmdb::txn &txn,
+ const std::string &room_id,
+ int64_t index = std::numeric_limits<int64_t>::max(),
+ bool forward = false);
+
+ std::optional<mtx::events::collections::TimelineEvent> getEvent(
+ const std::string &room_id,
+ const std::string &event_id);
+ struct TimelineRange
+ {
+ int64_t first, last;
+ };
+ std::optional<TimelineRange> getTimelineRange(const std::string &room_id);
+ std::optional<int64_t> getTimelineIndex(const std::string &room_id,
+ std::string_view event_id);
+ std::optional<std::string> getTimelineEventId(const std::string &room_id, int64_t index);
+
//! Remove old unused data.
void deleteOldMessages();
void deleteOldData() noexcept;
@@ -248,21 +272,6 @@ private:
const std::string &room_id,
const mtx::responses::Timeline &res);
- struct Messages
- {
- mtx::responses::Timeline timeline;
- uint64_t next_index;
- bool end_of_cache = false;
- };
- Messages getTimelineMessages(lmdb::txn &txn,
- const std::string &room_id,
- int64_t index = std::numeric_limits<int64_t>::max(),
- bool forward = false);
-
- std::optional<mtx::events::collections::TimelineEvent> getEvent(
- const std::string &room_id,
- const std::string &event_id);
-
//! Remove a room from the cache.
// void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
template<class T>
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
new file mode 100644
index 00000000..eb54d475
--- /dev/null
+++ b/src/timeline/EventStore.cpp
@@ -0,0 +1,259 @@
+#include "EventStore.h"
+
+#include <QThread>
+
+#include "Cache_p.h"
+#include "EventAccessors.h"
+#include "Logging.h"
+#include "Olm.h"
+
+QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::decryptedEvents_{
+ 1000};
+QCache<EventStore::IdIndex, mtx::events::collections::TimelineEvents> EventStore::events_by_id_{
+ 1000};
+QCache<EventStore::Index, mtx::events::collections::TimelineEvents> EventStore::events_{1000};
+
+EventStore::EventStore(std::string room_id, QObject *)
+ : room_id_(std::move(room_id))
+{
+ auto range = cache::client()->getTimelineRange(room_id_);
+
+ if (range) {
+ this->first = range->first;
+ this->last = range->last;
+ }
+}
+
+void
+EventStore::handleSync(const mtx::responses::Timeline &events)
+{
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ auto range = cache::client()->getTimelineRange(room_id_);
+
+ if (range && range->last > this->last) {
+ emit beginInsertRows(toExternalIdx(this->last + 1), toExternalIdx(range->last));
+ this->last = range->last;
+ emit endInsertRows();
+ }
+
+ for (const auto &event : events.events) {
+ std::string relates_to;
+ if (auto redaction =
+ std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(
+ &event)) {
+ relates_to = redaction->redacts;
+ } else if (auto reaction =
+ std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
+ &event)) {
+ relates_to = reaction->content.relates_to.event_id;
+ } else {
+ relates_to = mtx::accessors::in_reply_to_event(event);
+ }
+
+ if (!relates_to.empty()) {
+ auto idx = cache::client()->getTimelineIndex(room_id_, relates_to);
+ if (idx)
+ emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx));
+ }
+ }
+}
+
+mtx::events::collections::TimelineEvents *
+EventStore::event(int idx, bool decrypt)
+{
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ Index index{room_id_, toInternalIdx(idx)};
+ if (index.idx > last || index.idx < first)
+ return nullptr;
+
+ auto event_ptr = events_.object(index);
+ if (!event_ptr) {
+ auto event_id = cache::client()->getTimelineEventId(room_id_, index.idx);
+ if (!event_id)
+ return nullptr;
+
+ auto event = cache::client()->getEvent(room_id_, *event_id);
+ if (!event)
+ return nullptr;
+ else
+ event_ptr =
+ new mtx::events::collections::TimelineEvents(std::move(event->data));
+ events_.insert(index, event_ptr);
+ }
+
+ if (decrypt)
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ event_ptr))
+ return decryptEvent({room_id_, encrypted->event_id}, *encrypted);
+
+ return event_ptr;
+}
+
+std::optional<int>
+EventStore::idToIndex(std::string_view id) const
+{
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ auto idx = cache::client()->getTimelineIndex(room_id_, id);
+ if (idx)
+ return toExternalIdx(*idx);
+ else
+ return std::nullopt;
+}
+std::optional<std::string>
+EventStore::indexToId(int idx) const
+{
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx));
+}
+
+mtx::events::collections::TimelineEvents *
+EventStore::decryptEvent(const IdIndex &idx,
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e)
+{
+ if (auto cachedEvent = decryptedEvents_.object(idx))
+ return cachedEvent;
+
+ MegolmSessionIndex index;
+ index.room_id = room_id_;
+ index.session_id = e.content.session_id;
+ index.sender_key = e.content.sender_key;
+
+ mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
+ dummy.origin_server_ts = e.origin_server_ts;
+ dummy.event_id = e.event_id;
+ dummy.sender = e.sender;
+ dummy.content.body =
+ tr("-- Encrypted Event (No keys found for decryption) --",
+ "Placeholder, when the message was not decrypted yet or can't be decrypted.")
+ .toStdString();
+
+ auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) {
+ auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event));
+ decryptedEvents_.insert(idx, event_ptr);
+ return event_ptr;
+ };
+
+ try {
+ if (!cache::client()->inboundMegolmSessionExists(index)) {
+ nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ e.sender);
+ // TODO: request megolm session_id & session_key from the sender.
+ return asCacheEntry(std::move(dummy));
+ }
+ } catch (const lmdb::error &e) {
+ nhlog::db()->critical("failed to check megolm session's existence: {}", e.what());
+ dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --",
+ "Placeholder, when the message can't be decrypted, because "
+ "the DB access failed when trying to lookup the session.")
+ .toStdString();
+ return asCacheEntry(std::move(dummy));
+ }
+
+ std::string msg_str;
+ try {
+ auto session = cache::client()->getInboundMegolmSession(index);
+ auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext);
+ msg_str = std::string((char *)res.data.data(), res.data.size());
+ } catch (const lmdb::error &e) {
+ nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ e.what());
+ dummy.content.body =
+ tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
+ "Placeholder, when the message can't be decrypted, because the DB access "
+ "failed.")
+ .toStdString();
+ return asCacheEntry(std::move(dummy));
+ } catch (const mtx::crypto::olm_exception &e) {
+ nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ e.what());
+ dummy.content.body =
+ tr("-- Decryption Error (%1) --",
+ "Placeholder, when the message can't be decrypted. In this case, the Olm "
+ "decrytion returned an error, which is passed as %1.")
+ .arg(e.what())
+ .toStdString();
+ return asCacheEntry(std::move(dummy));
+ }
+
+ // Add missing fields for the event.
+ json body = json::parse(msg_str);
+ body["event_id"] = e.event_id;
+ body["sender"] = e.sender;
+ body["origin_server_ts"] = e.origin_server_ts;
+ body["unsigned"] = e.unsigned_data;
+
+ // relations are unencrypted in content...
+ if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0)
+ body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
+
+ json event_array = json::array();
+ event_array.push_back(body);
+
+ std::vector<mtx::events::collections::TimelineEvents> temp_events;
+ mtx::responses::utils::parse_timeline_events(event_array, temp_events);
+
+ if (temp_events.size() == 1) {
+ auto encInfo = mtx::accessors::file(temp_events[0]);
+
+ if (encInfo)
+ emit newEncryptedImage(encInfo.value());
+
+ return asCacheEntry(std::move(temp_events[0]));
+ }
+
+ dummy.content.body =
+ tr("-- Encrypted Event (Unknown event type) --",
+ "Placeholder, when the message was decrypted, but we couldn't parse it, because "
+ "Nheko/mtxclient don't support that event type yet.")
+ .toStdString();
+ return asCacheEntry(std::move(dummy));
+}
+
+mtx::events::collections::TimelineEvents *
+EventStore::event(std::string_view id, std::string_view related_to, bool decrypt)
+{
+ if (this->thread() != QThread::currentThread())
+ nhlog::db()->warn("{} called from a different thread!", __func__);
+
+ if (id.empty())
+ return nullptr;
+
+ IdIndex index{room_id_, std::string(id.data(), id.size())};
+
+ auto event_ptr = events_by_id_.object(index);
+ if (!event_ptr) {
+ auto event = cache::client()->getEvent(room_id_, index.id);
+ if (!event) {
+ // fetch
+ (void)related_to;
+ return nullptr;
+ }
+ event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data));
+ events_by_id_.insert(index, event_ptr);
+ }
+
+ if (decrypt)
+ if (auto encrypted =
+ std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ event_ptr))
+ return decryptEvent(index, *encrypted);
+
+ return event_ptr;
+}
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
new file mode 100644
index 00000000..77d73536
--- /dev/null
+++ b/src/timeline/EventStore.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#include <limits>
+#include <string>
+
+#include <QCache>
+#include <QObject>
+#include <qhashfunctions.h>
+
+#include <mtx/events/collections.hpp>
+#include <mtx/responses/sync.hpp>
+
+class EventStore : public QObject
+{
+ Q_OBJECT
+
+public:
+ EventStore(std::string room_id, QObject *parent);
+
+ struct Index
+ {
+ std::string room;
+ int64_t idx;
+
+ friend uint qHash(const Index &i, uint seed = 0) noexcept
+ {
+ QtPrivate::QHashCombine hash;
+ seed = hash(seed, QByteArray::fromRawData(i.room.data(), i.room.size()));
+ seed = hash(seed, i.idx);
+ return seed;
+ }
+
+ friend bool operator==(const Index &a, const Index &b) noexcept
+ {
+ return a.idx == b.idx && a.room == b.room;
+ }
+ };
+ struct IdIndex
+ {
+ std::string room, id;
+
+ friend uint qHash(const IdIndex &i, uint seed = 0) noexcept
+ {
+ QtPrivate::QHashCombine hash;
+ seed = hash(seed, QByteArray::fromRawData(i.room.data(), i.room.size()));
+ seed = hash(seed, QByteArray::fromRawData(i.id.data(), i.id.size()));
+ return seed;
+ }
+
+ friend bool operator==(const IdIndex &a, const IdIndex &b) noexcept
+ {
+ return a.id == b.id && a.room == b.room;
+ }
+ };
+
+ void fetchMore();
+ void handleSync(const mtx::responses::Timeline &events);
+
+ // optionally returns the event or nullptr and fetches it, after which it emits a
+ // relatedFetched event
+ mtx::events::collections::TimelineEvents *event(std::string_view id,
+ std::string_view related_to,
+ bool decrypt = true);
+ // always returns a proper event as long as the idx is valid
+ mtx::events::collections::TimelineEvents *event(int idx, bool decrypt = true);
+
+ int size() const
+ {
+ return last != std::numeric_limits<int64_t>::max()
+ ? static_cast<int>(last - first) + 1
+ : 0;
+ }
+ int toExternalIdx(int64_t idx) const { return static_cast<int>(idx - first); }
+ int64_t toInternalIdx(int idx) const { return first + idx; }
+
+ std::optional<int> idToIndex(std::string_view id) const;
+ std::optional<std::string> indexToId(int idx) const;
+
+signals:
+ void beginInsertRows(int from, int to);
+ void endInsertRows();
+ void dataChanged(int from, int to);
+ void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
+
+private:
+ mtx::events::collections::TimelineEvents *decryptEvent(
+ const IdIndex &idx,
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
+
+ std::string room_id_;
+
+ int64_t first = std::numeric_limits<int64_t>::max(),
+ last = std::numeric_limits<int64_t>::max();
+
+ static QCache<IdIndex, mtx::events::collections::TimelineEvents> decryptedEvents_;
+ static QCache<Index, mtx::events::collections::TimelineEvents> events_;
+ static QCache<IdIndex, mtx::events::collections::TimelineEvents> events_by_id_;
+};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 504c6dcf..492d4e0a 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -141,6 +141,7 @@ toRoomEventTypeString(const mtx::events::collections::TimelineEvents &event)
TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent)
: QAbstractListModel(parent)
+ , events(room_id.toStdString(), this)
, room_id_(room_id)
, manager_(manager)
{
@@ -165,41 +166,41 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
this,
[this](QString txn_id, QString event_id) {
pending.removeOne(txn_id);
-
- auto ev = events.value(txn_id);
-
- if (auto reaction =
- std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&ev)) {
- QString reactedTo =
- QString::fromStdString(reaction->content.relates_to.event_id);
- auto &rModel = reactions[reactedTo];
- rModel.removeReaction(*reaction);
- auto rCopy = *reaction;
- rCopy.event_id = event_id.toStdString();
- rModel.addReaction(room_id_.toStdString(), rCopy);
- }
-
- int idx = idToIndex(txn_id);
- if (idx < 0) {
- // transaction already received via sync
- return;
- }
- eventOrder[idx] = event_id;
- ev = std::visit(
- [event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
- auto eventCopy = e;
- eventCopy.event_id = event_id.toStdString();
- return eventCopy;
- },
- ev);
-
- events.remove(txn_id);
- events.insert(event_id, ev);
-
- // mark our messages as read
- readEvent(event_id.toStdString());
-
- emit dataChanged(index(idx, 0), index(idx, 0));
+ (void)event_id;
+ // auto ev = events.value(txn_id);
+
+ // if (auto reaction =
+ // std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&ev)) {
+ // QString reactedTo =
+ // QString::fromStdString(reaction->content.relates_to.event_id);
+ // auto &rModel = reactions[reactedTo];
+ // rModel.removeReaction(*reaction);
+ // auto rCopy = *reaction;
+ // rCopy.event_id = event_id.toStdString();
+ // rModel.addReaction(room_id_.toStdString(), rCopy);
+ //}
+
+ // int idx = idToIndex(txn_id);
+ // if (idx < 0) {
+ // // transaction already received via sync
+ // return;
+ //}
+ // eventOrder[idx] = event_id;
+ // ev = std::visit(
+ // [event_id](const auto &e) -> mtx::events::collections::TimelineEvents {
+ // auto eventCopy = e;
+ // eventCopy.event_id = event_id.toStdString();
+ // return eventCopy;
+ // },
+ // ev);
+
+ // events.remove(txn_id);
+ // events.insert(event_id, ev);
+
+ //// mark our messages as read
+ // readEvent(event_id.toStdString());
+
+ // emit dataChanged(index(idx, 0), index(idx, 0));
if (pending.size() > 0)
emit nextPendingMessage();
@@ -224,16 +225,24 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
Qt::QueuedConnection);
connect(
+ &events,
+ &EventStore::dataChanged,
this,
- &TimelineModel::eventFetched,
- this,
- [this](QString requestingEvent, mtx::events::collections::TimelineEvents event) {
- events.insert(QString::fromStdString(mtx::accessors::event_id(event)), event);
- auto idx = idToIndex(requestingEvent);
- if (idx >= 0)
- emit dataChanged(index(idx, 0), index(idx, 0));
+ [this](int from, int to) {
+ emit dataChanged(index(events.size() - to, 0), index(events.size() - from, 0));
},
Qt::QueuedConnection);
+
+ connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
+ nhlog::ui()->info("begin insert from {} to {}",
+ events.size() - to + (to - from),
+ events.size() - from + (to - from));
+ beginInsertRows(QModelIndex(),
+ events.size() - to + (to - from),
+ events.size() - from + (to - from));
+ });
+ connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); });
+ connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage);
}
QHash<int, QByteArray>
@@ -274,28 +283,22 @@ int
TimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
- return (int)this->eventOrder.size();
+ return this->events.size() + static_cast<int>(pending.size());
}
QVariantMap
-TimelineModel::getDump(QString eventId) const
+TimelineModel::getDump(QString eventId, QString relatedTo) const
{
- if (events.contains(eventId))
- return data(eventId, Dump).toMap();
+ if (auto event = events.event(eventId.toStdString(), relatedTo.toStdString()))
+ return data(*event, Dump).toMap();
return {};
}
QVariant
-TimelineModel::data(const QString &id, int role) const
+TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int role) const
{
using namespace mtx::accessors;
- namespace acc = mtx::accessors;
- mtx::events::collections::TimelineEvents event = events.value(id);
-
- if (auto e =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
- event = decryptEvent(*e).event;
- }
+ namespace acc = mtx::accessors;
switch (role) {
case UserId:
@@ -381,8 +384,9 @@ TimelineModel::data(const QString &id, int role) const
return QVariant(prop > 0 ? prop : 1.);
}
case Id:
- return id;
+ return QVariant(QString::fromStdString(event_id(event)));
case State: {
+ auto id = QString::fromStdString(event_id(event));
auto containsOthers = [](const auto &vec) {
for (const auto &e : vec)
if (e.second != http::client()->user_id().to_string())
@@ -401,19 +405,22 @@ TimelineModel::data(const QString &id, int role) const
return qml_mtx_events::Received;
}
case IsEncrypted: {
- return std::holds_alternative<
- mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(events[id]);
+ // return std::holds_alternative<
+ // mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(events[id]);
+ return false;
}
case IsRoomEncrypted: {
return cache::isRoomEncrypted(room_id_.toStdString());
}
case ReplyTo:
return QVariant(QString::fromStdString(in_reply_to_event(event)));
- case Reactions:
+ case Reactions: {
+ auto id = QString::fromStdString(event_id(event));
if (reactions.count(id))
return QVariant::fromValue((QObject *)&reactions.at(id));
else
return {};
+ }
case RoomId:
return QVariant(room_id_);
case RoomName:
@@ -425,30 +432,31 @@ TimelineModel::data(const QString &id, int role) const
auto names = roleNames();
// m.insert(names[Section], data(id, static_cast<int>(Section)));
- m.insert(names[Type], data(id, static_cast<int>(Type)));
- m.insert(names[TypeString], data(id, static_cast<int>(TypeString)));
- m.insert(names[IsOnlyEmoji], data(id, static_cast<int>(IsOnlyEmoji)));
- m.insert(names[Body], data(id, static_cast<int>(Body)));
- m.insert(names[FormattedBody], data(id, static_cast<int>(FormattedBody)));
- m.insert(names[UserId], data(id, static_cast<int>(UserId)));
- m.insert(names[UserName], data(id, static_cast<int>(UserName)));
- m.insert(names[Timestamp], data(id, static_cast<int>(Timestamp)));
- m.insert(names[Url], data(id, static_cast<int>(Url)));
- m.insert(names[ThumbnailUrl], data(id, static_cast<int>(ThumbnailUrl)));
- m.insert(names[Blurhash], data(id, static_cast<int>(Blurhash)));
- m.insert(names[Filename], data(id, static_cast<int>(Filename)));
- m.insert(names[Filesize], data(id, static_cast<int>(Filesize)));
- m.insert(names[MimeType], data(id, static_cast<int>(MimeType)));
- m.insert(names[Height], data(id, static_cast<int>(Height)));
- m.insert(names[Width], data(id, static_cast<int>(Width)));
- m.insert(names[ProportionalHeight], data(id, static_cast<int>(ProportionalHeight)));
- m.insert(names[Id], data(id, static_cast<int>(Id)));
- m.insert(names[State], data(id, static_cast<int>(State)));
- m.insert(names[IsEncrypted], data(id, static_cast<int>(IsEncrypted)));
- m.insert(names[IsRoomEncrypted], data(id, static_cast<int>(IsRoomEncrypted)));
- m.insert(names[ReplyTo], data(id, static_cast<int>(ReplyTo)));
- m.insert(names[RoomName], data(id, static_cast<int>(RoomName)));
- m.insert(names[RoomTopic], data(id, static_cast<int>(RoomTopic)));
+ m.insert(names[Type], data(event, static_cast<int>(Type)));
+ m.insert(names[TypeString], data(event, static_cast<int>(TypeString)));
+ m.insert(names[IsOnlyEmoji], data(event, static_cast<int>(IsOnlyEmoji)));
+ m.insert(names[Body], data(event, static_cast<int>(Body)));
+ m.insert(names[FormattedBody], data(event, static_cast<int>(FormattedBody)));
+ m.insert(names[UserId], data(event, static_cast<int>(UserId)));
+ m.insert(names[UserName], data(event, static_cast<int>(UserName)));
+ m.insert(names[Timestamp], data(event, static_cast<int>(Timestamp)));
+ m.insert(names[Url], data(event, static_cast<int>(Url)));
+ m.insert(names[ThumbnailUrl], data(event, static_cast<int>(ThumbnailUrl)));
+ m.insert(names[Blurhash], data(event, static_cast<int>(Blurhash)));
+ m.insert(names[Filename], data(event, static_cast<int>(Filename)));
+ m.insert(names[Filesize], data(event, static_cast<int>(Filesize)));
+ m.insert(names[MimeType], data(event, static_cast<int>(MimeType)));
+ m.insert(names[Height], data(event, static_cast<int>(Height)));
+ m.insert(names[Width], data(event, static_cast<int>(Width)));
+ m.insert(names[ProportionalHeight],
+ data(event, static_cast<int>(ProportionalHeight)));
+ m.insert(names[Id], data(event, static_cast<int>(Id)));
+ m.insert(names[State], data(event, static_cast<int>(State)));
+ m.insert(names[IsEncrypted], data(event, static_cast<int>(IsEncrypted)));
+ m.insert(names[IsRoomEncrypted], data(event, static_cast<int>(IsRoomEncrypted)));
+ m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo)));
+ m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
+ m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
return QVariant(m);
}
@@ -462,29 +470,33 @@ TimelineModel::data(const QModelIndex &index, int role) const
{
using namespace mtx::accessors;
namespace acc = mtx::accessors;
- if (index.row() < 0 && index.row() >= (int)eventOrder.size())
+ if (index.row() < 0 && index.row() >= rowCount())
return QVariant();
- QString id = eventOrder[index.row()];
+ auto event = events.event(rowCount() - index.row() - 1);
- mtx::events::collections::TimelineEvents event = events.value(id);
+ if (!event)
+ return "";
if (role == Section) {
- QDateTime date = origin_server_ts(event);
+ QDateTime date = origin_server_ts(*event);
date.setTime(QTime());
- std::string userId = acc::sender(event);
+ std::string userId = acc::sender(*event);
+
+ for (int r = rowCount() - index.row(); r < events.size(); r++) {
+ auto tempEv = events.event(r);
+ if (!tempEv)
+ break;
- for (size_t r = index.row() + 1; r < eventOrder.size(); r++) {
- auto tempEv = events.value(eventOrder[r]);
- QDateTime prevDate = origin_server_ts(tempEv);
+ QDateTime prevDate = origin_server_ts(*tempEv);
prevDate.setTime(QTime());
if (prevDate != date)
return QString("%2 %1")
.arg(date.toMSecsSinceEpoch())
.arg(QString::fromStdString(userId));
- std::string prevUserId = acc::sender(tempEv);
+ std::string prevUserId = acc::sender(*tempEv);
if (userId != prevUserId)
break;
}
@@ -492,16 +504,16 @@ TimelineModel::data(const QModelIndex &index, int role) const
return QString("%1").arg(QString::fromStdString(userId));
}
- return data(id, role);
+ return data(*event, role);
}
bool
TimelineModel::canFetchMore(const QModelIndex &) const
{
- if (eventOrder.empty())
+ if (!events.size())
return true;
if (!std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(
- events[eventOrder.back()]))
+ *events.event(0)))
return true;
else
@@ -562,13 +574,9 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
if (timeline.events.empty())
return;
- std::vector<QString> ids = internalAddEvents(timeline.events);
+ internalAddEvents(timeline.events);
- if (!ids.empty()) {
- beginInsertRows(QModelIndex(), 0, static_cast<int>(ids.size() - 1));
- this->eventOrder.insert(this->eventOrder.begin(), ids.rbegin(), ids.rend());
- endInsertRows();
- }
+ events.handleSync(timeline);
if (!timeline.events.empty())
updateLastMessage();
@@ -613,21 +621,17 @@ isYourJoin(const mtx::events::Event<T> &)
void
TimelineModel::updateLastMessage()
{
- for (auto it = eventOrder.begin(); it != eventOrder.end(); ++it) {
- auto event = events.value(*it);
- if (auto e = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
- &event)) {
- if (decryptDescription) {
- event = decryptEvent(*e).event;
- }
- }
+ for (auto it = events.size() - 1; it >= 0; --it) {
+ auto event = events.event(it, decryptDescription);
+ if (!event)
+ continue;
- if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, event)) {
- auto time = mtx::accessors::origin_server_ts(event);
+ if (std::visit([](const auto &e) -> bool { return isYourJoin(e); }, *event)) {
+ auto time = mtx::accessors::origin_server_ts(*event);
uint64_t ts = time.toMSecsSinceEpoch();
emit manager_->updateRoomsLastMessage(
room_id_,
- DescInfo{QString::fromStdString(mtx::accessors::event_id(event)),
+ DescInfo{QString::fromStdString(mtx::accessors::event_id(*event)),
QString::fromStdString(http::client()->user_id().to_string()),
tr("You joined this room."),
utils::descriptiveTime(time),
@@ -635,54 +639,34 @@ TimelineModel::updateLastMessage()
time});
return;
}
- if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, event))
+ if (!std::visit([](const auto &e) -> bool { return isMessage(e); }, *event))
continue;
auto description = utils::getMessageDescription(
- event, QString::fromStdString(http::client()->user_id().to_string()), room_id_);
+ *event, QString::fromStdString(http::client()->user_id().to_string()), room_id_);
emit manager_->updateRoomsLastMessage(room_id_, description);
return;
}
}
-std::vector<QString>
+void
TimelineModel::internalAddEvents(
const std::vector<mtx::events::collections::TimelineEvents> &timeline)
{
- std::vector<QString> ids;
for (auto e : timeline) {
QString id = QString::fromStdString(mtx::accessors::event_id(e));
- if (this->events.contains(id)) {
- this->events.insert(id, e);
- int idx = idToIndex(id);
- emit dataChanged(index(idx, 0), index(idx, 0));
- continue;
- }
-
- QString txid = QString::fromStdString(mtx::accessors::transaction_id(e));
- if (this->pending.removeOne(txid)) {
- this->events.insert(id, e);
- this->events.remove(txid);
- int idx = idToIndex(txid);
- if (idx < 0) {
- nhlog::ui()->warn("Received index out of range");
- continue;
- }
- eventOrder[idx] = id;
- emit dataChanged(index(idx, 0), index(idx, 0));
- continue;
- }
-
if (auto redaction =
std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) {
QString redacts = QString::fromStdString(redaction->redacts);
- auto redacted = std::find(eventOrder.begin(), eventOrder.end(), redacts);
- auto event = events.value(redacts);
+ auto event = events.event(redaction->redacts, redaction->event_id);
+ if (!event)
+ continue;
+
if (auto reaction =
std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
- &event)) {
+ event)) {
QString reactedTo =
QString::fromStdString(reaction->content.relates_to.event_id);
reactions[reactedTo].removeReaction(*reaction);
@@ -691,26 +675,6 @@ TimelineModel::internalAddEvents(
emit dataChanged(index(idx, 0), index(idx, 0));
}
- if (redacted != eventOrder.end()) {
- auto redactedEvent = std::visit(
- [](const auto &ev)
- -> mtx::events::RoomEvent<mtx::events::msg::Redacted> {
- mtx::events::RoomEvent<mtx::events::msg::Redacted>
- replacement = {};
- replacement.event_id = ev.event_id;
- replacement.room_id = ev.room_id;
- replacement.sender = ev.sender;
- replacement.origin_server_ts = ev.origin_server_ts;
- replacement.type = ev.type;
- return replacement;
- },
- e);
- events.insert(redacts, redactedEvent);
-
- int row = (int)std::distance(eventOrder.begin(), redacted);
- emit dataChanged(index(row, 0), index(row, 0));
- }
-
continue; // don't insert redaction into timeline
}
@@ -718,14 +682,13 @@ TimelineModel::internalAddEvents(
std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&e)) {
QString reactedTo =
QString::fromStdString(reaction->content.relates_to.event_id);
- events.insert(id, e);
- // remove local echo
- if (!txid.isEmpty()) {
- auto rCopy = *reaction;
- rCopy.event_id = txid.toStdString();
- reactions[reactedTo].removeReaction(rCopy);
- }
+ // // remove local echo
+ // if (!txid.isEmpty()) {
+ // auto rCopy = *reaction;
+ // rCopy.event_id = txid.toStdString();
+ // reactions[reactedTo].removeReaction(rCopy);
+ // }
reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction);
int idx = idToIndex(reactedTo);
@@ -734,40 +697,27 @@ TimelineModel::internalAddEvents(
continue; // don't insert reaction into timeline
}
- if (auto event =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&e)) {
- auto e_ = decryptEvent(*event).event;
- auto encInfo = mtx::accessors::file(e_);
-
- if (encInfo)
- emit newEncryptedImage(encInfo.value());
- }
-
- this->events.insert(id, e);
- ids.push_back(id);
-
- auto replyTo = mtx::accessors::in_reply_to_event(e);
- auto qReplyTo = QString::fromStdString(replyTo);
- if (!replyTo.empty() && !events.contains(qReplyTo)) {
- http::client()->get_event(
- this->room_id_.toStdString(),
- replyTo,
- [this, id, replyTo](
- const mtx::events::collections::TimelineEvents &timeline,
- mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->error(
- "Failed to retrieve event with id {}, which was "
- "requested to show the replyTo for event {}",
- replyTo,
- id.toStdString());
- return;
- }
- emit eventFetched(id, timeline);
- });
- }
+ // auto replyTo = mtx::accessors::in_reply_to_event(e);
+ // auto qReplyTo = QString::fromStdString(replyTo);
+ // if (!replyTo.empty() && !events.contains(qReplyTo)) {
+ // http::client()->get_event(
+ // this->room_id_.toStdString(),
+ // replyTo,
+ // [this, id, replyTo](
+ // const mtx::events::collections::TimelineEvents &timeline,
+ // mtx::http::RequestErr err) {
+ // if (err) {
+ // nhlog::net()->error(
+ // "Failed to retrieve event with id {}, which was "
+ // "requested to show the replyTo for event {}",
+ // replyTo,
+ // id.toStdString());
+ // return;
+ // }
+ // emit eventFetched(id, timeline);
+ // });
+ //}
}
- return ids;
}
void
@@ -798,22 +748,23 @@ TimelineModel::readEvent(const std::string &id)
void
TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs)
{
- std::vector<QString> ids = internalAddEvents(msgs.chunk);
-
- if (!ids.empty()) {
- beginInsertRows(QModelIndex(),
- static_cast<int>(this->eventOrder.size()),
- static_cast<int>(this->eventOrder.size() + ids.size() - 1));
- this->eventOrder.insert(this->eventOrder.end(), ids.begin(), ids.end());
- endInsertRows();
- }
-
- prev_batch_token_ = QString::fromStdString(msgs.end);
-
- if (ids.empty() && !msgs.chunk.empty()) {
- // no visible events fetched, prevent loading from stopping
- fetchMore(QModelIndex());
- }
+ (void)msgs;
+ // std::vector<QString> ids = internalAddEvents(msgs.chunk);
+
+ // if (!ids.empty()) {
+ // beginInsertRows(QModelIndex(),
+ // static_cast<int>(this->eventOrder.size()),
+ // static_cast<int>(this->eventOrder.size() + ids.size() - 1));
+ // this->eventOrder.insert(this->eventOrder.end(), ids.begin(), ids.end());
+ // endInsertRows();
+ //}
+
+ // prev_batch_token_ = QString::fromStdString(msgs.end);
+
+ // if (ids.empty() && !msgs.chunk.empty()) {
+ // // no visible events fetched, prevent loading from stopping
+ // fetchMore(QModelIndex());
+ //}
}
QString
@@ -852,7 +803,10 @@ TimelineModel::escapeEmoji(QString str) const
void
TimelineModel::viewRawMessage(QString id) const
{
- std::string ev = mtx::accessors::serialize_event(events.value(id)).dump(4);
+ auto e = events.event(id.toStdString(), "", false);
+ if (!e)
+ return;
+ std::string ev = mtx::accessors::serialize_event(*e).dump(4);
auto dialog = new dialogs::RawMessage(QString::fromStdString(ev));
Q_UNUSED(dialog);
}
@@ -860,13 +814,11 @@ TimelineModel::viewRawMessage(QString id) const
void
TimelineModel::viewDecryptedRawMessage(QString id) const
{
- auto event = events.value(id);
- if (auto e =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
- event = decryptEvent(*e).event;
- }
+ auto e = events.event(id.toStdString(), "");
+ if (!e)
+ return;
- std::string ev = mtx::accessors::serialize_event(event).dump(4);
+ std::string ev = mtx::accessors::serialize_event(*e).dump(4);
auto dialog = new dialogs::RawMessage(QString::fromStdString(ev));
Q_UNUSED(dialog);
}
@@ -877,114 +829,6 @@ TimelineModel::openUserProfile(QString userid) const
MainWindow::instance()->openUserProfile(userid, room_id_);
}
-DecryptionResult
-TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const
-{
- static QCache<std::string, DecryptionResult> decryptedEvents{300};
-
- if (auto cachedEvent = decryptedEvents.object(e.event_id))
- return *cachedEvent;
-
- MegolmSessionIndex index;
- index.room_id = room_id_.toStdString();
- index.session_id = e.content.session_id;
- index.sender_key = e.content.sender_key;
-
- mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
- dummy.origin_server_ts = e.origin_server_ts;
- dummy.event_id = e.event_id;
- dummy.sender = e.sender;
- dummy.content.body =
- tr("-- Encrypted Event (No keys found for decryption) --",
- "Placeholder, when the message was not decrypted yet or can't be decrypted.")
- .toStdString();
-
- try {
- if (!cache::inboundMegolmSessionExists(index)) {
- nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
- index.room_id,
- index.session_id,
- e.sender);
- // TODO: request megolm session_id & session_key from the sender.
- decryptedEvents.insert(
- dummy.event_id, new DecryptionResult{dummy, false}, 1);
- return {dummy, false};
- }
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to check megolm session's existence: {}", e.what());
- dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --",
- "Placeholder, when the message can't be decrypted, because "
- "the DB access failed when trying to lookup the session.")
- .toStdString();
- decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
- return {dummy, false};
- }
-
- std::string msg_str;
- try {
- auto session = cache::getInboundMegolmSession(index);
- auto res = olm::client()->decrypt_group_message(session, e.content.ciphertext);
- msg_str = std::string((char *)res.data.data(), res.data.size());
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})",
- index.room_id,
- index.session_id,
- index.sender_key,
- e.what());
- dummy.content.body =
- tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
- "Placeholder, when the message can't be decrypted, because the DB access "
- "failed.")
- .toStdString();
- decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
- return {dummy, false};
- } catch (const mtx::crypto::olm_exception &e) {
- nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}",
- index.room_id,
- index.session_id,
- index.sender_key,
- e.what());
- dummy.content.body =
- tr("-- Decryption Error (%1) --",
- "Placeholder, when the message can't be decrypted. In this case, the Olm "
- "decrytion returned an error, which is passed ad %1.")
- .arg(e.what())
- .toStdString();
- decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
- return {dummy, false};
- }
-
- // Add missing fields for the event.
- json body = json::parse(msg_str);
- body["event_id"] = e.event_id;
- body["sender"] = e.sender;
- body["origin_server_ts"] = e.origin_server_ts;
- body["unsigned"] = e.unsigned_data;
-
- // relations are unencrypted in content...
- if (json old_ev = e; old_ev["content"].count("m.relates_to") != 0)
- body["content"]["m.relates_to"] = old_ev["content"]["m.relates_to"];
-
- json event_array = json::array();
- event_array.push_back(body);
-
- std::vector<mtx::events::collections::TimelineEvents> temp_events;
- mtx::responses::utils::parse_timeline_events(event_array, temp_events);
-
- if (temp_events.size() == 1) {
- decryptedEvents.insert(e.event_id, new DecryptionResult{temp_events[0], true}, 1);
- return {temp_events[0], true};
- }
-
- dummy.content.body =
- tr("-- Encrypted Event (Unknown event type) --",
- "Placeholder, when the message was decrypted, but we couldn't parse it, because "
- "Nheko/mtxclient don't support that event type yet.")
- .toStdString();
- decryptedEvents.insert(dummy.event_id, new DecryptionResult{dummy, false}, 1);
- return {dummy, false};
-}
-
void
TimelineModel::replyAction(QString id)
{
@@ -995,23 +839,18 @@ TimelineModel::replyAction(QString id)
RelatedInfo
TimelineModel::relatedInfo(QString id)
{
- if (!events.contains(id))
+ auto event = events.event(id.toStdString(), "");
+ if (!event)
return {};
- auto event = events.value(id);
- if (auto e =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
- event = decryptEvent(*e).event;
- }
-
RelatedInfo related = {};
- related.quoted_user = QString::fromStdString(mtx::accessors::sender(event));
- related.related_event = mtx::accessors::event_id(event);
- related.type = mtx::accessors::msg_type(event);
+ related.quoted_user = QString::fromStdString(mtx::accessors::sender(*event));
+ related.related_event = mtx::accessors::event_id(*event);
+ related.type = mtx::accessors::msg_type(*event);
// get body, strip reply fallback, then transform the event to text, if it is a media event
// etc
- related.quoted_body = QString::fromStdString(mtx::accessors::body(event));
+ related.quoted_body = QString::fromStdString(mtx::accessors::body(*event));
QRegularExpression plainQuote("^>.*?$\n?", QRegularExpression::MultilineOption);
while (related.quoted_body.startsWith(">"))
related.quoted_body.remove(plainQuote);
@@ -1020,7 +859,7 @@ TimelineModel::relatedInfo(QString id)
related.quoted_body = utils::getQuoteBody(related);
// get quoted body and strip reply fallback
- related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(event);
+ related.quoted_formatted_body = mtx::accessors::formattedBodyWithFallback(*event);
related.quoted_formatted_body.remove(QRegularExpression(
"<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption));
related.room = room_id_;
@@ -1058,18 +897,19 @@ TimelineModel::idToIndex(QString id) const
{
if (id.isEmpty())
return -1;
- for (int i = 0; i < (int)eventOrder.size(); i++)
- if (id == eventOrder[i])
- return i;
- return -1;
+
+ auto idx = events.idToIndex(id.toStdString());
+ if (idx)
+ return events.size() - *idx;
+ else
+ return -1;
}
QString
TimelineModel::indexToId(int index) const
{
- if (index < 0 || index >= (int)eventOrder.size())
- return "";
- return eventOrder[index];
+ auto id = events.indexToId(events.size() - index);
+ return id ? QString::fromStdString(*id) : "";
}
// Note: this will only be called for our messages
@@ -1477,58 +1317,56 @@ struct SendMessageVisitor
void
TimelineModel::processOnePendingMessage()
{
- if (pending.isEmpty())
- return;
+ // if (pending.isEmpty())
+ // return;
- QString txn_id_qstr = pending.first();
+ // QString txn_id_qstr = pending.first();
- auto event = events.value(txn_id_qstr);
- std::visit(SendMessageVisitor{txn_id_qstr, this}, event);
+ // auto event = events.value(txn_id_qstr);
+ // std::visit(SendMessageVisitor{txn_id_qstr, this}, event);
}
void
TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
{
- std::visit(
- [](auto &msg) {
- msg.type = mtx::events::EventType::RoomMessage;
- msg.event_id = http::client()->generate_txn_id();
- msg.sender = http::client()->user_id().to_string();
- msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- },
- event);
-
- internalAddEvents({event});
-
- QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event));
- pending.push_back(txn_id_qstr);
- if (!std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&event)) {
- beginInsertRows(QModelIndex(), 0, 0);
- this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr);
- endInsertRows();
- }
- updateLastMessage();
-
- emit nextPendingMessage();
+ (void)event;
+ // std::visit(
+ // [](auto &msg) {
+ // msg.type = mtx::events::EventType::RoomMessage;
+ // msg.event_id = http::client()->generate_txn_id();
+ // msg.sender = http::client()->user_id().to_string();
+ // msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
+ // },
+ // event);
+
+ // internalAddEvents({event});
+
+ // QString txn_id_qstr = QString::fromStdString(mtx::accessors::event_id(event));
+ // pending.push_back(txn_id_qstr);
+ // if (!std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(&event)) {
+ // beginInsertRows(QModelIndex(), 0, 0);
+ // this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr);
+ // endInsertRows();
+ //}
+ // updateLastMessage();
+
+ // emit nextPendingMessage();
}
bool
TimelineModel::saveMedia(QString eventId) const
{
- mtx::events::collections::TimelineEvents event = events.value(eventId);
-
- if (auto e =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
- event = decryptEvent(*e).event;
- }
+ mtx::events::collections::TimelineEvents *event = events.event(eventId.toStdString(), "");
+ if (!event)
+ return false;
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(event));
- QString mimeType = QString::fromStdString(mtx::accessors::mimetype(event));
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
- auto encryptionInfo = mtx::accessors::file(event);
+ auto encryptionInfo = mtx::accessors::file(*event);
- qml_mtx_events::EventType eventType = toRoomEventType(event);
+ qml_mtx_events::EventType eventType = toRoomEventType(*event);
QString dialogTitle;
if (eventType == qml_mtx_events::EventType::ImageMessage) {
@@ -1593,18 +1431,15 @@ TimelineModel::saveMedia(QString eventId) const
void
TimelineModel::cacheMedia(QString eventId)
{
- mtx::events::collections::TimelineEvents event = events.value(eventId);
-
- if (auto e =
- std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
- event = decryptEvent(*e).event;
- }
+ mtx::events::collections::TimelineEvents *event = events.event(eventId.toStdString(), "");
+ if (!event)
+ return;
- QString mxcUrl = QString::fromStdString(mtx::accessors::url(event));
- QString originalFilename = QString::fromStdString(mtx::accessors::filename(event));
- QString mimeType = QString::fromStdString(mtx::accessors::mimetype(event));
+ QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
+ QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event));
+ QString mimeType = QString::fromStdString(mtx::accessors::mimetype(*event));
- auto encryptionInfo = mtx::accessors::file(event);
+ auto encryptionInfo = mtx::accessors::file(*event);
// If the message is a link to a non mxcUrl, don't download it
if (!mxcUrl.startsWith("mxc://")) {
@@ -1725,11 +1560,11 @@ TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg)
QString
TimelineModel::formatJoinRuleEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.event(id.toStdString(), "");
+ if (!e)
return "";
- auto event =
- std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(&events[id]);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::JoinRules>>(e);
if (!event)
return "";
@@ -1750,11 +1585,11 @@ TimelineModel::formatJoinRuleEvent(QString id)
QString
TimelineModel::formatGuestAccessEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.event(id.toStdString(), "");
+ if (!e)
return "";
- auto event =
- std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(&events[id]);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::GuestAccess>>(e);
if (!event)
return "";
@@ -1774,11 +1609,11 @@ TimelineModel::formatGuestAccessEvent(QString id)
QString
TimelineModel::formatHistoryVisibilityEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.event(id.toStdString(), "");
+ if (!e)
return "";
- auto event =
- std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(&events[id]);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::HistoryVisibility>>(e);
if (!event)
return "";
@@ -1808,11 +1643,11 @@ TimelineModel::formatHistoryVisibilityEvent(QString id)
QString
TimelineModel::formatPowerLevelEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.event(id.toStdString(), "");
+ if (!e)
return "";
- auto event =
- std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(&events[id]);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::PowerLevels>>(e);
if (!event)
return "";
@@ -1826,28 +1661,30 @@ TimelineModel::formatPowerLevelEvent(QString id)
QString
TimelineModel::formatMemberEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.event(id.toStdString(), "");
+ if (!e)
return "";
- auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(&events[id]);
+ auto event = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(e);
if (!event)
return "";
mtx::events::StateEvent<mtx::events::state::Member> *prevEvent = nullptr;
- QString prevEventId = QString::fromStdString(event->unsigned_data.replaces_state);
- if (!prevEventId.isEmpty()) {
- if (!events.contains(prevEventId)) {
+ if (!event->unsigned_data.replaces_state.empty()) {
+ auto tempPrevEvent =
+ events.event(event->unsigned_data.replaces_state, event->event_id);
+ if (!tempPrevEvent) {
http::client()->get_event(
this->room_id_.toStdString(),
event->unsigned_data.replaces_state,
- [this, id, prevEventId](
+ [this, id, prevEventId = event->unsigned_data.replaces_state](
const mtx::events::collections::TimelineEvents &timeline,
mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error(
"Failed to retrieve event with id {}, which was "
"requested to show the membership for event {}",
- prevEventId.toStdString(),
+ prevEventId,
id.toStdString());
return;
}
@@ -1856,7 +1693,7 @@ TimelineModel::formatMemberEvent(QString id)
} else {
prevEvent =
std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(
- &events[prevEventId]);
+ tempPrevEvent);
}
}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index a3b92f83..f322b482 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -9,6 +9,7 @@
#include <mtxclient/http/errors.hpp>
#include "CacheCryptoStructs.h"
+#include "EventStore.h"
#include "ReactionsModel.h"
namespace mtx::http {
@@ -170,7 +171,7 @@ public:
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- QVariant data(const QString &id, int role) const;
+ QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
bool canFetchMore(const QModelIndex &) const override;
void fetchMore(const QModelIndex &) override;
@@ -207,7 +208,7 @@ public slots:
void setCurrentIndex(int index);
int currentIndex() const { return idToIndex(currentId); }
void markEventsAsRead(const std::vector<QString> &event_ids);
- QVariantMap getDump(QString eventId) const;
+ QVariantMap getDump(QString eventId, QString relatedTo) const;
void updateTypingUsers(const std::vector<QString> &users)
{
if (this->typingUsers_ != users) {
@@ -257,9 +258,7 @@ signals:
void paginationInProgressChanged(const bool);
private:
- DecryptionResult decryptEvent(
- const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const;
- std::vector<QString> internalAddEvents(
+ void internalAddEvents(
const std::vector<mtx::events::collections::TimelineEvents> &timeline);
void sendEncryptedMessage(const std::string &txn_id, nlohmann::json content);
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
@@ -272,12 +271,12 @@ private:
void setPaginationInProgress(const bool paginationInProgress);
- QHash<QString, mtx::events::collections::TimelineEvents> events;
QSet<QString> read;
QList<QString> pending;
- std::vector<QString> eventOrder;
std::map<QString, ReactionsModel> reactions;
+ mutable EventStore events;
+
QString room_id_;
QString prev_batch_token_;
|