diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 67e07d7b..b6c2d4bb 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -136,6 +136,11 @@ struct RoomEventType
{
return qml_mtx_events::EventType::CallHangUp;
}
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallCandidates> &)
+ {
+ return qml_mtx_events::EventType::CallCandidates;
+ }
// ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
// ::EventType::LocationMessage; }
};
@@ -156,70 +161,10 @@ 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)
{
- connect(this,
- &TimelineModel::oldMessagesRetrieved,
- this,
- &TimelineModel::addBackwardsEvents,
- Qt::QueuedConnection);
- connect(
- this,
- &TimelineModel::messageFailed,
- this,
- [this](QString txn_id) {
- nhlog::ui()->error("Failed to send {}, retrying", txn_id.toStdString());
-
- QTimer::singleShot(5000, this, [this]() { emit nextPendingMessage(); });
- },
- Qt::QueuedConnection);
- connect(
- this,
- &TimelineModel::messageSent,
- 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));
-
- if (pending.size() > 0)
- emit nextPendingMessage();
- },
- Qt::QueuedConnection);
connect(
this,
&TimelineModel::redactionFailed,
@@ -228,27 +173,44 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
Qt::QueuedConnection);
connect(this,
- &TimelineModel::nextPendingMessage,
- this,
- &TimelineModel::processOnePendingMessage,
- Qt::QueuedConnection);
- connect(this,
&TimelineModel::newMessageToSend,
this,
&TimelineModel::addPendingMessage,
Qt::QueuedConnection);
+ connect(this, &TimelineModel::addPendingMessageToStore, &events, &EventStore::addPending);
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) {
+ nhlog::ui()->debug(
+ "data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
+ emit dataChanged(index(events.size() - to - 1, 0),
+ index(events.size() - from - 1, 0));
},
Qt::QueuedConnection);
+
+ connect(&events, &EventStore::beginInsertRows, this, [this](int from, int to) {
+ int first = events.size() - to;
+ int last = events.size() - from;
+ if (from >= events.size()) {
+ int batch_size = to - from;
+ first += batch_size;
+ last += batch_size;
+ } else {
+ first -= 1;
+ last -= 1;
+ }
+ nhlog::ui()->debug("begin insert from {} to {}", first, last);
+ beginInsertRows(QModelIndex(), first, last);
+ });
+ connect(&events, &EventStore::endInsertRows, this, [this]() { endInsertRows(); });
+ connect(&events, &EventStore::beginResetModel, this, [this]() { beginResetModel(); });
+ connect(&events, &EventStore::endResetModel, this, [this]() { endResetModel(); });
+ connect(&events, &EventStore::newEncryptedImage, this, &TimelineModel::newEncryptedImage);
+ connect(
+ &events, &EventStore::fetchedMore, this, [this]() { setPaginationInProgress(false); });
}
QHash<int, QByteArray>
@@ -290,28 +252,22 @@ int
TimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
- return (int)this->eventOrder.size();
+ return this->events.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.get(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:
@@ -397,8 +353,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())
@@ -409,7 +366,7 @@ TimelineModel::data(const QString &id, int role) const
// only show read receipts for messages not from us
if (acc::sender(event) != http::client()->user_id().to_string())
return qml_mtx_events::Empty;
- else if (pending.contains(id))
+ else if (!id.isEmpty() && id[0] == "m")
return qml_mtx_events::Sent;
else if (read.contains(id) || containsOthers(cache::readReceipts(id, room_id_)))
return qml_mtx_events::Read;
@@ -417,19 +374,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]);
+ auto id = event_id(event);
+ auto encrypted_event = events.get(id, id, false);
+ return encrypted_event &&
+ std::holds_alternative<
+ mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
+ *encrypted_event);
}
case IsRoomEncrypted: {
return cache::isRoomEncrypted(room_id_.toStdString());
}
case ReplyTo:
return QVariant(QString::fromStdString(in_reply_to_event(event)));
- case Reactions:
- if (reactions.count(id))
- return QVariant::fromValue((QObject *)&reactions.at(id));
- else
- return {};
+ case Reactions: {
+ auto id = event_id(event);
+ return QVariant::fromValue(events.reactions(id));
+ }
case RoomId:
return QVariant(room_id_);
case RoomName:
@@ -443,31 +403,32 @@ 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[CallType], data(id, static_cast<int>(CallType)));
+ 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)));
+ m.insert(names[CallType], data(event, static_cast<int>(CallType)));
return QVariant(m);
}
@@ -481,29 +442,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.get(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.get(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;
}
@@ -511,16 +476,17 @@ 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()]))
+ if (auto first = events.get(0);
+ first &&
+ !std::holds_alternative<mtx::events::StateEvent<mtx::events::state::Create>>(*first))
return true;
else
@@ -547,50 +513,44 @@ TimelineModel::fetchMore(const QModelIndex &)
}
setPaginationInProgress(true);
- mtx::http::MessagesOpts opts;
- opts.room_id = room_id_.toStdString();
- opts.from = prev_batch_token_.toStdString();
- nhlog::ui()->debug("Paginating room {}", opts.room_id);
-
- http::client()->messages(
- opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->error("failed to call /messages ({}): {} - {} - {}",
- opts.room_id,
- mtx::errors::to_string(err->matrix_error.errcode),
- err->matrix_error.error,
- err->parse_error);
- setPaginationInProgress(false);
- return;
- }
-
- emit oldMessagesRetrieved(std::move(res));
- setPaginationInProgress(false);
- });
+ events.fetchMore();
}
void
TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
{
- if (isInitialSync) {
- prev_batch_token_ = QString::fromStdString(timeline.prev_batch);
- isInitialSync = false;
- }
-
if (timeline.events.empty())
return;
- std::vector<QString> ids = internalAddEvents(timeline.events, true);
+ events.handleSync(timeline);
- if (!ids.empty()) {
- beginInsertRows(QModelIndex(), 0, static_cast<int>(ids.size() - 1));
- this->eventOrder.insert(this->eventOrder.begin(), ids.rbegin(), ids.rend());
- endInsertRows();
- }
+ using namespace mtx::events;
+ for (auto e : timeline.events) {
+ if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) {
+ MegolmSessionIndex index;
+ index.room_id = room_id_.toStdString();
+ index.session_id = encryptedEvent->content.session_id;
+ index.sender_key = encryptedEvent->content.sender_key;
+
+ auto result = olm::decryptEvent(index, *encryptedEvent);
+ if (result.event)
+ e = result.event.value();
+ }
- if (!timeline.events.empty())
- updateLastMessage();
+ if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallInvite>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallHangUp>>(e))
+ std::visit(
+ [this](auto &event) {
+ event.room_id = room_id_.toStdString();
+ if (event.sender != http::client()->user_id().to_string())
+ emit newCallEvent(event);
+ },
+ e);
+ }
+ updateLastMessage();
}
template<typename T>
@@ -649,21 +609,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.get(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),
@@ -671,191 +627,16 @@ 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>
-TimelineModel::internalAddEvents(
- const std::vector<mtx::events::collections::TimelineEvents> &timeline,
- bool emitCallEvents)
-{
- 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);
- if (auto reaction =
- std::get_if<mtx::events::RoomEvent<mtx::events::msg::Reaction>>(
- &event)) {
- QString reactedTo =
- QString::fromStdString(reaction->content.relates_to.event_id);
- reactions[reactedTo].removeReaction(*reaction);
- int idx = idToIndex(reactedTo);
- if (idx >= 0)
- 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
- }
-
- if (auto reaction =
- 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);
- }
-
- reactions[reactedTo].addReaction(room_id_.toStdString(), *reaction);
- int idx = idToIndex(reactedTo);
- if (idx >= 0)
- emit dataChanged(index(idx, 0), index(idx, 0));
- 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());
-
- if (std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallCandidates>>(e_)) {
- // don't display CallCandidate events to user
- events.insert(id, e);
- if (emitCallEvents)
- emit newCallEvent(e_);
- continue;
- }
-
- if (emitCallEvents) {
- if (auto callInvite = std::get_if<
- mtx::events::RoomEvent<mtx::events::msg::CallInvite>>(&e_)) {
- callInvite->room_id = room_id_.toStdString();
- emit newCallEvent(e_);
- } else if (std::holds_alternative<mtx::events::RoomEvent<
- mtx::events::msg::CallCandidates>>(e_) ||
- std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>(
- e_) ||
- std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>(
- e_)) {
- emit newCallEvent(e_);
- }
- }
- }
-
- if (std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallCandidates>>(e)) {
- // don't display CallCandidate events to user
- events.insert(id, e);
- if (emitCallEvents)
- emit newCallEvent(e);
- continue;
- }
-
- if (emitCallEvents) {
- if (auto callInvite =
- std::get_if<mtx::events::RoomEvent<mtx::events::msg::CallInvite>>(
- &e)) {
- callInvite->room_id = room_id_.toStdString();
- emit newCallEvent(e);
- } else if (std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallAnswer>>(e) ||
- std::holds_alternative<
- mtx::events::RoomEvent<mtx::events::msg::CallHangUp>>(e)) {
- emit newCallEvent(e);
- }
- }
-
- 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);
- });
- }
- }
- return ids;
-}
-
void
TimelineModel::setCurrentIndex(int index)
{
@@ -863,7 +644,7 @@ TimelineModel::setCurrentIndex(int index)
currentId = indexToId(index);
emit currentIndexChanged(index);
- if ((oldIndex > index || oldIndex == -1) && !pending.contains(currentId) &&
+ if ((oldIndex > index || oldIndex == -1) && !currentId.startsWith("m") &&
ChatPage::instance()->isActiveWindow()) {
readEvent(currentId.toStdString());
}
@@ -881,27 +662,6 @@ TimelineModel::readEvent(const std::string &id)
});
}
-void
-TimelineModel::addBackwardsEvents(const mtx::responses::Messages &msgs)
-{
- std::vector<QString> ids = internalAddEvents(msgs.chunk, false);
-
- 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
TimelineModel::displayName(QString id) const
{
@@ -938,7 +698,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.get(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);
}
@@ -946,13 +709,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.get(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);
}
@@ -963,114 +724,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)
{
@@ -1081,23 +734,18 @@ TimelineModel::replyAction(QString id)
RelatedInfo
TimelineModel::relatedInfo(QString id)
{
- if (!events.contains(id))
+ auto event = events.get(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);
@@ -1106,7 +754,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_;
@@ -1144,18 +792,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 - 1;
+ 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 - 1);
+ return id ? QString::fromStdString(*id) : "";
}
// Note: this will only be called for our messages
@@ -1189,28 +838,16 @@ TimelineModel::sendEncryptedMessageEvent(const std::string &txn_id,
try {
// Check if we have already an outbound megolm session then we can use.
if (cache::outboundMegolmSessionExists(room_id)) {
- auto data =
+ mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
+ event.content =
olm::encrypt_group_message(room_id, http::client()->device_id(), doc);
+ event.event_id = txn_id;
+ event.room_id = room_id;
+ event.sender = http::client()->user_id().to_string();
+ event.type = mtx::events::EventType::RoomEncrypted;
+ event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- http::client()->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
- room_id,
- txn_id,
- data,
- [this, txn_id](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code =
- static_cast<int>(err->status_code);
- nhlog::net()->warn("[{}] failed to send message: {} {}",
- txn_id,
- err->matrix_error.error,
- status_code);
- emit messageFailed(QString::fromStdString(txn_id));
- }
- emit messageSent(
- QString::fromStdString(txn_id),
- QString::fromStdString(res.event_id.to_string()));
- });
+ emit this->addPendingMessageToStore(event);
return;
}
@@ -1239,40 +876,25 @@ TimelineModel::sendEncryptedMessageEvent(const std::string &txn_id,
const auto members = cache::roomMembers(room_id);
nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
- auto keeper =
- std::make_shared<StateKeeper>([megolm_payload, room_id, doc, txn_id, this]() {
- try {
- auto data = olm::encrypt_group_message(
- room_id, http::client()->device_id(), doc);
+ auto keeper = std::make_shared<StateKeeper>([room_id, doc, txn_id, this]() {
+ try {
+ mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> event;
+ event.content = olm::encrypt_group_message(
+ room_id, http::client()->device_id(), doc);
+ event.event_id = txn_id;
+ event.room_id = room_id;
+ event.sender = http::client()->user_id().to_string();
+ event.type = mtx::events::EventType::RoomEncrypted;
+ event.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
- http::client()
- ->send_room_message<msg::Encrypted, EventType::RoomEncrypted>(
- room_id,
- txn_id,
- data,
- [this, txn_id](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code =
- static_cast<int>(err->status_code);
- nhlog::net()->warn(
- "[{}] failed to send message: {} {}",
- txn_id,
- err->matrix_error.error,
- status_code);
- emit messageFailed(
- QString::fromStdString(txn_id));
- }
- emit messageSent(
- QString::fromStdString(txn_id),
- QString::fromStdString(res.event_id.to_string()));
- });
- } catch (const lmdb::error &e) {
- nhlog::db()->critical(
- "failed to save megolm outbound session: {}", e.what());
- emit messageFailed(QString::fromStdString(txn_id));
- }
- });
+ emit this->addPendingMessageToStore(event);
+ } catch (const lmdb::error &e) {
+ nhlog::db()->critical("failed to save megolm outbound session: {}",
+ e.what());
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
+ }
+ });
mtx::requests::QueryKeys req;
for (const auto &member : members)
@@ -1286,8 +908,8 @@ TimelineModel::sendEncryptedMessageEvent(const std::string &txn_id,
nhlog::net()->warn("failed to query device keys: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
- // TODO: Mark the event as failed. Communicate with the UI.
- emit messageFailed(QString::fromStdString(txn_id));
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
return;
}
@@ -1387,11 +1009,13 @@ TimelineModel::sendEncryptedMessageEvent(const std::string &txn_id,
} catch (const lmdb::error &e) {
nhlog::db()->critical(
"failed to open outbound megolm session ({}): {}", room_id, e.what());
- emit messageFailed(QString::fromStdString(txn_id));
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
} catch (const mtx::crypto::olm_exception &e) {
nhlog::crypto()->critical(
"failed to open outbound megolm session ({}): {}", room_id, e.what());
- emit messageFailed(QString::fromStdString(txn_id));
+ emit ChatPage::instance()->showNotification(
+ tr("Failed to encrypt event, sending aborted!"));
}
}
@@ -1483,13 +1107,12 @@ TimelineModel::handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
struct SendMessageVisitor
{
- SendMessageVisitor(const QString &txn_id, TimelineModel *model)
- : txn_id_qstr_(txn_id)
- , model_(model)
+ explicit SendMessageVisitor(TimelineModel *model)
+ : model_(model)
{}
template<typename T, mtx::events::EventType Event>
- void sendRoomEvent(const mtx::events::RoomEvent<T> &msg)
+ void sendRoomEvent(mtx::events::RoomEvent<T> msg)
{
if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
auto encInfo = mtx::accessors::file(msg);
@@ -1497,36 +1120,13 @@ struct SendMessageVisitor
emit model_->newEncryptedImage(encInfo.value());
model_->sendEncryptedMessageEvent(
- txn_id_qstr_.toStdString(), nlohmann::json(msg.content), Event);
+ msg.event_id, nlohmann::json(msg.content), Event);
} else {
- sendUnencryptedRoomEvent<T, Event>(msg);
+ msg.type = Event;
+ emit model_->addPendingMessageToStore(msg);
}
}
- template<typename T, mtx::events::EventType Event>
- void sendUnencryptedRoomEvent(const mtx::events::RoomEvent<T> &msg)
- {
- QString txn_id_qstr = txn_id_qstr_;
- TimelineModel *model = model_;
- http::client()->send_room_message<T, Event>(
- model->room_id_.toStdString(),
- txn_id_qstr.toStdString(),
- msg.content,
- [txn_id_qstr, model](const mtx::responses::EventId &res,
- mtx::http::RequestErr err) {
- if (err) {
- const int status_code = static_cast<int>(err->status_code);
- nhlog::net()->warn("[{}] failed to send message: {} {}",
- txn_id_qstr.toStdString(),
- err->matrix_error.error,
- status_code);
- emit model->messageFailed(txn_id_qstr);
- }
- emit model->messageSent(txn_id_qstr,
- QString::fromStdString(res.event_id.to_string()));
- });
- }
-
// Do-nothing operator for all unhandled events
template<typename T>
void operator()(const mtx::events::Event<T> &)
@@ -1535,7 +1135,7 @@ struct SendMessageVisitor
// Operator for m.room.message events that contain a msgtype in their content
template<typename T,
std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
- void operator()(const mtx::events::RoomEvent<T> &msg)
+ void operator()(mtx::events::RoomEvent<T> msg)
{
sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg);
}
@@ -1545,10 +1145,10 @@ struct SendMessageVisitor
// reactions need to have the relation outside of ciphertext, or synapse / the homeserver
// cannot handle it correctly. See the MSC for more details:
// https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
+ void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
{
- sendUnencryptedRoomEvent<mtx::events::msg::Reaction,
- mtx::events::EventType::Reaction>(msg);
+ msg.type = mtx::events::EventType::Reaction;
+ emit model_->addPendingMessageToStore(msg);
}
void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event)
@@ -1575,65 +1175,38 @@ struct SendMessageVisitor
event);
}
- QString txn_id_qstr_;
TimelineModel *model_;
};
void
-TimelineModel::processOnePendingMessage()
-{
- if (pending.isEmpty())
- return;
-
- QString txn_id_qstr = pending.first();
-
- 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.event_id = http::client()->generate_txn_id();
+ msg.type = mtx::events::EventType::RoomMessage;
+ msg.event_id = "m" + http::client()->generate_txn_id();
msg.sender = http::client()->user_id().to_string();
msg.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
},
event);
- internalAddEvents({event}, false);
-
- 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) &&
- !std::get_if<mtx::events::RoomEvent<mtx::events::msg::CallCandidates>>(&event)) {
- beginInsertRows(QModelIndex(), 0, 0);
- this->eventOrder.insert(this->eventOrder.begin(), txn_id_qstr);
- endInsertRows();
- }
- updateLastMessage();
-
- emit nextPendingMessage();
+ std::visit(SendMessageVisitor{this}, event);
}
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.get(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) {
@@ -1698,18 +1271,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.get(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://")) {
@@ -1830,11 +1400,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.get(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 "";
@@ -1855,11 +1425,11 @@ TimelineModel::formatJoinRuleEvent(QString id)
QString
TimelineModel::formatGuestAccessEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.get(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 "";
@@ -1879,11 +1449,11 @@ TimelineModel::formatGuestAccessEvent(QString id)
QString
TimelineModel::formatHistoryVisibilityEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.get(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 "";
@@ -1913,11 +1483,11 @@ TimelineModel::formatHistoryVisibilityEvent(QString id)
QString
TimelineModel::formatPowerLevelEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.get(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 "";
@@ -1931,37 +1501,22 @@ TimelineModel::formatPowerLevelEvent(QString id)
QString
TimelineModel::formatMemberEvent(QString id)
{
- if (!events.contains(id))
+ mtx::events::collections::TimelineEvents *e = events.get(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)) {
- http::client()->get_event(
- this->room_id_.toStdString(),
- event->unsigned_data.replaces_state,
- [this, id, prevEventId](
- 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(),
- id.toStdString());
- return;
- }
- emit eventFetched(id, timeline);
- });
- } else {
+ if (!event->unsigned_data.replaces_state.empty()) {
+ auto tempPrevEvent =
+ events.get(event->unsigned_data.replaces_state, event->event_id);
+ if (tempPrevEvent) {
prevEvent =
std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(
- &events[prevEventId]);
+ tempPrevEvent);
}
}
|