From 2e77a1554f1572b7c7e59f8177a48e5dffa16c23 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 26 Jan 2021 22:36:35 +0100 Subject: Switch to new relations format --- src/timeline/EventStore.cpp | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'src/timeline/EventStore.cpp') diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index be4bc09e..4a90222f 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -293,16 +293,16 @@ EventStore::handleSync(const mtx::responses::Timeline &events) } for (const auto &event : events.events) { - std::string relates_to; + std::set relates_to; if (auto redaction = std::get_if>( &event)) { // fixup reactions auto redacted = events_by_id_.object({room_id_, redaction->redacts}); if (redacted) { - auto id = mtx::accessors::relates_to_event_id(*redacted); - if (!id.empty()) { - auto idx = idToIndex(id); + auto id = mtx::accessors::relations(*redacted); + if (id.annotates()) { + auto idx = idToIndex(id.annotates()->event_id); if (idx) { events_by_id_.remove( {room_id_, redaction->redacts}); @@ -312,20 +312,17 @@ EventStore::handleSync(const mtx::responses::Timeline &events) } } - relates_to = redaction->redacts; - } else if (auto reaction = - std::get_if>( - &event)) { - relates_to = reaction->content.relates_to.event_id; + relates_to.insert(redaction->redacts); } else { - relates_to = mtx::accessors::in_reply_to_event(event); + for (const auto &r : mtx::accessors::relations(event).relations) + relates_to.insert(r.event_id); } - if (!relates_to.empty()) { - auto idx = cache::client()->getTimelineIndex(room_id_, relates_to); + for (const auto &relates_to_id : relates_to) { + auto idx = cache::client()->getTimelineIndex(room_id_, relates_to_id); if (idx) { - events_by_id_.remove({room_id_, relates_to}); - decryptedEvents_.remove({room_id_, relates_to}); + events_by_id_.remove({room_id_, relates_to_id}); + decryptedEvents_.remove({room_id_, relates_to_id}); events_.remove({room_id_, *idx}); emit dataChanged(toExternalIdx(*idx), toExternalIdx(*idx)); } @@ -430,13 +427,14 @@ EventStore::reactions(const std::string &event_id) if (auto reaction = std::get_if>( related_event); - reaction && reaction->content.relates_to.key) { - auto &agg = aggregation[reaction->content.relates_to.key.value()]; + reaction && reaction->content.relations.annotates() && + reaction->content.relations.annotates()->key) { + auto key = reaction->content.relations.annotates()->key.value(); + auto &agg = aggregation[key]; if (agg.count == 0) { Reaction temp{}; - temp.key_ = - QString::fromStdString(reaction->content.relates_to.key.value()); + temp.key_ = QString::fromStdString(key); reactions.push_back(temp); } @@ -691,8 +689,7 @@ EventStore::decryptEvent(const IdIndex &idx, 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"]; + mtx::common::add_relations(body["content"], e.content.relations); json event_array = json::array(); event_array.push_back(body); -- cgit 1.5.1 From d6504812c71ff7251a5319113c580ab322469eb3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 27 Jan 2021 02:45:33 +0100 Subject: Render edits --- src/Cache.cpp | 30 +++++++++++++++++++++++++ src/Cache_p.h | 2 ++ src/timeline/EventStore.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++--- src/timeline/EventStore.h | 4 +++- 4 files changed, 86 insertions(+), 4 deletions(-) (limited to 'src/timeline/EventStore.cpp') diff --git a/src/Cache.cpp b/src/Cache.cpp index 94b9a6a6..49861a9a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -108,6 +108,11 @@ Cache::isHiddenEvent(lmdb::txn &txn, const std::string &room_id) { using namespace mtx::events; + + // Always hide edits + if (mtx::accessors::relations(e).replaces()) + return true; + if (auto encryptedEvent = std::get_if>(&e)) { MegolmSessionIndex index; index.room_id = room_id; @@ -1891,6 +1896,31 @@ Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id) return *val.data(); } +std::optional +Cache::getArrivalIndex(const std::string &room_id, std::string_view event_id) +{ + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + + lmdb::dbi orderDb{0}; + try { + orderDb = getEventToOrderDb(txn, room_id); + } catch (lmdb::runtime_error &e) { + nhlog::db()->error("Can't open db for room '{}', probably doesn't exist yet. ({})", + room_id, + e.what()); + return {}; + } + + 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(); +} + std::optional Cache::getTimelineEventId(const std::string &room_id, uint64_t index) { diff --git a/src/Cache_p.h b/src/Cache_p.h index e2ce1668..c96a3f30 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -205,6 +205,8 @@ public: std::optional getTimelineIndex(const std::string &room_id, std::string_view event_id); std::optional getTimelineEventId(const std::string &room_id, uint64_t index); + std::optional getArrivalIndex(const std::string &room_id, + std::string_view event_id); std::string previousBatchToken(const std::string &room_id); uint64_t saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res); diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 4a90222f..ebf2f024 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -405,6 +405,41 @@ EventStore::handle_room_verification(mtx::events::collections::TimelineEvents ev event); } +std::vector +EventStore::edits(const std::string &event_id) +{ + auto event_ids = cache::client()->relatedEvents(room_id_, event_id); + + auto original_event = get(event_id, "", false, false); + if (!original_event) + return {}; + + auto original_sender = mtx::accessors::sender(*original_event); + + std::vector edits; + for (const auto &id : event_ids) { + auto related_event = get(id, event_id, false, false); + if (!related_event) + continue; + + auto edit_rel = mtx::accessors::relations(*related_event); + if (edit_rel.replaces() == event_id && + original_sender == mtx::accessors::sender(*related_event)) + edits.push_back(*related_event); + } + + auto c = cache::client(); + std::sort(edits.begin(), + edits.end(), + [this, c](const mtx::events::collections::TimelineEvents &a, + const mtx::events::collections::TimelineEvents &b) { + return c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(a)) < + c->getArrivalIndex(this->room_id_, mtx::accessors::event_id(b)); + }); + + return edits; +} + QVariantList EventStore::reactions(const std::string &event_id) { @@ -487,7 +522,13 @@ EventStore::get(int idx, bool decrypt) if (!event_id) return nullptr; - auto event = cache::client()->getEvent(room_id_, *event_id); + std::optional event; + auto edits_ = edits(*event_id); + if (edits_.empty()) + event = cache::client()->getEvent(room_id_, *event_id); + else + event = {edits_.back()}; + if (!event) return nullptr; else @@ -714,7 +755,7 @@ EventStore::decryptEvent(const IdIndex &idx, } mtx::events::collections::TimelineEvents * -EventStore::get(std::string_view id, std::string_view related_to, bool decrypt) +EventStore::get(std::string_view id, std::string_view related_to, bool decrypt, bool resolve_edits) { if (this->thread() != QThread::currentThread()) nhlog::db()->warn("{} called from a different thread!", __func__); @@ -722,7 +763,14 @@ EventStore::get(std::string_view id, std::string_view related_to, bool decrypt) if (id.empty()) return nullptr; - IdIndex index{room_id_, std::string(id.data(), id.size())}; + std::string id_ = std::string(id); + if (resolve_edits) { + auto edits_ = edits(id_); + if (!edits_.empty()) + id_ = mtx::accessors::event_id(edits_.back()); + } + + IdIndex index{room_id_, id_}; auto event_ptr = events_by_id_.object(index); if (!event_ptr) { diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index f8eff9a9..ced7bdc0 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -66,7 +66,8 @@ public: // relatedFetched event mtx::events::collections::TimelineEvents *get(std::string_view id, std::string_view related_to, - bool decrypt = true); + bool decrypt = true, + bool resolve_edits = true); // always returns a proper event as long as the idx is valid mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true); @@ -110,6 +111,7 @@ public slots: void clearTimeline(); private: + std::vector edits(const std::string &event_id); mtx::events::collections::TimelineEvents *decryptEvent( const IdIndex &idx, const mtx::events::EncryptedEvent &e); -- cgit 1.5.1 From faeaf9dc6bfae5bf56f6edee4e21bb54db08b2e1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 27 Jan 2021 16:14:03 +0100 Subject: Fix edited replies --- src/EventAccessors.cpp | 21 +++++++++++++++++++++ src/EventAccessors.h | 2 ++ src/timeline/EventStore.cpp | 19 +++++++++++++++---- 3 files changed, 38 insertions(+), 4 deletions(-) (limited to 'src/timeline/EventStore.cpp') diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 4218f491..212c2970 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -264,6 +264,20 @@ struct EventRelations } }; +struct SetEventRelations +{ + mtx::common::Relations new_relations; + template + using related_ev_id_t = decltype(Content::relations); + template + void operator()(mtx::events::Event &e) + { + if constexpr (is_detected::value) { + e.content.relations = std::move(new_relations); + } + } +}; + struct EventTransactionId { template @@ -426,6 +440,13 @@ mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event) return std::visit(EventRelations{}, event); } +void +mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event, + mtx::common::Relations relations) +{ + std::visit(SetEventRelations{std::move(relations)}, event); +} + std::string mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event) { diff --git a/src/EventAccessors.h b/src/EventAccessors.h index 60912497..95e5df24 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -55,6 +55,8 @@ std::string mimetype(const mtx::events::collections::TimelineEvents &event); mtx::common::Relations relations(const mtx::events::collections::TimelineEvents &event); +void +set_relations(mtx::events::collections::TimelineEvents &event, mtx::common::Relations relations); std::string transaction_id(const mtx::events::collections::TimelineEvents &event); diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index ebf2f024..e5a66e19 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -414,7 +414,8 @@ EventStore::edits(const std::string &event_id) if (!original_event) return {}; - auto original_sender = mtx::accessors::sender(*original_event); + auto original_sender = mtx::accessors::sender(*original_event); + auto original_relations = mtx::accessors::relations(*original_event); std::vector edits; for (const auto &id : event_ids) { @@ -422,10 +423,20 @@ EventStore::edits(const std::string &event_id) if (!related_event) continue; - auto edit_rel = mtx::accessors::relations(*related_event); + auto related_ev = *related_event; + + auto edit_rel = mtx::accessors::relations(related_ev); if (edit_rel.replaces() == event_id && - original_sender == mtx::accessors::sender(*related_event)) - edits.push_back(*related_event); + original_sender == mtx::accessors::sender(related_ev)) { + if (edit_rel.synthesized && original_relations.reply_to() && + !edit_rel.reply_to()) { + edit_rel.relations.push_back( + {mtx::common::RelationType::InReplyTo, + original_relations.reply_to().value()}); + mtx::accessors::set_relations(related_ev, std::move(edit_rel)); + } + edits.push_back(std::move(related_ev)); + } } auto c = cache::client(); -- cgit 1.5.1 From 00fd4eecec1af3a38bc69f3849f9e49f826cef26 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 31 Jan 2021 22:41:43 +0100 Subject: Display edits correctly --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- resources/qml/TimelineRow.qml | 14 ++++++++++++++ src/EventAccessors.cpp | 20 ++++++++++++++++++++ src/EventAccessors.h | 3 +++ src/timeline/EventStore.cpp | 14 ++++++++------ src/timeline/TimelineModel.cpp | 39 ++++++++++++++++++++++++++++++++++++--- src/timeline/TimelineModel.h | 17 ++++++++++++++++- 8 files changed, 99 insertions(+), 12 deletions(-) (limited to 'src/timeline/EventStore.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index cf2b5959..577cbffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,7 +356,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 70fa15de3ec84cf0c0ab6250f2e5e62f34a6d05b + GIT_TAG 31e300546eb63ea25b0b879fb255beee6022da03 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 98ab9629..f498dd5a 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -220,7 +220,7 @@ "name": "mtxclient", "sources": [ { - "commit": "70fa15de3ec84cf0c0ab6250f2e5e62f34a6d05b", + "commit": "31e300546eb63ea25b0b879fb255beee6022da03", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 95a025cf..e4dc267b 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -85,6 +85,20 @@ Item { width: 16 } + ImageButton { + id: editButton + + visible: (Settings.buttonsInTimeline && model.isEditable) || model.isEdited + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + width: 16 + hoverEnabled: true + image: ":/icons/icons/ui/edit.png" + ToolTip.visible: hovered + ToolTip.text: model.isEditable ? qsTr("Edit") : qsTr("Edited") + onClicked: if (model.isEditable) chat.model.editAction(model.id) + } + EmojiButton { id: reactButton diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 212c2970..e6bc61b0 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -34,6 +34,20 @@ struct detector>, Op, Args...> template class Op, class... Args> using is_detected = typename detail::detector::value_t; +struct IsStateEvent +{ + template + bool operator()(const mtx::events::StateEvent &) + { + return true; + } + template + bool operator()(const mtx::events::Event &) + { + return false; + } +}; + struct EventMsgType { template @@ -476,3 +490,9 @@ mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents & { return std::visit([](const auto &e) { return nlohmann::json(e); }, event); } + +bool +mtx::accessors::is_state_event(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit(IsStateEvent{}, event); +} diff --git a/src/EventAccessors.h b/src/EventAccessors.h index 95e5df24..7bf695fc 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -17,6 +17,9 @@ room_id(const mtx::events::collections::TimelineEvents &event); std::string sender(const mtx::events::collections::TimelineEvents &event); +bool +is_state_event(const mtx::events::collections::TimelineEvents &event); + QDateTime origin_server_ts(const mtx::events::collections::TimelineEvents &event); diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index e5a66e19..94d43a83 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -774,15 +774,17 @@ EventStore::get(std::string_view id, std::string_view related_to, bool decrypt, if (id.empty()) return nullptr; - std::string id_ = std::string(id); + IdIndex index{room_id_, std::string(id)}; if (resolve_edits) { - auto edits_ = edits(id_); - if (!edits_.empty()) - id_ = mtx::accessors::event_id(edits_.back()); + auto edits_ = edits(index.id); + if (!edits_.empty()) { + index.id = mtx::accessors::event_id(edits_.back()); + auto event_ptr = + new mtx::events::collections::TimelineEvents(std::move(edits_.back())); + events_by_id_.insert(index, event_ptr); + } } - IdIndex index{room_id_, id_}; - auto event_ptr = events_by_id_.object(index); if (!event_ptr) { auto event = cache::client()->getEvent(room_id_, index.id); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index c47194f5..dd4f8696 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -288,6 +288,8 @@ TimelineModel::roleNames() const {ProportionalHeight, "proportionalHeight"}, {Id, "id"}, {State, "state"}, + {IsEdited, "isEdited"}, + {IsEditable, "isEditable"}, {IsEncrypted, "isEncrypted"}, {IsRoomEncrypted, "isRoomEncrypted"}, {ReplyTo, "replyTo"}, @@ -409,8 +411,12 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r return QVariant(prop > 0 ? prop : 1.); } - case Id: - return QVariant(QString::fromStdString(event_id(event))); + case Id: { + if (auto replaces = relations(event).replaces()) + return QVariant(QString::fromStdString(replaces.value())); + else + return QVariant(QString::fromStdString(event_id(event))); + } case State: { auto id = QString::fromStdString(event_id(event)); auto containsOthers = [](const auto &vec) { @@ -430,6 +436,11 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r else return qml_mtx_events::Received; } + case IsEdited: + return QVariant(relations(event).replaces().has_value()); + case IsEditable: + return QVariant(!is_state_event(event) && mtx::accessors::sender(event) == + http::client()->user_id().to_string()); case IsEncrypted: { auto id = event_id(event); auto encrypted_event = events.get(id, id, false); @@ -444,7 +455,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r case ReplyTo: return QVariant(QString::fromStdString(relations(event).reply_to().value_or(""))); case Reactions: { - auto id = event_id(event); + auto id = relations(event).replaces().value_or(event_id(event)); return QVariant::fromValue(events.reactions(id)); } case RoomId: @@ -813,6 +824,12 @@ TimelineModel::replyAction(QString id) setReply(id); } +void +TimelineModel::editAction(QString id) +{ + setEdit(id); +} + RelatedInfo TimelineModel::relatedInfo(QString id) { @@ -1501,6 +1518,22 @@ TimelineModel::formatMemberEvent(QString id) return rendered; } +void +TimelineModel::setEdit(QString newEdit) +{ + if (edit_ != newEdit) { + edit_ = newEdit; + emit editChanged(edit_); + + auto ev = events.get(newEdit.toStdString(), ""); + if (ev) { + setReply(QString::fromStdString( + mtx::accessors::relations(*ev).reply_to().value_or(""))); + // input()->setText(mtx::accessors::body(*ev)); + } + } +} + QString TimelineModel::roomName() const { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 51b8049e..463d8705 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -145,6 +145,7 @@ class TimelineModel : public QAbstractListModel Q_PROPERTY(std::vector typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY typingUsersChanged) Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply) + Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) Q_PROPERTY( bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) @@ -181,6 +182,8 @@ public: ProportionalHeight, Id, State, + IsEdited, + IsEditable, IsEncrypted, IsRoomEncrypted, ReplyTo, @@ -213,6 +216,7 @@ public: Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid, bool global = false); + Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void redactEvent(QString id); @@ -268,6 +272,16 @@ public slots: emit replyChanged(reply_); } } + QString edit() const { return edit_; } + void setEdit(QString newEdit); + void resetEdit() + { + if (!edit_.isEmpty()) { + edit_ = ""; + emit editChanged(edit_); + resetReply(); + } + } void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; } void clearTimeline() { events.clearTimeline(); } void receivedSessionKey(const std::string &session_key) @@ -292,6 +306,7 @@ signals: void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); void typingUsersChanged(std::vector users); void replyChanged(QString reply); + void editChanged(QString reply); void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); @@ -322,7 +337,7 @@ private: bool m_paginationInProgress = false; QString currentId; - QString reply_; + QString reply_, edit_; std::vector typingUsers_; TimelineViewManager *manager_; -- cgit 1.5.1