diff options
Diffstat (limited to 'src/Cache.cpp')
-rw-r--r-- | src/Cache.cpp | 245 |
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) |