summary refs log tree commit diff
path: root/src/Cache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Cache.cpp')
-rw-r--r--src/Cache.cpp245
1 files changed, 242 insertions, 3 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 25904e53..d7cd113b 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -24,6 +24,8 @@
 #include <qt6keychain/keychain.h>
 #endif
 
+#include <nlohmann/json.hpp>
+
 #include <mtx/responses/common.hpp>
 #include <mtx/responses/messages.hpp>
 
@@ -532,8 +534,8 @@ Cache::loadSecretsFromStore(
              name,
              toLoad,
              job,
-             name_    = name_,
-             internal = internal,
+             name__    = name_,
+             internal_ = internal,
              callback,
              databaseReadyOnFinished](QKeychain::Job *) mutable {
                 nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first);
@@ -549,7 +551,7 @@ Cache::loadSecretsFromStore(
                 if (secret.isEmpty()) {
                     nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString());
                 } else {
-                    callback(name_, internal, secret.toStdString());
+                    callback(name__, internal_, secret.toStdString());
                 }
 
                 // load next secret
@@ -2042,6 +2044,213 @@ isMessage(const mtx::events::RoomEvent<mtx::events::voip::CallHangUp> &)
 // }
 }
 
+template<typename T>
+std::optional<mtx::events::StateEvent<T>>
+Cache::getStateEvent(lmdb::txn &txn, const std::string &room_id, std::string_view state_key)
+{
+    try {
+        constexpr auto type = mtx::events::state_content_to_type<T>;
+        static_assert(type != mtx::events::EventType::Unsupported,
+                      "Not a supported type in state events.");
+
+        if (room_id.empty())
+            return std::nullopt;
+        const auto typeStr = to_string(type);
+
+        std::string_view value;
+        if (state_key.empty()) {
+            auto db = getStatesDb(txn, room_id);
+            if (!db.get(txn, typeStr, value)) {
+                return std::nullopt;
+            }
+        } else {
+            auto db = getStatesKeyDb(txn, room_id);
+            // we can search using state key, since the compare functions defaults to the whole
+            // string, when there is no nullbyte
+            std::string_view data     = state_key;
+            std::string_view typeStrV = typeStr;
+
+            auto cursor = lmdb::cursor::open(txn, db);
+            if (!cursor.get(typeStrV, data, MDB_GET_BOTH))
+                return std::nullopt;
+
+            try {
+                auto eventsDb = getEventsDb(txn, room_id);
+                auto eventid  = data;
+                if (auto sep = data.rfind('\0'); sep != std::string_view::npos) {
+                    if (!eventsDb.get(txn, eventid.substr(sep + 1), value))
+                        return std::nullopt;
+                } else {
+                    return std::nullopt;
+                }
+
+            } catch (std::exception &) {
+                return std::nullopt;
+            }
+        }
+
+        return nlohmann::json::parse(value).get<mtx::events::StateEvent<T>>();
+    } catch (std::exception &) {
+        return std::nullopt;
+    }
+}
+
+template<typename T>
+std::vector<mtx::events::StateEvent<T>>
+Cache::getStateEventsWithType(lmdb::txn &txn,
+                              const std::string &room_id,
+                              mtx::events::EventType type)
+
+{
+    if (room_id.empty())
+        return {};
+
+    std::vector<mtx::events::StateEvent<T>> events;
+
+    {
+        auto db                   = getStatesKeyDb(txn, room_id);
+        auto eventsDb             = getEventsDb(txn, room_id);
+        const auto typeStr        = to_string(type);
+        std::string_view typeStrV = typeStr;
+        std::string_view data;
+        std::string_view value;
+
+        auto cursor = lmdb::cursor::open(txn, db);
+        bool first  = true;
+        if (cursor.get(typeStrV, data, MDB_SET)) {
+            while (cursor.get(typeStrV, data, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) {
+                first = false;
+
+                try {
+                    auto eventid = data;
+                    if (auto sep = data.rfind('\0'); sep != std::string_view::npos) {
+                        if (eventsDb.get(txn, eventid.substr(sep + 1), value))
+                            events.push_back(
+                              nlohmann::json::parse(value).get<mtx::events::StateEvent<T>>());
+                    }
+                } catch (std::exception &e) {
+                    nhlog::db()->warn("Failed to parse state event: {}", e.what());
+                }
+            }
+        }
+    }
+
+    return events;
+}
+
+template<class T>
+void
+Cache::saveStateEvents(lmdb::txn &txn,
+                       lmdb::dbi &statesdb,
+                       lmdb::dbi &stateskeydb,
+                       lmdb::dbi &membersdb,
+                       lmdb::dbi &eventsDb,
+                       const std::string &room_id,
+                       const std::vector<T> &events)
+{
+    for (const auto &e : events)
+        saveStateEvent(txn, statesdb, stateskeydb, membersdb, eventsDb, room_id, e);
+}
+
+template<class T>
+void
+Cache::saveStateEvent(lmdb::txn &txn,
+                      lmdb::dbi &statesdb,
+                      lmdb::dbi &stateskeydb,
+                      lmdb::dbi &membersdb,
+                      lmdb::dbi &eventsDb,
+                      const std::string &room_id,
+                      const T &event)
+{
+    using namespace mtx::events;
+    using namespace mtx::events::state;
+
+    if (auto e = std::get_if<StateEvent<Member>>(&event); e != nullptr) {
+        switch (e->content.membership) {
+        //
+        // We only keep users with invite or join membership.
+        //
+        case Membership::Invite:
+        case Membership::Join: {
+            auto display_name =
+              e->content.display_name.empty() ? e->state_key : e->content.display_name;
+
+            std::string inviter = "";
+            if (e->content.membership == mtx::events::state::Membership::Invite) {
+                inviter = e->sender;
+            }
+
+            // Lightweight representation of a member.
+            MemberInfo tmp{
+              display_name,
+              e->content.avatar_url,
+              inviter,
+              e->content.reason,
+              e->content.is_direct,
+            };
+
+            membersdb.put(txn, e->state_key, nlohmann::json(tmp).dump());
+            break;
+        }
+        default: {
+            membersdb.del(txn, e->state_key, "");
+            break;
+        }
+        }
+    } else if (auto encr = std::get_if<StateEvent<Encryption>>(&event)) {
+        if (!encr->state_key.empty())
+            return;
+
+        setEncryptedRoom(txn, room_id);
+
+        std::string_view temp;
+        // ensure we don't replace the event in the db
+        if (statesdb.get(txn, to_string(encr->type), temp)) {
+            return;
+        }
+    }
+
+    std::visit(
+      [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) {
+          if constexpr (isStateEvent_<decltype(e)>) {
+              eventsDb.put(txn, e.event_id, nlohmann::json(e).dump());
+
+              if (e.type != EventType::Unsupported) {
+                  if (std::is_same_v<std::remove_cv_t<std::remove_reference_t<decltype(e)>>,
+                                     StateEvent<mtx::events::msg::Redacted>>) {
+                      // apply the redaction event
+                      if (e.type == EventType::RoomMember) {
+                          // membership is not revoked, but names are yeeted (so we set the name
+                          // to the mxid)
+                          MemberInfo tmp{e.state_key, ""};
+                          membersdb.put(txn, e.state_key, nlohmann::json(tmp).dump());
+                      } else if (e.state_key.empty()) {
+                          // strictly speaking some stuff in those events can be redacted, but
+                          // this is close enough. Ref:
+                          // https://spec.matrix.org/v1.6/rooms/v10/#redactions
+                          if (e.type != EventType::RoomCreate &&
+                              e.type != EventType::RoomJoinRules &&
+                              e.type != EventType::RoomPowerLevels &&
+                              e.type != EventType::RoomHistoryVisibility)
+                              statesdb.del(txn, to_string(e.type));
+                      } else
+                          stateskeydb.del(txn, to_string(e.type), e.state_key + '\0' + e.event_id);
+                  } else if (e.state_key.empty()) {
+                      statesdb.put(txn, to_string(e.type), nlohmann::json(e).dump());
+                  } else {
+                      auto data = e.state_key + '\0' + e.event_id;
+                      auto key  = to_string(e.type);
+
+                      // Work around https://bugs.openldap.org/show_bug.cgi?id=8447
+                      stateskeydb.del(txn, key, data);
+                      stateskeydb.put(txn, key, data);
+                  }
+              }
+          }
+      },
+      event);
+}
+
 void
 Cache::saveState(const mtx::responses::Sync &res)
 {
@@ -6004,3 +6213,33 @@ secret(const std::string &name)
     return instance_->secret(name);
 }
 } // namespace cache
+
+#define NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(Content)                                            \
+    template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent(                 \
+      lmdb::txn &txn, const std::string &room_id, std::string_view state_key);                     \
+                                                                                                   \
+    template std::vector<mtx::events::StateEvent<Content>> Cache::getStateEventsWithType(          \
+      lmdb::txn &txn, const std::string &room_id, mtx::events::EventType type);
+
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Aliases)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Avatar)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::CanonicalAlias)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Create)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Encryption)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::GuestAccess)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::HistoryVisibility)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::JoinRules)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Member)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Name)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PinnedEvents)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::PowerLevels)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Tombstone)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::ServerAcl)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Topic)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::Widget)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::policy_rule::UserRule)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::policy_rule::RoomRule)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::policy_rule::ServerRule)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Child)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::state::space::Parent)
+NHEKO_CACHE_GET_STATE_EVENT_DEFINITION(mtx::events::msc2545::ImagePack)