diff options
author | Joseph Donofry <joedonofry@gmail.com> | 2021-11-03 21:43:11 -0400 |
---|---|---|
committer | Joseph Donofry <joedonofry@gmail.com> | 2021-11-03 21:43:11 -0400 |
commit | 743a83c8e6f0b64b21e8042a9eb04ce35c713008 (patch) | |
tree | f980bdb8c45e607547f87e48f42144227166aa6c /src/Cache.cpp | |
parent | Merge remote-tracking branch 'nheko-im/master' into video_player_enhancements (diff) | |
parent | Update translations (diff) | |
download | nheko-743a83c8e6f0b64b21e8042a9eb04ce35c713008.tar.xz |
Update video_player_enhancements with changes from master
Diffstat (limited to 'src/Cache.cpp')
-rw-r--r-- | src/Cache.cpp | 6346 |
1 files changed, 3225 insertions, 3121 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp index 8b8b2985..58eb2630 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -13,6 +13,7 @@ #include <QFile> #include <QHash> #include <QMap> +#include <QMessageBox> #include <QStandardPaths> #if __has_include(<keychain.h>) @@ -29,19 +30,19 @@ #include "EventAccessors.h" #include "Logging.h" #include "MatrixClient.h" -#include "Olm.h" #include "UserSettingsPage.h" #include "Utils.h" +#include "encryption/Olm.h" //! Should be changed when a breaking change occurs in the cache format. //! This will reset client's data. -static const std::string CURRENT_CACHE_FORMAT_VERSION("2020.10.20"); -static const std::string SECRET("secret"); +static const std::string CURRENT_CACHE_FORMAT_VERSION("2021.08.31"); //! Keys used for the DB static const std::string_view NEXT_BATCH_KEY("next_batch"); static const std::string_view OLM_ACCOUNT_KEY("olm_account"); static const std::string_view CACHE_FORMAT_VERSION_KEY("cache_format_version"); +static const std::string_view CURRENT_ONLINE_BACKUP_VERSION("current_online_backup_version"); constexpr size_t MAX_RESTORED_MESSAGES = 30'000; @@ -96,55 +97,55 @@ std::unique_ptr<Cache> instance_ = nullptr; struct RO_txn { - ~RO_txn() { txn.reset(); } - operator MDB_txn *() const noexcept { return txn.handle(); } - operator lmdb::txn &() noexcept { return txn; } + ~RO_txn() { txn.reset(); } + operator MDB_txn *() const noexcept { return txn.handle(); } + operator lmdb::txn &() noexcept { return txn; } - lmdb::txn &txn; + lmdb::txn &txn; }; RO_txn ro_txn(lmdb::env &env) { - thread_local lmdb::txn txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - thread_local int reuse_counter = 0; + thread_local lmdb::txn txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + thread_local int reuse_counter = 0; - if (reuse_counter >= 100 || txn.env() != env.handle()) { - txn.abort(); - txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - reuse_counter = 0; - } else if (reuse_counter > 0) { - try { - txn.renew(); - } catch (...) { - txn.abort(); - txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); - reuse_counter = 0; - } + if (reuse_counter >= 100 || txn.env() != env.handle()) { + txn.abort(); + txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + reuse_counter = 0; + } else if (reuse_counter > 0) { + try { + txn.renew(); + } catch (...) { + txn.abort(); + txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY); + reuse_counter = 0; } - reuse_counter++; + } + reuse_counter++; - return RO_txn{txn}; + return RO_txn{txn}; } template<class T> bool containsStateUpdates(const T &e) { - return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, e); + return std::visit([](const auto &ev) { return Cache::isStateEvent_<decltype(ev)>; }, e); } bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - return std::holds_alternative<StrippedEvent<state::Avatar>>(e) || - std::holds_alternative<StrippedEvent<CanonicalAlias>>(e) || - std::holds_alternative<StrippedEvent<Name>>(e) || - std::holds_alternative<StrippedEvent<Member>>(e) || - std::holds_alternative<StrippedEvent<Topic>>(e); + return std::holds_alternative<StrippedEvent<state::Avatar>>(e) || + std::holds_alternative<StrippedEvent<CanonicalAlias>>(e) || + std::holds_alternative<StrippedEvent<Name>>(e) || + std::holds_alternative<StrippedEvent<Member>>(e) || + std::holds_alternative<StrippedEvent<Topic>>(e); } bool @@ -152,45 +153,45 @@ Cache::isHiddenEvent(lmdb::txn &txn, mtx::events::collections::TimelineEvents e, const std::string &room_id) { - using namespace mtx::events; + using namespace mtx::events; - // Always hide edits - if (mtx::accessors::relations(e).replaces()) - return true; - - if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { - MegolmSessionIndex index; - index.room_id = room_id; - index.session_id = encryptedEvent->content.session_id; - index.sender_key = encryptedEvent->content.sender_key; - - auto result = olm::decryptEvent(index, *encryptedEvent, true); - if (!result.error) - e = result.event.value(); - } + // Always hide edits + if (mtx::accessors::relations(e).replaces()) + return true; - mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents; - hiddenEvents.hidden_event_types = { - EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; - - if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) - hiddenEvents = - std::move(std::get<mtx::events::AccountDataEvent< - mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp) - .content); - if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) - hiddenEvents = - std::move(std::get<mtx::events::AccountDataEvent< - mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp) - .content); - - return std::visit( - [hiddenEvents](const auto &ev) { - return std::any_of(hiddenEvents.hidden_event_types.begin(), - hiddenEvents.hidden_event_types.end(), - [ev](EventType type) { return type == ev.type; }); - }, - e); + if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) { + MegolmSessionIndex index; + index.room_id = room_id; + index.session_id = encryptedEvent->content.session_id; + index.sender_key = encryptedEvent->content.sender_key; + + auto result = olm::decryptEvent(index, *encryptedEvent, true); + if (!result.error) + e = result.event.value(); + } + + mtx::events::account_data::nheko_extensions::HiddenEvents hiddenEvents; + hiddenEvents.hidden_event_types = { + EventType::Reaction, EventType::CallCandidates, EventType::Unsupported}; + + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, "")) + hiddenEvents = + std::move(std::get<mtx::events::AccountDataEvent< + mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp) + .content); + if (auto temp = getAccountData(txn, mtx::events::EventType::NhekoHiddenEvents, room_id)) + hiddenEvents = + std::move(std::get<mtx::events::AccountDataEvent< + mtx::events::account_data::nheko_extensions::HiddenEvents>>(*temp) + .content); + + return std::visit( + [hiddenEvents](const auto &ev) { + return std::any_of(hiddenEvents.hidden_event_types.begin(), + hiddenEvents.hidden_event_types.end(), + [ev](EventType type) { return type == ev.type; }); + }, + e); } Cache::Cache(const QString &userId, QObject *parent) @@ -198,217 +199,221 @@ Cache::Cache(const QString &userId, QObject *parent) , env_{nullptr} , localUserId_{userId} { - setup(); - connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); + setup(); + connect(this, &Cache::userKeysUpdate, this, &Cache::updateUserKeys, Qt::QueuedConnection); + connect( + this, + &Cache::verificationStatusChanged, + this, + [this](const std::string &u) { + if (u == localUserId_.toStdString()) { + auto status = verificationStatus(u); + emit selfVerificationStatusChanged(); + } + }, + Qt::QueuedConnection); } void Cache::setup() { - auto settings = UserSettings::instance(); + auto settings = UserSettings::instance(); - nhlog::db()->debug("setting up cache"); + nhlog::db()->debug("setting up cache"); - // Previous location of the cache directory - auto oldCache = QString("%1/%2%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) - .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); + // Previous location of the cache directory + auto oldCache = QString("%1/%2%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) + .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); - cacheDirectory_ = QString("%1/%2%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) - .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); + cacheDirectory_ = QString("%1/%2%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) + .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())) + .arg(QString::fromUtf8(settings->profile().toUtf8().toHex())); - bool isInitial = !QFile::exists(cacheDirectory_); + bool isInitial = !QFile::exists(cacheDirectory_); - // NOTE: If both cache directories exist it's better to do nothing: it - // could mean a previous migration failed or was interrupted. - bool needsMigration = isInitial && QFile::exists(oldCache); + // NOTE: If both cache directories exist it's better to do nothing: it + // could mean a previous migration failed or was interrupted. + bool needsMigration = isInitial && QFile::exists(oldCache); - if (needsMigration) { - nhlog::db()->info("found old state directory, migrating"); - if (!QDir().rename(oldCache, cacheDirectory_)) { - throw std::runtime_error(("Unable to migrate the old state directory (" + - oldCache + ") to the new location (" + - cacheDirectory_ + ")") - .toStdString() - .c_str()); - } - nhlog::db()->info("completed state migration"); + if (needsMigration) { + nhlog::db()->info("found old state directory, migrating"); + if (!QDir().rename(oldCache, cacheDirectory_)) { + throw std::runtime_error(("Unable to migrate the old state directory (" + oldCache + + ") to the new location (" + cacheDirectory_ + ")") + .toStdString() + .c_str()); } + nhlog::db()->info("completed state migration"); + } - env_ = lmdb::env::create(); - env_.set_mapsize(DB_SIZE); - env_.set_max_dbs(MAX_DBS); + env_ = lmdb::env::create(); + env_.set_mapsize(DB_SIZE); + env_.set_max_dbs(MAX_DBS); - if (isInitial) { - nhlog::db()->info("initializing LMDB"); + if (isInitial) { + nhlog::db()->info("initializing LMDB"); - if (!QDir().mkpath(cacheDirectory_)) { - throw std::runtime_error( - ("Unable to create state directory:" + cacheDirectory_) - .toStdString() - .c_str()); - } + if (!QDir().mkpath(cacheDirectory_)) { + throw std::runtime_error( + ("Unable to create state directory:" + cacheDirectory_).toStdString().c_str()); } + } - try { - // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but - // it can really mess up our database, so we shouldn't. For now, hopefully - // NOMETASYNC is fast enough. - env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC); - } catch (const lmdb::error &e) { - if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { - throw std::runtime_error("LMDB initialization failed" + - std::string(e.what())); - } + try { + // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but + // it can really mess up our database, so we shouldn't. For now, hopefully + // NOMETASYNC is fast enough. + env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC); + } catch (const lmdb::error &e) { + if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { + throw std::runtime_error("LMDB initialization failed" + std::string(e.what())); + } - nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what()); + nhlog::db()->warn("resetting cache due to LMDB version mismatch: {}", e.what()); - QDir stateDir(cacheDirectory_); + QDir stateDir(cacheDirectory_); - for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { - if (!stateDir.remove(file)) - throw std::runtime_error( - ("Unable to delete file " + file).toStdString().c_str()); - } - env_.open(cacheDirectory_.toStdString().c_str()); + for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { + if (!stateDir.remove(file)) + throw std::runtime_error(("Unable to delete file " + file).toStdString().c_str()); } + env_.open(cacheDirectory_.toStdString().c_str()); + } - auto txn = lmdb::txn::begin(env_); - syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE); - roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); - spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT); - spacesParentsDb_ = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT); - invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE); - readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE); - notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE); - - // Device management - devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE); - deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE); - - // Session management - inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); - outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); - megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); - - // What rooms are encrypted - encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - [[maybe_unused]] auto verificationDb = getVerificationDb(txn); - [[maybe_unused]] auto userKeysDb = getUserKeysDb(txn); + auto txn = lmdb::txn::begin(env_); + syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE); + roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); + spacesChildrenDb_ = lmdb::dbi::open(txn, SPACES_CHILDREN_DB, MDB_CREATE | MDB_DUPSORT); + spacesParentsDb_ = lmdb::dbi::open(txn, SPACES_PARENTS_DB, MDB_CREATE | MDB_DUPSORT); + invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE); + readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE); + notificationsDb_ = lmdb::dbi::open(txn, NOTIFICATIONS_DB, MDB_CREATE); - txn.commit(); + // Device management + devicesDb_ = lmdb::dbi::open(txn, DEVICES_DB, MDB_CREATE); + deviceKeysDb_ = lmdb::dbi::open(txn, DEVICE_KEYS_DB, MDB_CREATE); - databaseReady_ = true; + // Session management + inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); + outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); + megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); + + // What rooms are encrypted + encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + [[maybe_unused]] auto verificationDb = getVerificationDb(txn); + [[maybe_unused]] auto userKeysDb = getUserKeysDb(txn); + + txn.commit(); + + databaseReady_ = true; } void Cache::setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) { - nhlog::db()->info("mark room {} as encrypted", room_id); + nhlog::db()->info("mark room {} as encrypted", room_id); - encryptedRooms_.put(txn, room_id, "0"); + encryptedRooms_.put(txn, room_id, "0"); } bool Cache::isRoomEncrypted(const std::string &room_id) { - std::string_view unused; + std::string_view unused; - auto txn = ro_txn(env_); - auto res = encryptedRooms_.get(txn, room_id, unused); + auto txn = ro_txn(env_); + auto res = encryptedRooms_.get(txn, room_id, unused); - return res; + return res; } std::optional<mtx::events::state::Encryption> Cache::roomEncryptionSettings(const std::string &room_id) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - try { - auto txn = ro_txn(env_); - auto statesdb = getStatesDb(txn, room_id); - std::string_view event; - bool res = - statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event); - - if (res) { - try { - StateEvent<Encryption> msg = json::parse(event); - - return msg.content; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.encryption event: {}", - e.what()); - return Encryption{}; - } - } - } catch (lmdb::error &) { + try { + auto txn = ro_txn(env_); + auto statesdb = getStatesDb(txn, room_id); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomEncryption), event); + + if (res) { + try { + StateEvent<Encryption> msg = json::parse(event); + + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.encryption event: {}", e.what()); + return Encryption{}; + } } + } catch (lmdb::error &) { + } - return std::nullopt; + return std::nullopt; } mtx::crypto::ExportedSessionKeys Cache::exportSessionKeys() { - using namespace mtx::crypto; + using namespace mtx::crypto; - ExportedSessionKeys keys; + ExportedSessionKeys keys; - auto txn = ro_txn(env_); - auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_); + auto txn = ro_txn(env_); + auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_); - std::string_view key, value; - while (cursor.get(key, value, MDB_NEXT)) { - ExportedSession exported; - MegolmSessionIndex index; + std::string_view key, value; + while (cursor.get(key, value, MDB_NEXT)) { + ExportedSession exported; + MegolmSessionIndex index; - auto saved_session = unpickle<InboundSessionObject>(std::string(value), SECRET); + auto saved_session = unpickle<InboundSessionObject>(std::string(value), pickle_secret_); - try { - index = nlohmann::json::parse(key).get<MegolmSessionIndex>(); - } catch (const nlohmann::json::exception &e) { - nhlog::db()->critical("failed to export megolm session: {}", e.what()); - continue; - } + try { + index = nlohmann::json::parse(key).get<MegolmSessionIndex>(); + } catch (const nlohmann::json::exception &e) { + nhlog::db()->critical("failed to export megolm session: {}", e.what()); + continue; + } - exported.room_id = index.room_id; - exported.sender_key = index.sender_key; - exported.session_id = index.session_id; - exported.session_key = export_session(saved_session.get(), -1); + exported.room_id = index.room_id; + exported.sender_key = index.sender_key; + exported.session_id = index.session_id; + exported.session_key = export_session(saved_session.get(), -1); - keys.sessions.push_back(exported); - } + keys.sessions.push_back(exported); + } - cursor.close(); + cursor.close(); - return keys; + return keys; } void Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) { - for (const auto &s : keys.sessions) { - MegolmSessionIndex index; - index.room_id = s.room_id; - index.session_id = s.session_id; - index.sender_key = s.sender_key; + for (const auto &s : keys.sessions) { + MegolmSessionIndex index; + index.room_id = s.room_id; + index.session_id = s.session_id; + index.sender_key = s.sender_key; - GroupSessionData data{}; - data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain; - if (s.sender_claimed_keys.count("ed25519")) - data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519"); + GroupSessionData data{}; + data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain; + if (s.sender_claimed_keys.count("ed25519")) + data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519"); - auto exported_session = mtx::crypto::import_session(s.session_key); + auto exported_session = mtx::crypto::import_session(s.session_key); - saveInboundMegolmSession(index, std::move(exported_session), data); - ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); - } + saveInboundMegolmSession(index, std::move(exported_session), data); + ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id); + } } // @@ -420,65 +425,64 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, mtx::crypto::InboundGroupSessionPtr session, const GroupSessionData &data) { - using namespace mtx::crypto; - const auto key = json(index).dump(); - const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET); + using namespace mtx::crypto; + const auto key = json(index).dump(); + const auto pickled = pickle<InboundSessionObject>(session.get(), pickle_secret_); - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - std::string_view value; - if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto oldSession = unpickle<InboundSessionObject>(std::string(value), SECRET); - if (olm_inbound_group_session_first_known_index(session.get()) > - olm_inbound_group_session_first_known_index(oldSession.get())) { - nhlog::crypto()->warn( - "Not storing inbound session with newer first known index"); - return; - } + std::string_view value; + if (inboundMegolmSessionDb_.get(txn, key, value)) { + auto oldSession = unpickle<InboundSessionObject>(std::string(value), pickle_secret_); + if (olm_inbound_group_session_first_known_index(session.get()) > + olm_inbound_group_session_first_known_index(oldSession.get())) { + nhlog::crypto()->warn("Not storing inbound session with newer first known index"); + return; } + } - inboundMegolmSessionDb_.put(txn, key, pickled); - megolmSessionDataDb_.put(txn, key, json(data).dump()); - txn.commit(); + inboundMegolmSessionDb_.put(txn, key, pickled); + megolmSessionDataDb_.put(txn, key, json(data).dump()); + txn.commit(); } mtx::crypto::InboundGroupSessionPtr Cache::getInboundMegolmSession(const MegolmSessionIndex &index) { - using namespace mtx::crypto; + using namespace mtx::crypto; - try { - auto txn = ro_txn(env_); - std::string key = json(index).dump(); - std::string_view value; + try { + auto txn = ro_txn(env_); + std::string key = json(index).dump(); + std::string_view value; - if (inboundMegolmSessionDb_.get(txn, key, value)) { - auto session = unpickle<InboundSessionObject>(std::string(value), SECRET); - return session; - } - } catch (std::exception &e) { - nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + if (inboundMegolmSessionDb_.get(txn, key, value)) { + auto session = unpickle<InboundSessionObject>(std::string(value), pickle_secret_); + return session; } + } catch (std::exception &e) { + nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + } - return nullptr; + return nullptr; } bool Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) { - using namespace mtx::crypto; + using namespace mtx::crypto; - try { - auto txn = ro_txn(env_); - std::string key = json(index).dump(); - std::string_view value; + try { + auto txn = ro_txn(env_); + std::string key = json(index).dump(); + std::string_view value; - return inboundMegolmSessionDb_.get(txn, key, value); - } catch (std::exception &e) { - nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); - } + return inboundMegolmSessionDb_.get(txn, key, value); + } catch (std::exception &e) { + nhlog::db()->error("Failed to get inbound megolm session {}", e.what()); + } - return false; + return false; } void @@ -486,42 +490,42 @@ Cache::updateOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &ptr) { - using namespace mtx::crypto; + using namespace mtx::crypto; - if (!outboundMegolmSessionExists(room_id)) - return; + if (!outboundMegolmSessionExists(room_id)) + return; - GroupSessionData data = data_; - data.message_index = olm_outbound_group_session_message_index(ptr.get()); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(ptr.get()); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(ptr.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ptr.get()); - // Save the updated pickled data for the session. - json j; - j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET); + // Save the updated pickled data for the session. + json j; + j["session"] = pickle<OutboundSessionObject>(ptr.get(), pickle_secret_); - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.put(txn, room_id, j.dump()); - megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); + txn.commit(); } void Cache::dropOutboundMegolmSession(const std::string &room_id) { - using namespace mtx::crypto; + using namespace mtx::crypto; - if (!outboundMegolmSessionExists(room_id)) - return; + if (!outboundMegolmSessionExists(room_id)) + return; - { - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.del(txn, room_id); - // don't delete session data, so that we can still share the session. - txn.commit(); - } + { + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.del(txn, room_id); + // don't delete session data, so that we can still share the session. + txn.commit(); + } } void @@ -529,86 +533,86 @@ Cache::saveOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data_, mtx::crypto::OutboundGroupSessionPtr &session) { - using namespace mtx::crypto; - const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET); + using namespace mtx::crypto; + const auto pickled = pickle<OutboundSessionObject>(session.get(), pickle_secret_); - GroupSessionData data = data_; - data.message_index = olm_outbound_group_session_message_index(session.get()); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(session.get()); + GroupSessionData data = data_; + data.message_index = olm_outbound_group_session_message_index(session.get()); + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(session.get()); - json j; - j["session"] = pickled; + json j; + j["session"] = pickled; - auto txn = lmdb::txn::begin(env_); - outboundMegolmSessionDb_.put(txn, room_id, j.dump()); - megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + outboundMegolmSessionDb_.put(txn, room_id, j.dump()); + megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump()); + txn.commit(); } bool Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept { - try { - auto txn = ro_txn(env_); - std::string_view value; - return outboundMegolmSessionDb_.get(txn, room_id, value); - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); - return false; - } + try { + auto txn = ro_txn(env_); + std::string_view value; + return outboundMegolmSessionDb_.get(txn, room_id, value); + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); + return false; + } } OutboundGroupSessionDataRef Cache::getOutboundMegolmSession(const std::string &room_id) { - try { - using namespace mtx::crypto; - - auto txn = ro_txn(env_); - std::string_view value; - outboundMegolmSessionDb_.get(txn, room_id, value); - auto obj = json::parse(value); + try { + using namespace mtx::crypto; - OutboundGroupSessionDataRef ref{}; - ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET); + auto txn = ro_txn(env_); + std::string_view value; + outboundMegolmSessionDb_.get(txn, room_id, value); + auto obj = json::parse(value); - MegolmSessionIndex index; - index.room_id = room_id; - index.sender_key = olm::client()->identity_keys().ed25519; - index.session_id = mtx::crypto::session_id(ref.session.get()); + OutboundGroupSessionDataRef ref{}; + ref.session = unpickle<OutboundSessionObject>(obj.at("session"), pickle_secret_); - if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { - ref.data = nlohmann::json::parse(value).get<GroupSessionData>(); - } + MegolmSessionIndex index; + index.room_id = room_id; + index.sender_key = olm::client()->identity_keys().ed25519; + index.session_id = mtx::crypto::session_id(ref.session.get()); - return ref; - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); - return {}; + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + ref.data = nlohmann::json::parse(value).get<GroupSessionData>(); } + + return ref; + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what()); + return {}; + } } std::optional<GroupSessionData> Cache::getMegolmSessionData(const MegolmSessionIndex &index) { - try { - using namespace mtx::crypto; - - auto txn = ro_txn(env_); + try { + using namespace mtx::crypto; - std::string_view value; - if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { - return nlohmann::json::parse(value).get<GroupSessionData>(); - } + auto txn = ro_txn(env_); - return std::nullopt; - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what()); - return std::nullopt; + std::string_view value; + if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) { + return nlohmann::json::parse(value).get<GroupSessionData>(); } + + return std::nullopt; + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what()); + return std::nullopt; + } } // // OLM sessions. @@ -619,287 +623,353 @@ Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session, uint64_t timestamp) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - const auto pickled = pickle<SessionObject>(session.get(), SECRET); - const auto session_id = mtx::crypto::session_id(session.get()); + const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_); + const auto session_id = mtx::crypto::session_id(session.get()); - StoredOlmSession stored_session; - stored_session.pickled_session = pickled; - stored_session.last_message_ts = timestamp; + StoredOlmSession stored_session; + stored_session.pickled_session = pickled; + stored_session.last_message_ts = timestamp; - db.put(txn, session_id, json(stored_session).dump()); + db.put(txn, session_id, json(stored_session).dump()); - txn.commit(); + txn.commit(); } std::optional<mtx::crypto::OlmSessionPtr> Cache::getOlmSession(const std::string &curve25519, const std::string &session_id) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view pickled; - bool found = db.get(txn, session_id, pickled); + std::string_view pickled; + bool found = db.get(txn, session_id, pickled); - txn.commit(); + txn.commit(); - if (found) { - auto data = json::parse(pickled).get<StoredOlmSession>(); - return unpickle<SessionObject>(data.pickled_session, SECRET); - } + if (found) { + auto data = json::parse(pickled).get<StoredOlmSession>(); + return unpickle<SessionObject>(data.pickled_session, pickle_secret_); + } - return std::nullopt; + return std::nullopt; } std::optional<mtx::crypto::OlmSessionPtr> Cache::getLatestOlmSession(const std::string &curve25519) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view session_id, pickled_session; + std::string_view session_id, pickled_session; - std::optional<StoredOlmSession> currentNewest; + std::optional<StoredOlmSession> currentNewest; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, pickled_session, MDB_NEXT)) { - auto data = json::parse(pickled_session).get<StoredOlmSession>(); - if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) - currentNewest = data; - } - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, pickled_session, MDB_NEXT)) { + auto data = json::parse(pickled_session).get<StoredOlmSession>(); + if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) + currentNewest = data; + } + cursor.close(); - txn.commit(); + txn.commit(); - return currentNewest - ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session, SECRET)) - : std::nullopt; + return currentNewest ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session, + pickle_secret_)) + : std::nullopt; } std::vector<std::string> Cache::getOlmSessions(const std::string &curve25519) { - using namespace mtx::crypto; + using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + auto txn = lmdb::txn::begin(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view session_id, unused; - std::vector<std::string> res; + std::string_view session_id, unused; + std::vector<std::string> res; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, unused, MDB_NEXT)) - res.emplace_back(session_id); - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, unused, MDB_NEXT)) + res.emplace_back(session_id); + cursor.close(); - txn.commit(); + txn.commit(); - return res; + return res; } void Cache::saveOlmAccount(const std::string &data) { - auto txn = lmdb::txn::begin(env_); - syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + syncStateDb_.put(txn, OLM_ACCOUNT_KEY, data); + txn.commit(); } std::string Cache::restoreOlmAccount() { + auto txn = ro_txn(env_); + + std::string_view pickled; + syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled); + + return std::string(pickled.data(), pickled.size()); +} + +void +Cache::saveBackupVersion(const OnlineBackupVersion &data) +{ + auto txn = lmdb::txn::begin(env_); + syncStateDb_.put(txn, CURRENT_ONLINE_BACKUP_VERSION, nlohmann::json(data).dump()); + txn.commit(); +} + +void +Cache::deleteBackupVersion() +{ + auto txn = lmdb::txn::begin(env_); + syncStateDb_.del(txn, CURRENT_ONLINE_BACKUP_VERSION); + txn.commit(); +} + +std::optional<OnlineBackupVersion> +Cache::backupVersion() +{ + try { auto txn = ro_txn(env_); - std::string_view pickled; - syncStateDb_.get(txn, OLM_ACCOUNT_KEY, pickled); + std::string_view v; + syncStateDb_.get(txn, CURRENT_ONLINE_BACKUP_VERSION, v); + + return nlohmann::json::parse(v).get<OnlineBackupVersion>(); + } catch (...) { + return std::nullopt; + } +} + +static void +fatalSecretError() +{ + QMessageBox::critical( + ChatPage::instance(), + QCoreApplication::translate("SecretStorage", "Failed to connect to secret storage"), + QCoreApplication::translate( + "SecretStorage", + "Nheko could not connect to the secure storage to save encryption secrets to. This can " + "have multiple reasons. Check if your D-Bus service is running and you have configured a " + "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If " + "you are having trouble, feel free to open an issue here: " + "https://github.com/Nheko-Reborn/nheko/issues")); - return std::string(pickled.data(), pickled.size()); + QCoreApplication::exit(1); + exit(1); } void -Cache::storeSecret(const std::string name, const std::string secret) -{ - auto settings = UserSettings::instance(); - auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); - job->setAutoDelete(true); - job->setInsecureFallback(true); - job->setSettings(UserSettings::instance()->qsettings()); - - job->setKey( - "matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - job->setTextData(QString::fromStdString(secret)); - - QObject::connect( - job, - &QKeychain::WritePasswordJob::finished, - this, - [name, this](QKeychain::Job *job) { - if (job->error()) { - nhlog::db()->warn("Storing secret '{}' failed: {}", - name, - job->errorString().toStdString()); - } else { - // if we emit the signal directly, qtkeychain breaks and won't execute new - // jobs. You can't start a job from the finish signal of a job. - QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); - nhlog::db()->info("Storing secret '{}' successful", name); - } - }, - Qt::ConnectionType::DirectConnection); - job->start(); +Cache::storeSecret(const std::string name, const std::string secret, bool internal) +{ + auto settings = UserSettings::instance(); + auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); + job->setAutoDelete(true); + job->setInsecureFallback(true); + job->setSettings(UserSettings::instance()->qsettings()); + + job->setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); + + job->setTextData(QString::fromStdString(secret)); + + QObject::connect( + job, + &QKeychain::WritePasswordJob::finished, + this, + [name, this](QKeychain::Job *job) { + if (job->error()) { + nhlog::db()->warn( + "Storing secret '{}' failed: {}", name, job->errorString().toStdString()); + fatalSecretError(); + } else { + // if we emit the signal directly, qtkeychain breaks and won't execute new + // jobs. You can't start a job from the finish signal of a job. + QTimer::singleShot(100, [this, name] { emit secretChanged(name); }); + nhlog::db()->info("Storing secret '{}' successful", name); + } + }, + Qt::ConnectionType::DirectConnection); + job->start(); } void -Cache::deleteSecret(const std::string name) +Cache::deleteSecret(const std::string name, bool internal) { - auto settings = UserSettings::instance(); - QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); + auto settings = UserSettings::instance(); + QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); + job.setAutoDelete(false); + job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); - job.setKey( - "matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); + job.setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! + QEventLoop loop; + job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); - emit secretChanged(name); + emit secretChanged(name); } std::optional<std::string> -Cache::secret(const std::string name) -{ - auto settings = UserSettings::instance(); - QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setSettings(UserSettings::instance()->qsettings()); - - job.setKey( - "matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) - .toBase64()) + - "." + QString::fromStdString(name)); - - // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean - // time! - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); - - const QString secret = job.textData(); - if (job.error()) { - nhlog::db()->debug( - "Restoring secret '{}' failed: {}", name, job.errorString().toStdString()); - return std::nullopt; - } - if (secret.isEmpty()) { - nhlog::db()->debug("Restored empty secret '{}'.", name); - return std::nullopt; +Cache::secret(const std::string name, bool internal) +{ + auto settings = UserSettings::instance(); + QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); + job.setAutoDelete(false); + job.setInsecureFallback(true); + job.setSettings(UserSettings::instance()->qsettings()); + + job.setKey( + (internal ? "nheko." : "matrix.") + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256) + .toBase64()) + + "." + QString::fromStdString(name)); + + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! + QEventLoop loop; + job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); + job.start(); + loop.exec(); + + const QString secret = job.textData(); + if (job.error()) { + if (job.error() == QKeychain::Error::EntryNotFound) + return std::nullopt; + nhlog::db()->error("Restoring secret '{}' failed ({}): {}", + name, + job.error(), + job.errorString().toStdString()); + + fatalSecretError(); + return std::nullopt; + } + if (secret.isEmpty()) { + nhlog::db()->debug("Restored empty secret '{}'.", name); + return std::nullopt; + } + + return secret.toStdString(); +} + +std::string +Cache::pickleSecret() +{ + if (pickle_secret_.empty()) { + auto s = secret("pickle_secret", true); + if (!s) { + this->pickle_secret_ = mtx::client::utils::random_token(64, true); + storeSecret("pickle_secret", pickle_secret_, true); + } else { + this->pickle_secret_ = *s; } + } - return secret.toStdString(); + return pickle_secret_; } void Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) { - invitesDb_.del(txn, room_id); - getInviteStatesDb(txn, room_id).drop(txn, true); - getInviteMembersDb(txn, room_id).drop(txn, true); + invitesDb_.del(txn, room_id); + getInviteStatesDb(txn, room_id).drop(txn, true); + getInviteMembersDb(txn, room_id).drop(txn, true); } void Cache::removeInvite(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - removeInvite(txn, room_id); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + removeInvite(txn, room_id); + txn.commit(); } void Cache::removeRoom(lmdb::txn &txn, const std::string &roomid) { - roomsDb_.del(txn, roomid); - getStatesDb(txn, roomid).drop(txn, true); - getAccountDataDb(txn, roomid).drop(txn, true); - getMembersDb(txn, roomid).drop(txn, true); + roomsDb_.del(txn, roomid); + getStatesDb(txn, roomid).drop(txn, true); + getAccountDataDb(txn, roomid).drop(txn, true); + getMembersDb(txn, roomid).drop(txn, true); } void Cache::removeRoom(const std::string &roomid) { - auto txn = lmdb::txn::begin(env_, nullptr, 0); - roomsDb_.del(txn, roomid); - txn.commit(); + auto txn = lmdb::txn::begin(env_, nullptr, 0); + roomsDb_.del(txn, roomid); + txn.commit(); } void Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token) { - syncStateDb_.put(txn, NEXT_BATCH_KEY, token); -} - -void -Cache::setNextBatchToken(lmdb::txn &txn, const QString &token) -{ - setNextBatchToken(txn, token.toStdString()); + syncStateDb_.put(txn, NEXT_BATCH_KEY, token); } bool Cache::isInitialized() { - if (!env_.handle()) - return false; + if (!env_.handle()) + return false; - auto txn = ro_txn(env_); - std::string_view token; + auto txn = ro_txn(env_); + std::string_view token; - bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); + bool res = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); - return res; + return res; } std::string Cache::nextBatchToken() { - if (!env_.handle()) - throw lmdb::error("Env already closed", MDB_INVALID); + if (!env_.handle()) + throw lmdb::error("Env already closed", MDB_INVALID); - auto txn = ro_txn(env_); - std::string_view token; + auto txn = ro_txn(env_); + std::string_view token; - bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); + bool result = syncStateDb_.get(txn, NEXT_BATCH_KEY, token); - if (result) - return std::string(token.data(), token.size()); - else - return ""; + if (result) + return std::string(token.data(), token.size()); + else + return ""; } void Cache::deleteData() { + if (this->databaseReady_) { this->databaseReady_ = false; // TODO: We need to remove the env_ while not accepting new requests. lmdb::dbi_close(env_, syncStateDb_); @@ -920,596 +990,607 @@ Cache::deleteData() verification_storage.status.clear(); if (!cacheDirectory_.isEmpty()) { - QDir(cacheDirectory_).removeRecursively(); - nhlog::db()->info("deleted cache files from disk"); + QDir(cacheDirectory_).removeRecursively(); + nhlog::db()->info("deleted cache files from disk"); } deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); deleteSecret(mtx::secret_storage::secrets::cross_signing_master); deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); + deleteSecret("pickle_secret", true); + } } //! migrates db to the current format bool Cache::runMigrations() { - std::string stored_version; - { - auto txn = ro_txn(env_); - - std::string_view current_version; - bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - - if (!res) - return false; - - stored_version = std::string(current_version); - } + std::string stored_version; + { + auto txn = ro_txn(env_); - std::vector<std::pair<std::string, std::function<bool()>>> migrations{ - {"2020.05.01", - [this]() { - try { - auto txn = lmdb::txn::begin(env_, nullptr); - auto pending_receipts = - lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); - lmdb::dbi_drop(txn, pending_receipts, true); - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical( - "Failed to delete pending_receipts database in migration!"); - return false; - } + std::string_view current_version; + bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - nhlog::db()->info("Successfully deleted pending receipts database."); - return true; - }}, - {"2020.07.05", - [this]() { + if (!res) + return false; + + stored_version = std::string(current_version); + } + + std::vector<std::pair<std::string, std::function<bool()>>> migrations{ + {"2020.05.01", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto pending_receipts = lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE); + lmdb::dbi_drop(txn, pending_receipts, true); + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to delete pending_receipts database in migration!"); + return false; + } + + nhlog::db()->info("Successfully deleted pending receipts database."); + return true; + }}, + {"2020.07.05", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto room_ids = getRoomIds(txn); + + for (const auto &room_id : room_ids) { try { - auto txn = lmdb::txn::begin(env_, nullptr); - auto room_ids = getRoomIds(txn); - - for (const auto &room_id : room_ids) { - try { - auto messagesDb = lmdb::dbi::open( - txn, std::string(room_id + "/messages").c_str()); - - // keep some old messages and batch token - { - auto roomsCursor = - lmdb::cursor::open(txn, messagesDb); - std::string_view ts, stored_message; - bool start = true; - mtx::responses::Timeline oldMessages; - while (roomsCursor.get(ts, - stored_message, - start ? MDB_FIRST - : MDB_NEXT)) { - start = false; - - auto j = json::parse(std::string_view( - stored_message.data(), - stored_message.size())); - - if (oldMessages.prev_batch.empty()) - oldMessages.prev_batch = - j["token"].get<std::string>(); - else if (j["token"] != - oldMessages.prev_batch) - break; - - mtx::events::collections::TimelineEvent - te; - mtx::events::collections::from_json( - j["event"], te); - oldMessages.events.push_back(te.data); - } - // messages were stored in reverse order, so we - // need to reverse them - std::reverse(oldMessages.events.begin(), - oldMessages.events.end()); - // save messages using the new method - auto eventsDb = getEventsDb(txn, room_id); - saveTimelineMessages( - txn, eventsDb, room_id, oldMessages); - } - - // delete old messages db - lmdb::dbi_drop(txn, messagesDb, true); - } catch (std::exception &e) { - nhlog::db()->error( - "While migrating messages from {}, ignoring error {}", - room_id, - e.what()); - } + auto messagesDb = + lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str()); + + // keep some old messages and batch token + { + auto roomsCursor = lmdb::cursor::open(txn, messagesDb); + std::string_view ts, stored_message; + bool start = true; + mtx::responses::Timeline oldMessages; + while ( + roomsCursor.get(ts, stored_message, start ? MDB_FIRST : MDB_NEXT)) { + start = false; + + auto j = json::parse( + std::string_view(stored_message.data(), stored_message.size())); + + if (oldMessages.prev_batch.empty()) + oldMessages.prev_batch = j["token"].get<std::string>(); + else if (j["token"] != oldMessages.prev_batch) + break; + + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(j["event"], te); + oldMessages.events.push_back(te.data); } - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical( - "Failed to delete messages database in migration!"); - return false; + // messages were stored in reverse order, so we + // need to reverse them + std::reverse(oldMessages.events.begin(), oldMessages.events.end()); + // save messages using the new method + auto eventsDb = getEventsDb(txn, room_id); + saveTimelineMessages(txn, eventsDb, room_id, oldMessages); + } + + // delete old messages db + lmdb::dbi_drop(txn, messagesDb, true); + } catch (std::exception &e) { + nhlog::db()->error( + "While migrating messages from {}, ignoring error {}", room_id, e.what()); } + } + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to delete messages database in migration!"); + return false; + } + + nhlog::db()->info("Successfully deleted pending receipts database."); + return true; + }}, + {"2020.10.20", + [this]() { + try { + using namespace mtx::crypto; + + auto txn = lmdb::txn::begin(env_); + + auto mainDb = lmdb::dbi::open(txn, nullptr); + + std::string_view dbName, ignored; + auto olmDbCursor = lmdb::cursor::open(txn, mainDb); + while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) { + // skip every db but olm session dbs + nhlog::db()->debug("Db {}", dbName); + if (dbName.find("olm_sessions/") != 0) + continue; + + nhlog::db()->debug("Migrating {}", dbName); + + auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str()); + + std::string_view session_id, session_value; + + std::vector<std::pair<std::string, StoredOlmSession>> sessions; + + auto cursor = lmdb::cursor::open(txn, olmDb); + while (cursor.get(session_id, session_value, MDB_NEXT)) { + nhlog::db()->debug( + "session_id {}, session_value {}", session_id, session_value); + StoredOlmSession session; + bool invalid = false; + for (auto c : session_value) + if (!isprint(c)) { + invalid = true; + break; + } + if (invalid) + continue; - nhlog::db()->info("Successfully deleted pending receipts database."); - return true; - }}, - {"2020.10.20", - [this]() { - try { - using namespace mtx::crypto; - - auto txn = lmdb::txn::begin(env_); - - auto mainDb = lmdb::dbi::open(txn, nullptr); - - std::string_view dbName, ignored; - auto olmDbCursor = lmdb::cursor::open(txn, mainDb); - while (olmDbCursor.get(dbName, ignored, MDB_NEXT)) { - // skip every db but olm session dbs - nhlog::db()->debug("Db {}", dbName); - if (dbName.find("olm_sessions/") != 0) - continue; - - nhlog::db()->debug("Migrating {}", dbName); - - auto olmDb = lmdb::dbi::open(txn, std::string(dbName).c_str()); - - std::string_view session_id, session_value; - - std::vector<std::pair<std::string, StoredOlmSession>> sessions; - - auto cursor = lmdb::cursor::open(txn, olmDb); - while (cursor.get(session_id, session_value, MDB_NEXT)) { - nhlog::db()->debug("session_id {}, session_value {}", - session_id, - session_value); - StoredOlmSession session; - bool invalid = false; - for (auto c : session_value) - if (!isprint(c)) { - invalid = true; - break; - } - if (invalid) - continue; - - nhlog::db()->debug("Not skipped"); - - session.pickled_session = session_value; - sessions.emplace_back(session_id, session); - } - cursor.close(); + nhlog::db()->debug("Not skipped"); - olmDb.drop(txn, true); + session.pickled_session = session_value; + sessions.emplace_back(session_id, session); + } + cursor.close(); - auto newDbName = std::string(dbName); - newDbName.erase(0, sizeof("olm_sessions") - 1); - newDbName = "olm_sessions.v2" + newDbName; + olmDb.drop(txn, true); - auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE); + auto newDbName = std::string(dbName); + newDbName.erase(0, sizeof("olm_sessions") - 1); + newDbName = "olm_sessions.v2" + newDbName; - for (const auto &[key, value] : sessions) { - // nhlog::db()->debug("{}\n{}", key, json(value).dump()); - newDb.put(txn, key, json(value).dump()); - } - } - olmDbCursor.close(); + auto newDb = lmdb::dbi::open(txn, newDbName.c_str(), MDB_CREATE); - txn.commit(); - } catch (const lmdb::error &) { - nhlog::db()->critical("Failed to migrate olm sessions,"); - return false; + for (const auto &[key, value] : sessions) { + // nhlog::db()->debug("{}\n{}", key, json(value).dump()); + newDb.put(txn, key, json(value).dump()); } + } + olmDbCursor.close(); + + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to migrate olm sessions,"); + return false; + } + + nhlog::db()->info("Successfully migrated olm sessions."); + return true; + }}, + {"2021.08.22", + [this]() { + try { + auto txn = lmdb::txn::begin(env_, nullptr); + auto try_drop = [&txn](const std::string &dbName) { + try { + auto db = lmdb::dbi::open(txn, dbName.c_str()); + db.drop(txn, true); + } catch (std::exception &e) { + nhlog::db()->warn("Failed to drop '{}': {}", dbName, e.what()); + } + }; + + auto room_ids = getRoomIds(txn); + + for (const auto &room : room_ids) { + try_drop(room + "/state"); + try_drop(room + "/state_by_key"); + try_drop(room + "/account_data"); + try_drop(room + "/members"); + try_drop(room + "/mentions"); + try_drop(room + "/events"); + try_drop(room + "/event_order"); + try_drop(room + "/event2order"); + try_drop(room + "/msg2order"); + try_drop(room + "/order2msg"); + try_drop(room + "/pending"); + try_drop(room + "/related"); + } + + // clear db, don't delete + roomsDb_.drop(txn, false); + setNextBatchToken(txn, ""); + + txn.commit(); + } catch (const lmdb::error &) { + nhlog::db()->critical("Failed to clear cache!"); + return false; + } + + nhlog::db()->info("Successfully cleared the cache. Will do a clean sync after startup."); + return true; + }}, + {"2021.08.31", + [this]() { + storeSecret("pickle_secret", "secret", true); + return true; + }}, + }; + + nhlog::db()->info("Running migrations, this may take a while!"); + for (const auto &[target_version, migration] : migrations) { + if (target_version > stored_version) + if (!migration()) { + nhlog::db()->critical("migration failure!"); + return false; + } + } + nhlog::db()->info("Migrations finished."); - nhlog::db()->info("Successfully migrated olm sessions."); - return true; - }}, - }; - - nhlog::db()->info("Running migrations, this may take a while!"); - for (const auto &[target_version, migration] : migrations) { - if (target_version > stored_version) - if (!migration()) { - nhlog::db()->critical("migration failure!"); - return false; - } - } - nhlog::db()->info("Migrations finished."); - - setCurrentFormat(); - return true; + setCurrentFormat(); + return true; } cache::CacheVersion Cache::formatVersion() { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view current_version; - bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); + std::string_view current_version; + bool res = syncStateDb_.get(txn, CACHE_FORMAT_VERSION_KEY, current_version); - if (!res) - return cache::CacheVersion::Older; + if (!res) + return cache::CacheVersion::Older; - std::string stored_version(current_version.data(), current_version.size()); + std::string stored_version(current_version.data(), current_version.size()); - if (stored_version < CURRENT_CACHE_FORMAT_VERSION) - return cache::CacheVersion::Older; - else if (stored_version > CURRENT_CACHE_FORMAT_VERSION) - return cache::CacheVersion::Older; - else - return cache::CacheVersion::Current; + if (stored_version < CURRENT_CACHE_FORMAT_VERSION) + return cache::CacheVersion::Older; + else if (stored_version > CURRENT_CACHE_FORMAT_VERSION) + return cache::CacheVersion::Older; + else + return cache::CacheVersion::Current; } void Cache::setCurrentFormat() { - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION); + syncStateDb_.put(txn, CACHE_FORMAT_VERSION_KEY, CURRENT_CACHE_FORMAT_VERSION); - txn.commit(); + txn.commit(); } CachedReceipts Cache::readReceipts(const QString &event_id, const QString &room_id) { - CachedReceipts receipts; + CachedReceipts receipts; - ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()}; - nlohmann::json json_key = receipt_key; + ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()}; + nlohmann::json json_key = receipt_key; - try { - auto txn = ro_txn(env_); - auto key = json_key.dump(); - - std::string_view value; + try { + auto txn = ro_txn(env_); + auto key = json_key.dump(); - bool res = readReceiptsDb_.get(txn, key, value); + std::string_view value; - if (res) { - auto json_response = - json::parse(std::string_view(value.data(), value.size())); - auto values = json_response.get<std::map<std::string, uint64_t>>(); + bool res = readReceiptsDb_.get(txn, key, value); - for (const auto &v : values) - // timestamp, user_id - receipts.emplace(v.second, v.first); - } + if (res) { + auto json_response = json::parse(std::string_view(value.data(), value.size())); + auto values = json_response.get<std::map<std::string, uint64_t>>(); - } catch (const lmdb::error &e) { - nhlog::db()->critical("readReceipts: {}", e.what()); + for (const auto &v : values) + // timestamp, user_id + receipts.emplace(v.second, v.first); } - return receipts; + } catch (const lmdb::error &e) { + nhlog::db()->critical("readReceipts: {}", e.what()); + } + + return receipts; } void Cache::updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts) { - auto user_id = this->localUserId_.toStdString(); - for (const auto &receipt : receipts) { - const auto event_id = receipt.first; - auto event_receipts = receipt.second; + auto user_id = this->localUserId_.toStdString(); + for (const auto &receipt : receipts) { + const auto event_id = receipt.first; + auto event_receipts = receipt.second; - ReadReceiptKey receipt_key{event_id, room_id}; - nlohmann::json json_key = receipt_key; + ReadReceiptKey receipt_key{event_id, room_id}; + nlohmann::json json_key = receipt_key; - try { - const auto key = json_key.dump(); + try { + const auto key = json_key.dump(); - std::string_view prev_value; + std::string_view prev_value; - bool exists = readReceiptsDb_.get(txn, key, prev_value); + bool exists = readReceiptsDb_.get(txn, key, prev_value); - std::map<std::string, uint64_t> saved_receipts; + std::map<std::string, uint64_t> saved_receipts; - // If an entry for the event id already exists, we would - // merge the existing receipts with the new ones. - if (exists) { - auto json_value = json::parse( - std::string_view(prev_value.data(), prev_value.size())); + // If an entry for the event id already exists, we would + // merge the existing receipts with the new ones. + if (exists) { + auto json_value = + json::parse(std::string_view(prev_value.data(), prev_value.size())); - // Retrieve the saved receipts. - saved_receipts = json_value.get<std::map<std::string, uint64_t>>(); - } + // Retrieve the saved receipts. + saved_receipts = json_value.get<std::map<std::string, uint64_t>>(); + } - // Append the new ones. - for (const auto &[read_by, timestamp] : event_receipts) { - if (read_by == user_id) { - emit removeNotification(QString::fromStdString(room_id), - QString::fromStdString(event_id)); - } - saved_receipts.emplace(read_by, timestamp); - } + // Append the new ones. + for (const auto &[read_by, timestamp] : event_receipts) { + if (read_by == user_id) { + emit removeNotification(QString::fromStdString(room_id), + QString::fromStdString(event_id)); + } + saved_receipts.emplace(read_by, timestamp); + } - // Save back the merged (or only the new) receipts. - nlohmann::json json_updated_value = saved_receipts; - std::string merged_receipts = json_updated_value.dump(); + // Save back the merged (or only the new) receipts. + nlohmann::json json_updated_value = saved_receipts; + std::string merged_receipts = json_updated_value.dump(); - readReceiptsDb_.put(txn, key, merged_receipts); + readReceiptsDb_.put(txn, key, merged_receipts); - } catch (const lmdb::error &e) { - nhlog::db()->critical("updateReadReceipts: {}", e.what()); - } + } catch (const lmdb::error &e) { + nhlog::db()->critical("updateReadReceipts: {}", e.what()); } + } } void Cache::calculateRoomReadStatus() { - const auto joined_rooms = joinedRooms(); + const auto joined_rooms = joinedRooms(); - std::map<QString, bool> readStatus; + std::map<QString, bool> readStatus; - for (const auto &room : joined_rooms) - readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room)); + for (const auto &room : joined_rooms) + readStatus.emplace(QString::fromStdString(room), calculateRoomReadStatus(room)); - emit roomReadStatus(readStatus); + emit roomReadStatus(readStatus); } bool Cache::calculateRoomReadStatus(const std::string &room_id) { - std::string last_event_id_, fullyReadEventId_; - { - auto txn = ro_txn(env_); - - // Get last event id on the room. - const auto last_event_id = getLastEventId(txn, room_id); - const auto localUser = utils::localUser().toStdString(); - - std::string fullyReadEventId; - if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) { - if (auto fr = std::get_if< - mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>( - &ev.value())) { - fullyReadEventId = fr->content.event_id; - } - } - - if (last_event_id.empty() || fullyReadEventId.empty()) - return true; + std::string last_event_id_, fullyReadEventId_; + { + auto txn = ro_txn(env_); - if (last_event_id == fullyReadEventId) - return false; + // Get last event id on the room. + const auto last_event_id = getLastEventId(txn, room_id); + const auto localUser = utils::localUser().toStdString(); - last_event_id_ = std::string(last_event_id); - fullyReadEventId_ = std::string(fullyReadEventId); + std::string fullyReadEventId; + if (auto ev = getAccountData(txn, mtx::events::EventType::FullyRead, room_id)) { + if (auto fr = + std::get_if<mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>( + &ev.value())) { + fullyReadEventId = fr->content.event_id; + } } - // Retrieve all read receipts for that event. - return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_); + if (last_event_id.empty() || fullyReadEventId.empty()) + return true; + + if (last_event_id == fullyReadEventId) + return false; + + last_event_id_ = std::string(last_event_id); + fullyReadEventId_ = std::string(fullyReadEventId); + } + + // Retrieve all read receipts for that event. + return getEventIndex(room_id, last_event_id_) > getEventIndex(room_id, fullyReadEventId_); } void Cache::saveState(const mtx::responses::Sync &res) { - using namespace mtx::events; - auto local_user_id = this->localUserId_.toStdString(); + using namespace mtx::events; + auto local_user_id = this->localUserId_.toStdString(); - auto currentBatchToken = nextBatchToken(); + auto currentBatchToken = nextBatchToken(); - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - setNextBatchToken(txn, res.next_batch); - - if (!res.account_data.events.empty()) { - auto accountDataDb = getAccountDataDb(txn, ""); - for (const auto &ev : res.account_data.events) - std::visit( - [&txn, &accountDataDb](const auto &event) { - auto j = json(event); - accountDataDb.put(txn, j["type"].get<std::string>(), j.dump()); - }, - ev); - } + setNextBatchToken(txn, res.next_batch); - auto userKeyCacheDb = getUserKeysDb(txn); - - std::set<std::string> spaces_with_updates; - std::set<std::string> rooms_with_space_updates; - - // Save joined rooms - for (const auto &room : res.rooms.join) { - auto statesdb = getStatesDb(txn, room.first); - auto stateskeydb = getStatesKeyDb(txn, room.first); - auto membersdb = getMembersDb(txn, room.first); - auto eventsDb = getEventsDb(txn, room.first); - - saveStateEvents(txn, - statesdb, - stateskeydb, - membersdb, - eventsDb, - room.first, - room.second.state.events); - saveStateEvents(txn, - statesdb, - stateskeydb, - membersdb, - eventsDb, - room.first, - room.second.timeline.events); - - saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); - - RoomInfo updatedInfo; - updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); - updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); - updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); - updatedInfo.version = getRoomVersion(txn, statesdb).toStdString(); - updatedInfo.is_space = getRoomIsSpace(txn, statesdb); - - if (updatedInfo.is_space) { - bool space_updates = false; - for (const auto &e : room.second.state.events) - if (std::holds_alternative<StateEvent<state::space::Child>>(e) || - std::holds_alternative<StateEvent<state::PowerLevels>>(e)) - space_updates = true; - for (const auto &e : room.second.timeline.events) - if (std::holds_alternative<StateEvent<state::space::Child>>(e) || - std::holds_alternative<StateEvent<state::PowerLevels>>(e)) - space_updates = true; - - if (space_updates) - spaces_with_updates.insert(room.first); - } + if (!res.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, ""); + for (const auto &ev : res.account_data.events) + std::visit( + [&txn, &accountDataDb](const auto &event) { + auto j = json(event); + accountDataDb.put(txn, j["type"].get<std::string>(), j.dump()); + }, + ev); + } - { - bool room_has_space_update = false; - for (const auto &e : room.second.state.events) { - if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) { - spaces_with_updates.insert(se->state_key); - room_has_space_update = true; - } - } - for (const auto &e : room.second.timeline.events) { - if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) { - spaces_with_updates.insert(se->state_key); - room_has_space_update = true; - } - } + auto userKeyCacheDb = getUserKeysDb(txn); - if (room_has_space_update) - rooms_with_space_updates.insert(room.first); - } + std::set<std::string> spaces_with_updates; + std::set<std::string> rooms_with_space_updates; - bool has_new_tags = false; - // Process the account_data associated with this room - if (!room.second.account_data.events.empty()) { - auto accountDataDb = getAccountDataDb(txn, room.first); - - for (const auto &evt : room.second.account_data.events) { - std::visit( - [&txn, &accountDataDb](const auto &event) { - auto j = json(event); - accountDataDb.put( - txn, j["type"].get<std::string>(), j.dump()); - }, - evt); - - // for tag events - if (std::holds_alternative<AccountDataEvent<account_data::Tags>>( - evt)) { - auto tags_evt = - std::get<AccountDataEvent<account_data::Tags>>(evt); - has_new_tags = true; - for (const auto &tag : tags_evt.content.tags) { - updatedInfo.tags.push_back(tag.first); - } - } - if (auto fr = std::get_if<mtx::events::AccountDataEvent< - mtx::events::account_data::FullyRead>>(&evt)) { - nhlog::db()->debug("Fully read: {}", fr->content.event_id); - } - } + // Save joined rooms + for (const auto &room : res.rooms.join) { + auto statesdb = getStatesDb(txn, room.first); + auto stateskeydb = getStatesKeyDb(txn, room.first); + auto membersdb = getMembersDb(txn, room.first); + auto eventsDb = getEventsDb(txn, room.first); + + saveStateEvents( + txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.state.events); + saveStateEvents( + txn, statesdb, stateskeydb, membersdb, eventsDb, room.first, room.second.timeline.events); + + saveTimelineMessages(txn, eventsDb, room.first, room.second.timeline); + + RoomInfo updatedInfo; + updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString(); + updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString(); + updatedInfo.avatar_url = getRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); + updatedInfo.version = getRoomVersion(txn, statesdb).toStdString(); + updatedInfo.is_space = getRoomIsSpace(txn, statesdb); + + if (updatedInfo.is_space) { + bool space_updates = false; + for (const auto &e : room.second.state.events) + if (std::holds_alternative<StateEvent<state::space::Child>>(e) || + std::holds_alternative<StateEvent<state::PowerLevels>>(e)) + space_updates = true; + for (const auto &e : room.second.timeline.events) + if (std::holds_alternative<StateEvent<state::space::Child>>(e) || + std::holds_alternative<StateEvent<state::PowerLevels>>(e)) + space_updates = true; + + if (space_updates) + spaces_with_updates.insert(room.first); + } + + { + bool room_has_space_update = false; + for (const auto &e : room.second.state.events) { + if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) { + spaces_with_updates.insert(se->state_key); + room_has_space_update = true; } - if (!has_new_tags) { - // retrieve the old tags, they haven't changed - std::string_view data; - if (roomsDb_.get(txn, room.first, data)) { - try { - RoomInfo tmp = - json::parse(std::string_view(data.data(), data.size())); - updatedInfo.tags = tmp.tags; - } catch (const json::exception &e) { - nhlog::db()->warn( - "failed to parse room info: room_id ({}), {}: {}", - room.first, - std::string(data.data(), data.size()), - e.what()); - } - } + } + for (const auto &e : room.second.timeline.events) { + if (auto se = std::get_if<StateEvent<state::space::Parent>>(&e)) { + spaces_with_updates.insert(se->state_key); + room_has_space_update = true; + } + } + + if (room_has_space_update) + rooms_with_space_updates.insert(room.first); + } + + bool has_new_tags = false; + // Process the account_data associated with this room + if (!room.second.account_data.events.empty()) { + auto accountDataDb = getAccountDataDb(txn, room.first); + + for (const auto &evt : room.second.account_data.events) { + std::visit( + [&txn, &accountDataDb](const auto &event) { + auto j = json(event); + accountDataDb.put(txn, j["type"].get<std::string>(), j.dump()); + }, + evt); + + // for tag events + if (std::holds_alternative<AccountDataEvent<account_data::Tags>>(evt)) { + auto tags_evt = std::get<AccountDataEvent<account_data::Tags>>(evt); + has_new_tags = true; + for (const auto &tag : tags_evt.content.tags) { + updatedInfo.tags.push_back(tag.first); + } + } + if (auto fr = std::get_if< + mtx::events::AccountDataEvent<mtx::events::account_data::FullyRead>>(&evt)) { + nhlog::db()->debug("Fully read: {}", fr->content.event_id); + } + } + } + if (!has_new_tags) { + // retrieve the old tags, they haven't changed + std::string_view data; + if (roomsDb_.get(txn, room.first, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data.data(), data.size())); + updatedInfo.tags = tmp.tags; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room.first, + std::string(data.data(), data.size()), + e.what()); } + } + } - roomsDb_.put(txn, room.first, json(updatedInfo).dump()); + roomsDb_.put(txn, room.first, json(updatedInfo).dump()); - for (const auto &e : room.second.ephemeral.events) { - if (auto receiptsEv = std::get_if< - mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(&e)) { - Receipts receipts; + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = + std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(&e)) { + Receipts receipts; - for (const auto &[event_id, userReceipts] : - receiptsEv->content.receipts) { - for (const auto &[user_id, receipt] : userReceipts.users) { - receipts[event_id][user_id] = receipt.ts; - } - } - updateReadReceipt(txn, room.first, receipts); - } + for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + receipts[event_id][user_id] = receipt.ts; + } } - - // Clean up non-valid invites. - removeInvite(txn, room.first); + updateReadReceipt(txn, room.first, receipts); + } } - saveInvites(txn, res.rooms.invite); + // Clean up non-valid invites. + removeInvite(txn, room.first); + } - savePresence(txn, res.presence); + saveInvites(txn, res.rooms.invite); - markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); - deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left); + savePresence(txn, res.presence); - removeLeftRooms(txn, res.rooms.leave); + markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken); - updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates)); + removeLeftRooms(txn, res.rooms.leave); - txn.commit(); + updateSpaces(txn, spaces_with_updates, std::move(rooms_with_space_updates)); + + txn.commit(); - std::map<QString, bool> readStatus; - - for (const auto &room : res.rooms.join) { - for (const auto &e : room.second.ephemeral.events) { - if (auto receiptsEv = std::get_if< - mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(&e)) { - std::vector<QString> receipts; - - for (const auto &[event_id, userReceipts] : - receiptsEv->content.receipts) { - for (const auto &[user_id, receipt] : userReceipts.users) { - (void)receipt; - - if (user_id != local_user_id) { - receipts.push_back( - QString::fromStdString(event_id)); - break; - } - } - } - if (!receipts.empty()) - emit newReadReceipts(QString::fromStdString(room.first), - receipts); + std::map<QString, bool> readStatus; + + for (const auto &room : res.rooms.join) { + for (const auto &e : room.second.ephemeral.events) { + if (auto receiptsEv = + std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Receipt>>(&e)) { + std::vector<QString> receipts; + + for (const auto &[event_id, userReceipts] : receiptsEv->content.receipts) { + for (const auto &[user_id, receipt] : userReceipts.users) { + (void)receipt; + + if (user_id != local_user_id) { + receipts.push_back(QString::fromStdString(event_id)); + break; } + } } - readStatus.emplace(QString::fromStdString(room.first), - calculateRoomReadStatus(room.first)); + if (!receipts.empty()) + emit newReadReceipts(QString::fromStdString(room.first), receipts); + } } + readStatus.emplace(QString::fromStdString(room.first), calculateRoomReadStatus(room.first)); + } - emit roomReadStatus(readStatus); + emit roomReadStatus(readStatus); } void Cache::saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::InvitedRoom> &rooms) { - for (const auto &room : rooms) { - auto statesdb = getInviteStatesDb(txn, room.first); - auto membersdb = getInviteMembersDb(txn, room.first); + for (const auto &room : rooms) { + auto statesdb = getInviteStatesDb(txn, room.first); + auto membersdb = getInviteMembersDb(txn, room.first); - saveInvite(txn, statesdb, membersdb, room.second); + saveInvite(txn, statesdb, membersdb, room.second); - RoomInfo updatedInfo; - updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString(); - updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString(); - updatedInfo.avatar_url = - getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); - updatedInfo.is_space = getInviteRoomIsSpace(txn, statesdb); - updatedInfo.is_invite = true; + RoomInfo updatedInfo; + updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString(); + updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString(); + updatedInfo.avatar_url = getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString(); + updatedInfo.is_space = getInviteRoomIsSpace(txn, statesdb); + updatedInfo.is_invite = true; - invitesDb_.put(txn, room.first, json(updatedInfo).dump()); - } + invitesDb_.put(txn, room.first, json(updatedInfo).dump()); + } } void @@ -1518,32 +1599,29 @@ Cache::saveInvite(lmdb::txn &txn, lmdb::dbi &membersdb, const mtx::responses::InvitedRoom &room) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - for (const auto &e : room.invite_state) { - if (auto msg = std::get_if<StrippedEvent<Member>>(&e)) { - auto display_name = msg->content.display_name.empty() - ? msg->state_key - : msg->content.display_name; + for (const auto &e : room.invite_state) { + if (auto msg = std::get_if<StrippedEvent<Member>>(&e)) { + auto display_name = + msg->content.display_name.empty() ? msg->state_key : msg->content.display_name; - MemberInfo tmp{display_name, msg->content.avatar_url}; + MemberInfo tmp{display_name, msg->content.avatar_url}; - membersdb.put(txn, msg->state_key, json(tmp).dump()); - } else { - std::visit( - [&txn, &statesdb](auto msg) { - auto j = json(msg); - bool res = - statesdb.put(txn, j["type"].get<std::string>(), j.dump()); - - if (!res) - nhlog::db()->warn("couldn't save data: {}", - json(msg).dump()); - }, - e); - } + membersdb.put(txn, msg->state_key, json(tmp).dump()); + } else { + std::visit( + [&txn, &statesdb](auto msg) { + auto j = json(msg); + bool res = statesdb.put(txn, j["type"].get<std::string>(), j.dump()); + + if (!res) + nhlog::db()->warn("couldn't save data: {}", json(msg).dump()); + }, + e); } + } } void @@ -1551,281 +1629,279 @@ Cache::savePresence( lmdb::txn &txn, const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates) { - for (const auto &update : presenceUpdates) { - auto presenceDb = getPresenceDb(txn); + for (const auto &update : presenceUpdates) { + auto presenceDb = getPresenceDb(txn); - presenceDb.put(txn, update.sender, json(update.content).dump()); - } + presenceDb.put(txn, update.sender, json(update.content).dump()); + } } std::vector<std::string> Cache::roomsWithStateUpdates(const mtx::responses::Sync &res) { - std::vector<std::string> rooms; - for (const auto &room : res.rooms.join) { - bool hasUpdates = false; - for (const auto &s : room.second.state.events) { - if (containsStateUpdates(s)) { - hasUpdates = true; - break; - } - } - - for (const auto &s : room.second.timeline.events) { - if (containsStateUpdates(s)) { - hasUpdates = true; - break; - } - } + std::vector<std::string> rooms; + for (const auto &room : res.rooms.join) { + bool hasUpdates = false; + for (const auto &s : room.second.state.events) { + if (containsStateUpdates(s)) { + hasUpdates = true; + break; + } + } - if (hasUpdates) - rooms.emplace_back(room.first); + for (const auto &s : room.second.timeline.events) { + if (containsStateUpdates(s)) { + hasUpdates = true; + break; + } } - for (const auto &room : res.rooms.invite) { - for (const auto &s : room.second.invite_state) { - if (containsStateUpdates(s)) { - rooms.emplace_back(room.first); - break; - } - } + if (hasUpdates) + rooms.emplace_back(room.first); + } + + for (const auto &room : res.rooms.invite) { + for (const auto &s : room.second.invite_state) { + if (containsStateUpdates(s)) { + rooms.emplace_back(room.first); + break; + } } + } - return rooms; + return rooms; } RoomInfo Cache::singleRoomInfo(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - try { - auto statesdb = getStatesDb(txn, room_id); - - std::string_view data; - - // Check if the room is joined. - if (roomsDb_.get(txn, room_id, data)) { - try { - RoomInfo tmp = json::parse(data); - tmp.member_count = getMembersDb(txn, room_id).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); - - return tmp; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room_id, - std::string(data.data(), data.size()), - e.what()); - } - } - } catch (const lmdb::error &e) { - nhlog::db()->warn( - "failed to read room info from db: room_id ({}), {}", room_id, e.what()); + try { + auto statesdb = getStatesDb(txn, room_id); + + std::string_view data; + + // Check if the room is joined. + if (roomsDb_.get(txn, room_id, data)) { + try { + RoomInfo tmp = json::parse(data); + tmp.member_count = getMembersDb(txn, room_id).size(txn); + tmp.join_rule = getRoomJoinRule(txn, statesdb); + tmp.guest_access = getRoomGuestAccess(txn, statesdb); + + return tmp; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room_id, + std::string(data.data(), data.size()), + e.what()); + } } + } catch (const lmdb::error &e) { + nhlog::db()->warn("failed to read room info from db: room_id ({}), {}", room_id, e.what()); + } - return RoomInfo(); + return RoomInfo(); } std::map<QString, RoomInfo> Cache::getRoomInfo(const std::vector<std::string> &rooms) { - std::map<QString, RoomInfo> room_info; + std::map<QString, RoomInfo> room_info; - // TODO This should be read only. - auto txn = lmdb::txn::begin(env_); + // TODO This should be read only. + auto txn = lmdb::txn::begin(env_); - for (const auto &room : rooms) { - std::string_view data; - auto statesdb = getStatesDb(txn, room); - - // Check if the room is joined. - if (roomsDb_.get(txn, room, data)) { - try { - RoomInfo tmp = json::parse(data); - tmp.member_count = getMembersDb(txn, room).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); - - room_info.emplace(QString::fromStdString(room), std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", - room, - std::string(data.data(), data.size()), - e.what()); - } - } else { - // Check if the room is an invite. - if (invitesDb_.get(txn, room, data)) { - try { - RoomInfo tmp = json::parse(std::string_view(data)); - tmp.member_count = getInviteMembersDb(txn, room).size(txn); - - room_info.emplace(QString::fromStdString(room), - std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - room, - std::string(data.data(), data.size()), - e.what()); - } - } + for (const auto &room : rooms) { + std::string_view data; + auto statesdb = getStatesDb(txn, room); + + // Check if the room is joined. + if (roomsDb_.get(txn, room, data)) { + try { + RoomInfo tmp = json::parse(data); + tmp.member_count = getMembersDb(txn, room).size(txn); + tmp.join_rule = getRoomJoinRule(txn, statesdb); + tmp.guest_access = getRoomGuestAccess(txn, statesdb); + + room_info.emplace(QString::fromStdString(room), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}", + room, + std::string(data.data(), data.size()), + e.what()); + } + } else { + // Check if the room is an invite. + if (invitesDb_.get(txn, room, data)) { + try { + RoomInfo tmp = json::parse(std::string_view(data)); + tmp.member_count = getInviteMembersDb(txn, room).size(txn); + + room_info.emplace(QString::fromStdString(room), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + room, + std::string(data.data(), data.size()), + e.what()); } + } } + } - txn.commit(); + txn.commit(); - return room_info; + return room_info; } std::vector<QString> Cache::roomIds() { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector<QString> rooms; - std::string_view room_id, unused; + std::vector<QString> rooms; + std::string_view room_id, unused; - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - while (roomsCursor.get(room_id, unused, MDB_NEXT)) - rooms.push_back(QString::fromStdString(std::string(room_id))); + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + while (roomsCursor.get(room_id, unused, MDB_NEXT)) + rooms.push_back(QString::fromStdString(std::string(room_id))); - roomsCursor.close(); + roomsCursor.close(); - return rooms; + return rooms; } QMap<QString, mtx::responses::Notifications> Cache::getTimelineMentions() { - // TODO: Should be read-only, but getMentionsDb will attempt to create a DB - // if it doesn't exist, throwing an error. - auto txn = lmdb::txn::begin(env_, nullptr); + // TODO: Should be read-only, but getMentionsDb will attempt to create a DB + // if it doesn't exist, throwing an error. + auto txn = lmdb::txn::begin(env_, nullptr); - QMap<QString, mtx::responses::Notifications> notifs; + QMap<QString, mtx::responses::Notifications> notifs; - auto room_ids = getRoomIds(txn); + auto room_ids = getRoomIds(txn); - for (const auto &room_id : room_ids) { - auto roomNotifs = getTimelineMentionsForRoom(txn, room_id); - notifs[QString::fromStdString(room_id)] = roomNotifs; - } + for (const auto &room_id : room_ids) { + auto roomNotifs = getTimelineMentionsForRoom(txn, room_id); + notifs[QString::fromStdString(room_id)] = roomNotifs; + } - txn.commit(); + txn.commit(); - return notifs; + return notifs; } std::string Cache::previousBatchToken(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_, nullptr); - auto orderDb = getEventOrderDb(txn, room_id); + auto txn = lmdb::txn::begin(env_, nullptr); + auto orderDb = getEventOrderDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); - std::string_view indexVal, val; - if (!cursor.get(indexVal, val, MDB_FIRST)) { - return ""; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + std::string_view indexVal, val; + if (!cursor.get(indexVal, val, MDB_FIRST)) { + return ""; + } - auto j = json::parse(val); + auto j = json::parse(val); - return j.value("prev_batch", ""); + return j.value("prev_batch", ""); } Cache::Messages Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, uint64_t index, bool forward) { - // TODO(nico): Limit the messages returned by this maybe? - auto orderDb = getOrderToMessageDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); + // TODO(nico): Limit the messages returned by this maybe? + auto orderDb = getOrderToMessageDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); - Messages messages{}; + Messages messages{}; - std::string_view indexVal, event_id; + std::string_view indexVal, event_id; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (index == std::numeric_limits<uint64_t>::max()) { - if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) { - index = lmdb::from_sv<uint64_t>(indexVal); - } else { - messages.end_of_cache = true; - return messages; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (index == std::numeric_limits<uint64_t>::max()) { + if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) { + index = lmdb::from_sv<uint64_t>(indexVal); } else { - if (cursor.get(indexVal, event_id, MDB_SET)) { - index = lmdb::from_sv<uint64_t>(indexVal); - } else { - messages.end_of_cache = true; - return messages; - } + messages.end_of_cache = true; + return messages; } + } else { + if (cursor.get(indexVal, event_id, MDB_SET)) { + index = lmdb::from_sv<uint64_t>(indexVal); + } else { + messages.end_of_cache = true; + return messages; + } + } - int counter = 0; - - bool ret; - while ((ret = cursor.get(indexVal, - event_id, - counter == 0 ? (forward ? MDB_FIRST : MDB_LAST) - : (forward ? MDB_NEXT : MDB_PREV))) && - counter++ < BATCH_SIZE) { - std::string_view event; - bool success = eventsDb.get(txn, event_id, event); - if (!success) - continue; + int counter = 0; - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json(json::parse(event), te); - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", e.what()); - continue; - } + bool ret; + while ((ret = cursor.get(indexVal, + event_id, + counter == 0 ? (forward ? MDB_FIRST : MDB_LAST) + : (forward ? MDB_NEXT : MDB_PREV))) && + counter++ < BATCH_SIZE) { + std::string_view event; + bool success = eventsDb.get(txn, event_id, event); + if (!success) + continue; - messages.timeline.events.push_back(std::move(te.data)); + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json(json::parse(event), te); + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + continue; } - cursor.close(); - // std::reverse(timeline.events.begin(), timeline.events.end()); - messages.next_index = lmdb::from_sv<uint64_t>(indexVal); - messages.end_of_cache = !ret; + messages.timeline.events.push_back(std::move(te.data)); + } + cursor.close(); - return messages; + // std::reverse(timeline.events.begin(), timeline.events.end()); + messages.next_index = lmdb::from_sv<uint64_t>(indexVal); + messages.end_of_cache = !ret; + + return messages; } std::optional<mtx::events::collections::TimelineEvent> Cache::getEvent(const std::string &room_id, const std::string &event_id) { - auto txn = ro_txn(env_); - auto eventsDb = getEventsDb(txn, room_id); + auto txn = ro_txn(env_); + auto eventsDb = getEventsDb(txn, room_id); - std::string_view event{}; - bool success = eventsDb.get(txn, event_id, event); - if (!success) - return {}; + std::string_view event{}; + bool success = eventsDb.get(txn, event_id, event); + if (!success) + return {}; - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json(json::parse(event), te); - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", e.what()); - return std::nullopt; - } + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json(json::parse(event), te); + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + return std::nullopt; + } - return te; + return te; } void Cache::storeEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto event_json = mtx::accessors::serialize_event(event.data); - eventsDb.put(txn, event_id, event_json.dump()); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto event_json = mtx::accessors::serialize_event(event.data); + eventsDb.put(txn, event_id, event_json.dump()); + txn.commit(); } void @@ -1833,922 +1909,944 @@ Cache::replaceEvent(const std::string &room_id, const std::string &event_id, const mtx::events::collections::TimelineEvent &event) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - auto event_json = mtx::accessors::serialize_event(event.data).dump(); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto event_json = mtx::accessors::serialize_event(event.data).dump(); - { - eventsDb.del(txn, event_id); - eventsDb.put(txn, event_id, event_json); - for (auto relation : mtx::accessors::relations(event.data).relations) { - relationsDb.put(txn, relation.event_id, event_id); - } + { + eventsDb.del(txn, event_id); + eventsDb.put(txn, event_id, event_json); + for (auto relation : mtx::accessors::relations(event.data).relations) { + relationsDb.put(txn, relation.event_id, event_id); } + } - txn.commit(); + txn.commit(); } std::vector<std::string> Cache::relatedEvents(const std::string &room_id, const std::string &event_id) { - auto txn = ro_txn(env_); - auto relationsDb = getRelationsDb(txn, room_id); + auto txn = ro_txn(env_); + auto relationsDb = getRelationsDb(txn, room_id); - std::vector<std::string> related_ids; + std::vector<std::string> related_ids; - auto related_cursor = lmdb::cursor::open(txn, relationsDb); - std::string_view related_to = event_id, related_event; - bool first = true; + auto related_cursor = lmdb::cursor::open(txn, relationsDb); + std::string_view related_to = event_id, related_event; + bool first = true; - try { - if (!related_cursor.get(related_to, related_event, MDB_SET)) - return {}; + try { + if (!related_cursor.get(related_to, related_event, MDB_SET)) + return {}; - while (related_cursor.get( - related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - if (event_id != std::string_view(related_to.data(), related_to.size())) - break; + while ( + related_cursor.get(related_to, related_event, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + if (event_id != std::string_view(related_to.data(), related_to.size())) + break; - related_ids.emplace_back(related_event.data(), related_event.size()); - } - } catch (const lmdb::error &e) { - nhlog::db()->error("related events error: {}", e.what()); + related_ids.emplace_back(related_event.data(), related_event.size()); } + } catch (const lmdb::error &e) { + nhlog::db()->error("related events error: {}", e.what()); + } - return related_ids; + return related_ids; } size_t Cache::memberCount(const std::string &room_id) { - auto txn = ro_txn(env_); - return getMembersDb(txn, room_id).size(txn); + auto txn = ro_txn(env_); + return getMembersDb(txn, room_id).size(txn); } QMap<QString, RoomInfo> Cache::roomInfo(bool withInvites) { - QMap<QString, RoomInfo> result; + QMap<QString, RoomInfo> result; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view room_id; - std::string_view room_data; + std::string_view room_id; + std::string_view room_data; - // Gather info about the joined rooms. - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(std::move(room_data)); - tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } - roomsCursor.close(); - - if (withInvites) { - // Gather info about the invites. - auto invitesCursor = lmdb::cursor::open(txn, invitesDb_); - while (invitesCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } - invitesCursor.close(); + // Gather info about the joined rooms. + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { + RoomInfo tmp = json::parse(std::move(room_data)); + tmp.member_count = getMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); + } + roomsCursor.close(); + + if (withInvites) { + // Gather info about the invites. + auto invitesCursor = lmdb::cursor::open(txn, invitesDb_); + while (invitesCursor.get(room_id, room_data, MDB_NEXT)) { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); } + invitesCursor.close(); + } - return result; + return result; } std::string Cache::getLastEventId(lmdb::txn &txn, const std::string &room_id) { - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(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::dbi orderDb; + try { + orderDb = getOrderToMessageDb(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 {}; + } - std::string_view indexVal, val; + std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (!cursor.get(indexVal, val, MDB_LAST)) { - return {}; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (!cursor.get(indexVal, val, MDB_LAST)) { + return {}; + } - return std::string(val.data(), val.size()); + return std::string(val.data(), val.size()); } std::optional<Cache::TimelineRange> Cache::getTimelineRange(const std::string &room_id) { - auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(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 {}; - } + auto txn = ro_txn(env_); + lmdb::dbi orderDb; + try { + orderDb = getOrderToMessageDb(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 {}; + } - std::string_view indexVal, val; + std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (!cursor.get(indexVal, val, MDB_LAST)) { - return {}; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + if (!cursor.get(indexVal, val, MDB_LAST)) { + return {}; + } - TimelineRange range{}; - range.last = lmdb::from_sv<uint64_t>(indexVal); + TimelineRange range{}; + range.last = lmdb::from_sv<uint64_t>(indexVal); - if (!cursor.get(indexVal, val, MDB_FIRST)) { - return {}; - } - range.first = lmdb::from_sv<uint64_t>(indexVal); + if (!cursor.get(indexVal, val, MDB_FIRST)) { + return {}; + } + range.first = lmdb::from_sv<uint64_t>(indexVal); - return range; + return range; } std::optional<uint64_t> Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id) { - if (event_id.empty() || room_id.empty()) - return {}; + if (event_id.empty() || room_id.empty()) + return {}; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getMessageToOrderDb(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::dbi orderDb; + try { + orderDb = getMessageToOrderDb(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 {}; + } - std::string_view indexVal{event_id.data(), event_id.size()}, val; + std::string_view indexVal{event_id.data(), event_id.size()}, val; - bool success = orderDb.get(txn, indexVal, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, indexVal, val); + if (!success) { + return {}; + } - return lmdb::from_sv<uint64_t>(val); + return lmdb::from_sv<uint64_t>(val); } std::optional<uint64_t> Cache::getEventIndex(const std::string &room_id, std::string_view event_id) { - if (room_id.empty() || event_id.empty()) - return {}; + if (room_id.empty() || event_id.empty()) + return {}; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - 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::dbi orderDb; + 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 {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, event_id, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, event_id, val); + if (!success) { + return {}; + } - return lmdb::from_sv<uint64_t>(val); + return lmdb::from_sv<uint64_t>(val); } std::optional<std::pair<uint64_t, std::string>> Cache::lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id) { - if (room_id.empty() || event_id.empty()) - return {}; - - auto txn = ro_txn(env_); - - lmdb::dbi orderDb; - lmdb::dbi eventOrderDb; - lmdb::dbi timelineDb; - try { - orderDb = getEventToOrderDb(txn, room_id); - eventOrderDb = getEventOrderDb(txn, room_id); - timelineDb = getMessageToOrderDb(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 {}; - } - - std::string_view indexVal; - - bool success = orderDb.get(txn, event_id, indexVal); - if (!success) { - return {}; - } - - try { - uint64_t prevIdx = lmdb::from_sv<uint64_t>(indexVal); - std::string prevId{event_id}; - - auto cursor = lmdb::cursor::open(txn, eventOrderDb); - cursor.get(indexVal, MDB_SET); - while (cursor.get(indexVal, event_id, MDB_NEXT)) { - std::string evId = json::parse(event_id)["event_id"].get<std::string>(); - std::string_view temp; - if (timelineDb.get(txn, evId, temp)) { - return std::pair{prevIdx, std::string(prevId)}; - } else { - prevIdx = lmdb::from_sv<uint64_t>(indexVal); - prevId = std::move(evId); - } - } - + if (room_id.empty() || event_id.empty()) + return {}; + + auto txn = ro_txn(env_); + + lmdb::dbi orderDb; + lmdb::dbi eventOrderDb; + lmdb::dbi timelineDb; + try { + orderDb = getEventToOrderDb(txn, room_id); + eventOrderDb = getEventOrderDb(txn, room_id); + timelineDb = getMessageToOrderDb(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 {}; + } + + std::string_view indexVal; + + bool success = orderDb.get(txn, event_id, indexVal); + if (!success) { + return {}; + } + + try { + uint64_t prevIdx = lmdb::from_sv<uint64_t>(indexVal); + std::string prevId{event_id}; + + auto cursor = lmdb::cursor::open(txn, eventOrderDb); + cursor.get(indexVal, MDB_SET); + while (cursor.get(indexVal, event_id, MDB_NEXT)) { + std::string evId = json::parse(event_id)["event_id"].get<std::string>(); + std::string_view temp; + if (timelineDb.get(txn, evId, temp)) { return std::pair{prevIdx, std::string(prevId)}; - } catch (lmdb::runtime_error &e) { - nhlog::db()->error( - "Failed to get last invisible event after {}", event_id, e.what()); - return {}; + } else { + prevIdx = lmdb::from_sv<uint64_t>(indexVal); + prevId = std::move(evId); + } } + + return std::pair{prevIdx, std::string(prevId)}; + } catch (lmdb::runtime_error &e) { + nhlog::db()->error("Failed to get last invisible event after {}", event_id, e.what()); + return {}; + } } std::optional<uint64_t> Cache::getArrivalIndex(const std::string &room_id, std::string_view event_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - lmdb::dbi orderDb; - 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::dbi orderDb; + 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 {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, event_id, val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, event_id, val); + if (!success) { + return {}; + } - return lmdb::from_sv<uint64_t>(val); + return lmdb::from_sv<uint64_t>(val); } std::optional<std::string> Cache::getTimelineEventId(const std::string &room_id, uint64_t index) { - auto txn = ro_txn(env_); - lmdb::dbi orderDb; - try { - orderDb = getOrderToMessageDb(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 {}; - } + auto txn = ro_txn(env_); + lmdb::dbi orderDb; + try { + orderDb = getOrderToMessageDb(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 {}; + } - std::string_view val; + std::string_view val; - bool success = orderDb.get(txn, lmdb::to_sv(index), val); - if (!success) { - return {}; - } + bool success = orderDb.get(txn, lmdb::to_sv(index), val); + if (!success) { + return {}; + } - return std::string(val); + return std::string(val); } QHash<QString, RoomInfo> Cache::invites() { - QHash<QString, RoomInfo> result; + QHash<QString, RoomInfo> result; - auto txn = ro_txn(env_); - auto cursor = lmdb::cursor::open(txn, invitesDb_); + auto txn = ro_txn(env_); + auto cursor = lmdb::cursor::open(txn, invitesDb_); - std::string_view room_id, room_data; + std::string_view room_id, room_data; - while (cursor.get(room_id, room_data, MDB_NEXT)) { - try { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); - result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - room_id, - std::string(room_data), - e.what()); - } + while (cursor.get(room_id, room_data, MDB_NEXT)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn); + result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + room_id, + std::string(room_data), + e.what()); } + } - cursor.close(); + cursor.close(); - return result; + return result; } std::optional<RoomInfo> Cache::invite(std::string_view roomid) { - std::optional<RoomInfo> result; + std::optional<RoomInfo> result; - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view room_data; + std::string_view room_data; - if (invitesDb_.get(txn, roomid, room_data)) { - try { - RoomInfo tmp = json::parse(room_data); - tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn); - result = std::move(tmp); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info for invite: " - "room_id ({}), {}: {}", - roomid, - std::string(room_data), - e.what()); - } + if (invitesDb_.get(txn, roomid, room_data)) { + try { + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn); + result = std::move(tmp); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse room info for invite: " + "room_id ({}), {}: {}", + roomid, + std::string(room_data), + e.what()); } + } - return result; + return result; } QString Cache::getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); - if (res) { - try { - StateEvent<Avatar> msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent<Avatar> msg = json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.url.empty()) - return QString::fromStdString(msg.content.url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); - } + if (!msg.content.url.empty()) + return QString::fromStdString(msg.content.url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); } + } - // We don't use an avatar for group chats. - if (membersdb.size(txn) > 2) - return QString(); + // We don't use an avatar for group chats. + if (membersdb.size(txn) > 2) + return QString(); - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id; - std::string_view member_data; - std::string fallback_url; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id; + std::string_view member_data; + std::string fallback_url; - // Resolve avatar for 1-1 chats. - while (cursor.get(user_id, member_data, MDB_NEXT)) { - try { - MemberInfo m = json::parse(member_data); - if (user_id == localUserId_.toStdString()) { - fallback_url = m.avatar_url; - continue; - } + // Resolve avatar for 1-1 chats. + while (cursor.get(user_id, member_data, MDB_NEXT)) { + try { + MemberInfo m = json::parse(member_data); + if (user_id == localUserId_.toStdString()) { + fallback_url = m.avatar_url; + continue; + } - cursor.close(); - return QString::fromStdString(m.avatar_url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + cursor.close(); + return QString::fromStdString(m.avatar_url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - // Default case when there is only one member. - return QString::fromStdString(fallback_url); + // Default case when there is only one member. + return QString::fromStdString(fallback_url); } QString Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); - if (res) { - try { - StateEvent<Name> msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent<Name> msg = json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.name.empty()) - return QString::fromStdString(msg.content.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); - } + if (!msg.content.name.empty()) + return QString::fromStdString(msg.content.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); } + } - res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); + res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); - if (res) { - try { - StateEvent<CanonicalAlias> msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent<CanonicalAlias> msg = + json::parse(std::string_view(event.data(), event.size())); - if (!msg.content.alias.empty()) - return QString::fromStdString(msg.content.alias); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", - e.what()); - } + if (!msg.content.alias.empty()) + return QString::fromStdString(msg.content.alias); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - const auto total = membersdb.size(txn); + auto cursor = lmdb::cursor::open(txn, membersdb); + const auto total = membersdb.size(txn); - std::size_t ii = 0; - std::string_view user_id; - std::string_view member_data; - std::map<std::string, MemberInfo> members; + std::size_t ii = 0; + std::string_view user_id; + std::string_view member_data; + std::map<std::string, MemberInfo> members; - while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) { - try { - members.emplace(user_id, json::parse(member_data)); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } - - ii++; + while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) { + try { + members.emplace(user_id, json::parse(member_data)); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } - cursor.close(); + ii++; + } - if (total == 1 && !members.empty()) - return QString::fromStdString(members.begin()->second.name); + cursor.close(); - auto first_member = [&members, this]() { - for (const auto &m : members) { - if (m.first != localUserId_.toStdString()) - return QString::fromStdString(m.second.name); - } + if (total == 1 && !members.empty()) + return QString::fromStdString(members.begin()->second.name); + + auto first_member = [&members, this]() { + for (const auto &m : members) { + if (m.first != localUserId_.toStdString()) + return QString::fromStdString(m.second.name); + } - return localUserId_; - }(); + return localUserId_; + }(); - if (total == 2) - return first_member; - else if (total > 2) - return QString("%1 and %2 others").arg(first_member).arg(total - 1); + if (total == 2) + return first_member; + else if (total > 2) + return QString("%1 and %2 others").arg(first_member).arg(total - 1); - return "Empty Room"; + return "Empty Room"; } mtx::events::state::JoinRule Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomJoinRules), event); - if (res) { - try { - StateEvent<state::JoinRules> msg = json::parse(event); - return msg.content.join_rule; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what()); - } + if (res) { + try { + StateEvent<state::JoinRules> msg = json::parse(event); + return msg.content.join_rule; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what()); } - return state::JoinRule::Knock; + } + return state::JoinRule::Knock; } bool Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomGuestAccess), event); - if (res) { - try { - StateEvent<GuestAccess> msg = json::parse(event); - return msg.content.guest_access == AccessState::CanJoin; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.guest_access event: {}", - e.what()); - } + if (res) { + try { + StateEvent<GuestAccess> msg = json::parse(event); + return msg.content.guest_access == AccessState::CanJoin; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.guest_access event: {}", e.what()); } - return false; + } + return false; } QString Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomTopic), event); - if (res) { - try { - StateEvent<Topic> msg = json::parse(event); + if (res) { + try { + StateEvent<Topic> msg = json::parse(event); - if (!msg.content.topic.empty()) - return QString::fromStdString(msg.content.topic); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (!msg.content.topic.empty()) + return QString::fromStdString(msg.content.topic); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return QString(); + return QString(); } QString Cache::getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StateEvent<Create> msg = json::parse(event); + if (res) { + try { + StateEvent<Create> msg = json::parse(event); - if (!msg.content.room_version.empty()) - return QString::fromStdString(msg.content.room_version); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); - } + if (!msg.content.room_version.empty()) + return QString::fromStdString(msg.content.room_version); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); } + } - nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); - return QString("1"); + nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); + return QString("1"); } bool Cache::getRoomIsSpace(lmdb::txn &txn, lmdb::dbi &statesdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StateEvent<Create> msg = json::parse(event); + if (res) { + try { + StateEvent<Create> msg = json::parse(event); - return msg.content.type == mtx::events::state::room_type::space; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); - } + return msg.content.type == mtx::events::state::room_type::space; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.create event: {}", e.what()); } + } - nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); - return false; + nhlog::db()->warn("m.room.create event is missing room version, assuming version \"1\""); + return false; } std::optional<mtx::events::state::CanonicalAlias> Cache::getRoomAliases(const std::string &roomid) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - auto txn = ro_txn(env_); - auto statesdb = getStatesDb(txn, roomid); + auto txn = ro_txn(env_); + auto statesdb = getStatesDb(txn, roomid); - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomCanonicalAlias), event); - if (res) { - try { - StateEvent<CanonicalAlias> msg = json::parse(event); + if (res) { + try { + StateEvent<CanonicalAlias> msg = json::parse(event); - return msg.content; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", - e.what()); - } + return msg.content; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.canonical_alias event: {}", e.what()); } + } - return std::nullopt; + return std::nullopt; } QString Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomName), event); - if (res) { - try { - StrippedEvent<state::Name> msg = json::parse(event); - return QString::fromStdString(msg.content.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); - } + if (res) { + try { + StrippedEvent<state::Name> msg = json::parse(event); + return QString::fromStdString(msg.content.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.name event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id, member_data; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id, member_data; - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; + while (cursor.get(user_id, member_data, MDB_NEXT)) { + if (user_id == localUserId_.toStdString()) + continue; - try { - MemberInfo tmp = json::parse(member_data); - cursor.close(); + try { + MemberInfo tmp = json::parse(member_data); + cursor.close(); - return QString::fromStdString(tmp.name); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + return QString::fromStdString(tmp.name); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - return QString("Empty Room"); + return QString("Empty Room"); } QString Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); + std::string_view event; + bool res = statesdb.get(txn, to_string(mtx::events::EventType::RoomAvatar), event); - if (res) { - try { - StrippedEvent<state::Avatar> msg = json::parse(event); - return QString::fromStdString(msg.content.url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); - } + if (res) { + try { + StrippedEvent<state::Avatar> msg = json::parse(event); + return QString::fromStdString(msg.content.url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.avatar event: {}", e.what()); } + } - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string_view user_id, member_data; + auto cursor = lmdb::cursor::open(txn, membersdb); + std::string_view user_id, member_data; - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; + while (cursor.get(user_id, member_data, MDB_NEXT)) { + if (user_id == localUserId_.toStdString()) + continue; - try { - MemberInfo tmp = json::parse(member_data); - cursor.close(); + try { + MemberInfo tmp = json::parse(member_data); + cursor.close(); - return QString::fromStdString(tmp.avatar_url); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse member info: {}", e.what()); - } + return QString::fromStdString(tmp.avatar_url); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse member info: {}", e.what()); } + } - cursor.close(); + cursor.close(); - return QString(); + return QString(); } QString Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event); + std::string_view event; + bool res = db.get(txn, to_string(mtx::events::EventType::RoomTopic), event); - if (res) { - try { - StrippedEvent<Topic> msg = json::parse(event); - return QString::fromStdString(msg.content.topic); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (res) { + try { + StrippedEvent<Topic> msg = json::parse(event); + return QString::fromStdString(msg.content.topic); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return QString(); + return QString(); } bool Cache::getInviteRoomIsSpace(lmdb::txn &txn, lmdb::dbi &db) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - std::string_view event; - bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event); + std::string_view event; + bool res = db.get(txn, to_string(mtx::events::EventType::RoomCreate), event); - if (res) { - try { - StrippedEvent<Create> msg = json::parse(event); - return msg.content.type == mtx::events::state::room_type::space; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); - } + if (res) { + try { + StrippedEvent<Create> msg = json::parse(event); + return msg.content.type == mtx::events::state::room_type::space; + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.topic event: {}", e.what()); } + } - return false; + return false; } std::vector<std::string> Cache::joinedRooms() { - auto txn = ro_txn(env_); - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); + auto txn = ro_txn(env_); + auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - std::string_view id, data; - std::vector<std::string> room_ids; + std::string_view id, data; + std::vector<std::string> room_ids; - // Gather the room ids for the joined rooms. - while (roomsCursor.get(id, data, MDB_NEXT)) - room_ids.emplace_back(id); + // Gather the room ids for the joined rooms. + while (roomsCursor.get(id, data, MDB_NEXT)) + room_ids.emplace_back(id); - roomsCursor.close(); + roomsCursor.close(); - return room_ids; + return room_ids; } std::optional<MemberInfo> Cache::getMember(const std::string &room_id, const std::string &user_id) { - if (user_id.empty() || !env_.handle()) - return std::nullopt; + if (user_id.empty() || !env_.handle()) + return std::nullopt; - try { - auto txn = ro_txn(env_); + try { + auto txn = ro_txn(env_); - auto membersdb = getMembersDb(txn, room_id); + auto membersdb = getMembersDb(txn, room_id); - std::string_view info; - if (membersdb.get(txn, user_id, info)) { - MemberInfo m = json::parse(info); - return m; - } - } catch (std::exception &e) { - nhlog::db()->warn( - "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what()); + std::string_view info; + if (membersdb.get(txn, user_id, info)) { + MemberInfo m = json::parse(info); + return m; } - return std::nullopt; + } catch (std::exception &e) { + nhlog::db()->warn( + "Failed to read member ({}) in room ({}): {}", user_id, room_id, e.what()); + } + return std::nullopt; } std::vector<RoomMember> Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len) { - auto txn = ro_txn(env_); - auto db = getMembersDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, db); + auto txn = ro_txn(env_); + auto db = getMembersDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, db); - std::size_t currentIndex = 0; + std::size_t currentIndex = 0; - const auto endIndex = std::min(startIndex + len, db.size(txn)); + const auto endIndex = std::min(startIndex + len, db.size(txn)); - std::vector<RoomMember> members; + std::vector<RoomMember> members; - std::string_view user_id, user_data; - while (cursor.get(user_id, user_data, MDB_NEXT)) { - if (currentIndex < startIndex) { - currentIndex += 1; - continue; - } + std::string_view user_id, user_data; + while (cursor.get(user_id, user_data, MDB_NEXT)) { + if (currentIndex < startIndex) { + currentIndex += 1; + continue; + } - if (currentIndex >= endIndex) - break; + if (currentIndex >= endIndex) + break; - try { - MemberInfo tmp = json::parse(user_data); - members.emplace_back( - RoomMember{QString::fromStdString(std::string(user_id)), - QString::fromStdString(tmp.name)}); - } catch (const json::exception &e) { - nhlog::db()->warn("{}", e.what()); - } + try { + MemberInfo tmp = json::parse(user_data); + members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), + QString::fromStdString(tmp.name)}); + } catch (const json::exception &e) { + nhlog::db()->warn("{}", e.what()); + } + + currentIndex += 1; + } + + cursor.close(); + + return members; +} - currentIndex += 1; +std::vector<RoomMember> +Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len) +{ + auto txn = ro_txn(env_); + auto db = getInviteMembersDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, db); + + std::size_t currentIndex = 0; + + const auto endIndex = std::min(startIndex + len, db.size(txn)); + + std::vector<RoomMember> members; + + std::string_view user_id, user_data; + while (cursor.get(user_id, user_data, MDB_NEXT)) { + if (currentIndex < startIndex) { + currentIndex += 1; + continue; } - cursor.close(); + if (currentIndex >= endIndex) + break; - return members; + try { + MemberInfo tmp = json::parse(user_data); + members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), + QString::fromStdString(tmp.name)}); + } catch (const json::exception &e) { + nhlog::db()->warn("{}", e.what()); + } + + currentIndex += 1; + } + + cursor.close(); + + return members; } bool Cache::isRoomMember(const std::string &user_id, const std::string &room_id) { - try { - auto txn = ro_txn(env_); - auto db = getMembersDb(txn, room_id); + try { + auto txn = ro_txn(env_); + auto db = getMembersDb(txn, room_id); - std::string_view value; - bool res = db.get(txn, user_id, value); + std::string_view value; + bool res = db.get(txn, user_id, value); - return res; - } catch (std::exception &e) { - nhlog::db()->warn("Failed to read member membership ({}) in room ({}): {}", - user_id, - room_id, - e.what()); - } - return false; + return res; + } catch (std::exception &e) { + nhlog::db()->warn( + "Failed to read member membership ({}) in room ({}): {}", user_id, room_id, e.what()); + } + return false; } void Cache::savePendingMessage(const std::string &room_id, const mtx::events::collections::TimelineEvent &message) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); - mtx::responses::Timeline timeline; - timeline.events.push_back(message.data); - saveTimelineMessages(txn, eventsDb, room_id, timeline); + mtx::responses::Timeline timeline; + timeline.events.push_back(message.data); + saveTimelineMessages(txn, eventsDb, room_id, timeline); - auto pending = getPendingMessagesDb(txn, room_id); + auto pending = getPendingMessagesDb(txn, room_id); - int64_t now = QDateTime::currentMSecsSinceEpoch(); - pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data)); + int64_t now = QDateTime::currentMSecsSinceEpoch(); + pending.put(txn, lmdb::to_sv(now), mtx::accessors::event_id(message.data)); - txn.commit(); + txn.commit(); } std::optional<mtx::events::collections::TimelineEvent> Cache::firstPendingMessage(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - auto pending = getPendingMessagesDb(txn, room_id); - - { - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - auto eventsDb = getEventsDb(txn, room_id); - std::string_view event; - if (!eventsDb.get(txn, pendingTxn, event)) { - pending.del(txn, tsIgnored, pendingTxn); - continue; - } + auto txn = lmdb::txn::begin(env_); + auto pending = getPendingMessagesDb(txn, room_id); + + { + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + auto eventsDb = getEventsDb(txn, room_id); + std::string_view event; + if (!eventsDb.get(txn, pendingTxn, event)) { + pending.del(txn, tsIgnored, pendingTxn); + continue; + } + + try { + mtx::events::collections::TimelineEvent te; + mtx::events::collections::from_json(json::parse(event), te); - try { - mtx::events::collections::TimelineEvent te; - mtx::events::collections::from_json(json::parse(event), te); - - pendingCursor.close(); - txn.commit(); - return te; - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", - e.what()); - pending.del(txn, tsIgnored, pendingTxn); - continue; - } - } + pendingCursor.close(); + txn.commit(); + return te; + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + pending.del(txn, tsIgnored, pendingTxn); + continue; + } } + } - txn.commit(); + txn.commit(); - return std::nullopt; + return std::nullopt; } void Cache::removePendingStatus(const std::string &room_id, const std::string &txn_id) { - auto txn = lmdb::txn::begin(env_); - auto pending = getPendingMessagesDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto pending = getPendingMessagesDb(txn, room_id); - { - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) - lmdb::cursor_del(pendingCursor); - } + { + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) + lmdb::cursor_del(pendingCursor); } + } - txn.commit(); + txn.commit(); } void @@ -2757,390 +2855,398 @@ Cache::saveTimelineMessages(lmdb::txn &txn, const std::string &room_id, const mtx::responses::Timeline &res) { - if (res.events.empty()) - return; - - auto relationsDb = getRelationsDb(txn, room_id); - - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); - auto pending = getPendingMessagesDb(txn, room_id); - - if (res.limited) { - lmdb::dbi_drop(txn, orderDb, false); - lmdb::dbi_drop(txn, evToOrderDb, false); - lmdb::dbi_drop(txn, msg2orderDb, false); - lmdb::dbi_drop(txn, order2msgDb, false); - lmdb::dbi_drop(txn, pending, true); - } - - using namespace mtx::events; - using namespace mtx::events::state; - - std::string_view indexVal, val; - uint64_t index = std::numeric_limits<uint64_t>::max() / 2; - auto cursor = lmdb::cursor::open(txn, orderDb); - if (cursor.get(indexVal, val, MDB_LAST)) { - index = lmdb::from_sv<uint64_t>(indexVal); - } - - uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2; - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - if (msgCursor.get(indexVal, val, MDB_LAST)) { - msgIndex = lmdb::from_sv<uint64_t>(indexVal); - } - - bool first = true; - for (const auto &e : res.events) { - auto event = mtx::accessors::serialize_event(e); - auto txn_id = mtx::accessors::transaction_id(e); - - std::string event_id_val = event.value("event_id", ""); - if (event_id_val.empty()) { - nhlog::db()->error("Event without id!"); - continue; + if (res.events.empty()) + return; + + auto relationsDb = getRelationsDb(txn, room_id); + + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); + auto pending = getPendingMessagesDb(txn, room_id); + + if (res.limited) { + lmdb::dbi_drop(txn, orderDb, false); + lmdb::dbi_drop(txn, evToOrderDb, false); + lmdb::dbi_drop(txn, msg2orderDb, false); + lmdb::dbi_drop(txn, order2msgDb, false); + lmdb::dbi_drop(txn, pending, true); + } + + using namespace mtx::events; + using namespace mtx::events::state; + + std::string_view indexVal, val; + uint64_t index = std::numeric_limits<uint64_t>::max() / 2; + auto cursor = lmdb::cursor::open(txn, orderDb); + if (cursor.get(indexVal, val, MDB_LAST)) { + index = lmdb::from_sv<uint64_t>(indexVal); + } + + uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2; + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + if (msgCursor.get(indexVal, val, MDB_LAST)) { + msgIndex = lmdb::from_sv<uint64_t>(indexVal); + } + + bool first = true; + for (const auto &e : res.events) { + auto event = mtx::accessors::serialize_event(e); + auto txn_id = mtx::accessors::transaction_id(e); + + std::string event_id_val = event.value("event_id", ""); + if (event_id_val.empty()) { + nhlog::db()->error("Event without id!"); + continue; + } + + std::string_view event_id = event_id_val; + + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + if (first && !res.prev_batch.empty()) + orderEntry["prev_batch"] = res.prev_batch; + + std::string_view txn_order; + if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) { + eventsDb.put(txn, event_id, event.dump()); + eventsDb.del(txn, txn_id); + + std::string_view msg_txn_order; + if (msg2orderDb.get(txn, txn_id, msg_txn_order)) { + order2msgDb.put(txn, msg_txn_order, event_id); + msg2orderDb.put(txn, event_id, msg_txn_order); + msg2orderDb.del(txn, txn_id); + } + + orderDb.put(txn, txn_order, orderEntry.dump()); + evToOrderDb.put(txn, event_id, txn_order); + evToOrderDb.del(txn, txn_id); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.del(txn, r.event_id, txn_id); + relationsDb.put(txn, r.event_id, event_id); + } } + } + + auto pendingCursor = lmdb::cursor::open(txn, pending); + std::string_view tsIgnored, pendingTxn; + while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { + if (std::string_view(pendingTxn.data(), pendingTxn.size()) == txn_id) + lmdb::cursor_del(pendingCursor); + } + } else if (auto redaction = + std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(&e)) { + if (redaction->redacts.empty()) + continue; + + std::string_view oldEvent; + bool success = eventsDb.get(txn, redaction->redacts, oldEvent); + if (!success) + continue; + + mtx::events::collections::TimelineEvent te; + try { + mtx::events::collections::from_json( + json::parse(std::string_view(oldEvent.data(), oldEvent.size())), te); + // overwrite the content and add redation data + std::visit( + [redaction](auto &ev) { + ev.unsigned_data.redacted_because = *redaction; + ev.unsigned_data.redacted_by = redaction->event_id; + }, + te.data); + event = mtx::accessors::serialize_event(te.data); + event["content"].clear(); + + } catch (std::exception &e) { + nhlog::db()->error("Failed to parse message from cache {}", e.what()); + continue; + } - std::string_view event_id = event_id_val; - - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - if (first && !res.prev_batch.empty()) - orderEntry["prev_batch"] = res.prev_batch; - - std::string_view txn_order; - if (!txn_id.empty() && evToOrderDb.get(txn, txn_id, txn_order)) { - eventsDb.put(txn, event_id, event.dump()); - eventsDb.del(txn, txn_id); - - std::string_view msg_txn_order; - if (msg2orderDb.get(txn, txn_id, msg_txn_order)) { - order2msgDb.put(txn, msg_txn_order, event_id); - msg2orderDb.put(txn, event_id, msg_txn_order); - msg2orderDb.del(txn, txn_id); - } - - orderDb.put(txn, txn_order, orderEntry.dump()); - evToOrderDb.put(txn, event_id, txn_order); - evToOrderDb.del(txn, txn_id); - - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.del(txn, r.event_id, txn_id); - relationsDb.put(txn, r.event_id, event_id); - } - } - } - - auto pendingCursor = lmdb::cursor::open(txn, pending); - std::string_view tsIgnored, pendingTxn; - while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { - if (std::string_view(pendingTxn.data(), pendingTxn.size()) == - txn_id) - lmdb::cursor_del(pendingCursor); - } - } else if (auto redaction = - std::get_if<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>( - &e)) { - if (redaction->redacts.empty()) - continue; - - std::string_view oldEvent; - bool success = eventsDb.get(txn, redaction->redacts, oldEvent); - if (!success) - continue; - - mtx::events::collections::TimelineEvent te; - try { - mtx::events::collections::from_json( - json::parse(std::string_view(oldEvent.data(), oldEvent.size())), - te); - // overwrite the content and add redation data - std::visit( - [redaction](auto &ev) { - ev.unsigned_data.redacted_because = *redaction; - ev.unsigned_data.redacted_by = redaction->event_id; - }, - te.data); - event = mtx::accessors::serialize_event(te.data); - event["content"].clear(); - - } catch (std::exception &e) { - nhlog::db()->error("Failed to parse message from cache {}", - e.what()); - continue; - } - - eventsDb.put(txn, redaction->redacts, event.dump()); - eventsDb.put(txn, redaction->event_id, json(*redaction).dump()); - } else { - eventsDb.put(txn, event_id, event.dump()); - - ++index; - - first = false; + eventsDb.put(txn, redaction->redacts, event.dump()); + eventsDb.put(txn, redaction->event_id, json(*redaction).dump()); + } else { + first = false; - nhlog::db()->debug("saving '{}'", orderEntry.dump()); + // This check protects against duplicates in the timeline. If the event_id + // is already in the DB, we skip putting it (again) in ordered DBs, and only + // update the event itself and its relations. + std::string_view unused_read; + if (!evToOrderDb.get(txn, event_id, unused_read)) { + ++index; - cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND); - evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); + nhlog::db()->debug("saving '{}'", orderEntry.dump()); - // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(txn, e, room_id)) { - ++msgIndex; - msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND); + cursor.put(lmdb::to_sv(index), orderEntry.dump(), MDB_APPEND); + evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); - msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); - } + // TODO(Nico): Allow blacklisting more event types in UI + if (!isHiddenEvent(txn, e, room_id)) { + ++msgIndex; + msgCursor.put(lmdb::to_sv(msgIndex), event_id, MDB_APPEND); - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.put(txn, r.event_id, event_id); - } - } - } + msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); + } + } else { + nhlog::db()->warn("duplicate event '{}'", orderEntry.dump()); + } + eventsDb.put(txn, event_id, event.dump()); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.put(txn, r.event_id, event_id); + } } + } } + } } uint64_t Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Messages &res) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); - std::string_view indexVal, val; - uint64_t index = std::numeric_limits<uint64_t>::max() / 2; - { - auto cursor = lmdb::cursor::open(txn, orderDb); - if (cursor.get(indexVal, val, MDB_FIRST)) { - index = lmdb::from_sv<uint64_t>(indexVal); - } - } - - uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2; - { - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - if (msgCursor.get(indexVal, val, MDB_FIRST)) { - msgIndex = lmdb::from_sv<uint64_t>(indexVal); - } - } - - if (res.chunk.empty()) { - if (orderDb.get(txn, lmdb::to_sv(index), val)) { - auto orderEntry = json::parse(val); - orderEntry["prev_batch"] = res.end; - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - txn.commit(); - } - return index; + std::string_view indexVal, val; + uint64_t index = std::numeric_limits<uint64_t>::max() / 2; + { + auto cursor = lmdb::cursor::open(txn, orderDb); + if (cursor.get(indexVal, val, MDB_FIRST)) { + index = lmdb::from_sv<uint64_t>(indexVal); } + } - std::string event_id_val; - for (const auto &e : res.chunk) { - if (std::holds_alternative< - mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e)) - continue; - - auto event = mtx::accessors::serialize_event(e); - event_id_val = event["event_id"].get<std::string>(); - std::string_view event_id = event_id_val; - eventsDb.put(txn, event_id, event.dump()); - - --index; - - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); - - // TODO(Nico): Allow blacklisting more event types in UI - if (!isHiddenEvent(txn, e, room_id)) { - --msgIndex; - order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id); - - msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); - } - - auto relations = mtx::accessors::relations(e); - if (!relations.relations.empty()) { - for (const auto &r : relations.relations) { - if (!r.event_id.empty()) { - relationsDb.put(txn, r.event_id, event_id); - } - } + uint64_t msgIndex = std::numeric_limits<uint64_t>::max() / 2; + { + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + if (msgCursor.get(indexVal, val, MDB_FIRST)) { + msgIndex = lmdb::from_sv<uint64_t>(indexVal); + } + } + + if (res.chunk.empty()) { + if (orderDb.get(txn, lmdb::to_sv(index), val)) { + auto orderEntry = json::parse(val); + orderEntry["prev_batch"] = res.end; + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + txn.commit(); + } + return index; + } + + std::string event_id_val; + for (const auto &e : res.chunk) { + if (std::holds_alternative<mtx::events::RedactionEvent<mtx::events::msg::Redaction>>(e)) + continue; + + auto event = mtx::accessors::serialize_event(e); + event_id_val = event["event_id"].get<std::string>(); + std::string_view event_id = event_id_val; + + // This check protects against duplicates in the timeline. If the event_id is + // already in the DB, we skip putting it (again) in ordered DBs, and only update the + // event itself and its relations. + std::string_view unused_read; + if (!evToOrderDb.get(txn, event_id, unused_read)) { + --index; + + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + evToOrderDb.put(txn, event_id, lmdb::to_sv(index)); + + // TODO(Nico): Allow blacklisting more event types in UI + if (!isHiddenEvent(txn, e, room_id)) { + --msgIndex; + order2msgDb.put(txn, lmdb::to_sv(msgIndex), event_id); + + msg2orderDb.put(txn, event_id, lmdb::to_sv(msgIndex)); + } + } + eventsDb.put(txn, event_id, event.dump()); + + auto relations = mtx::accessors::relations(e); + if (!relations.relations.empty()) { + for (const auto &r : relations.relations) { + if (!r.event_id.empty()) { + relationsDb.put(txn, r.event_id, event_id); } + } } + } - json orderEntry = json::object(); - orderEntry["event_id"] = event_id_val; - orderEntry["prev_batch"] = res.end; - orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); + json orderEntry = json::object(); + orderEntry["event_id"] = event_id_val; + orderEntry["prev_batch"] = res.end; + orderDb.put(txn, lmdb::to_sv(index), orderEntry.dump()); - txn.commit(); + txn.commit(); - return msgIndex; + return msgIndex; } void Cache::clearTimeline(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto msg2orderDb = getMessageToOrderDb(txn, room_id); - auto order2msgDb = getOrderToMessageDb(txn, room_id); + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto msg2orderDb = getMessageToOrderDb(txn, room_id); + auto order2msgDb = getOrderToMessageDb(txn, room_id); - std::string_view indexVal, val; - auto cursor = lmdb::cursor::open(txn, orderDb); + std::string_view indexVal, val; + auto cursor = lmdb::cursor::open(txn, orderDb); - bool start = true; - bool passed_pagination_token = false; - while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { - start = false; - json obj; + bool start = true; + bool passed_pagination_token = false; + while (cursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; + json obj; - try { - obj = json::parse(std::string_view(val.data(), val.size())); - } catch (std::exception &) { - // workaround bug in the initial db format, where we sometimes didn't store - // json... - obj = {{"event_id", std::string(val.data(), val.size())}}; - } - - if (passed_pagination_token) { - if (obj.count("event_id") != 0) { - std::string event_id = obj["event_id"].get<std::string>(); - - if (!event_id.empty()) { - evToOrderDb.del(txn, event_id); - eventsDb.del(txn, event_id); - relationsDb.del(txn, event_id); - - std::string_view order{}; - bool exists = msg2orderDb.get(txn, event_id, order); - if (exists) { - order2msgDb.del(txn, order); - msg2orderDb.del(txn, event_id); - } - } - } - lmdb::cursor_del(cursor); - } else { - if (obj.count("prev_batch") != 0) - passed_pagination_token = true; + try { + obj = json::parse(std::string_view(val.data(), val.size())); + } catch (std::exception &) { + // workaround bug in the initial db format, where we sometimes didn't store + // json... + obj = {{"event_id", std::string(val.data(), val.size())}}; + } + + if (passed_pagination_token) { + if (obj.count("event_id") != 0) { + std::string event_id = obj["event_id"].get<std::string>(); + + if (!event_id.empty()) { + evToOrderDb.del(txn, event_id); + eventsDb.del(txn, event_id); + relationsDb.del(txn, event_id); + + std::string_view order{}; + bool exists = msg2orderDb.get(txn, event_id, order); + if (exists) { + order2msgDb.del(txn, order); + msg2orderDb.del(txn, event_id); + } } + } + lmdb::cursor_del(cursor); + } else { + if (obj.count("prev_batch") != 0) + passed_pagination_token = true; } + } - auto msgCursor = lmdb::cursor::open(txn, order2msgDb); - start = true; - while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { - start = false; - - std::string_view eventId; - bool innerStart = true; - bool found = false; - while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) { - innerStart = false; - - json obj; - try { - obj = json::parse(std::string_view(eventId.data(), eventId.size())); - } catch (std::exception &) { - obj = {{"event_id", std::string(eventId.data(), eventId.size())}}; - } + auto msgCursor = lmdb::cursor::open(txn, order2msgDb); + start = true; + while (msgCursor.get(indexVal, val, start ? MDB_LAST : MDB_PREV)) { + start = false; - if (obj["event_id"] == std::string(val.data(), val.size())) { - found = true; - break; - } - } + std::string_view eventId; + bool innerStart = true; + bool found = false; + while (cursor.get(indexVal, eventId, innerStart ? MDB_LAST : MDB_PREV)) { + innerStart = false; + + json obj; + try { + obj = json::parse(std::string_view(eventId.data(), eventId.size())); + } catch (std::exception &) { + obj = {{"event_id", std::string(eventId.data(), eventId.size())}}; + } - if (!found) - break; + if (obj["event_id"] == std::string(val.data(), val.size())) { + found = true; + break; + } } - do { - lmdb::cursor_del(msgCursor); - } while (msgCursor.get(indexVal, val, MDB_PREV)); + if (!found) + break; + } - cursor.close(); - msgCursor.close(); - txn.commit(); + do { + lmdb::cursor_del(msgCursor); + } while (msgCursor.get(indexVal, val, MDB_PREV)); + + cursor.close(); + msgCursor.close(); + txn.commit(); } mtx::responses::Notifications Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id) { - auto db = getMentionsDb(txn, room_id); + auto db = getMentionsDb(txn, room_id); - if (db.size(txn) == 0) { - return mtx::responses::Notifications{}; - } + if (db.size(txn) == 0) { + return mtx::responses::Notifications{}; + } - mtx::responses::Notifications notif; - std::string_view event_id, msg; + mtx::responses::Notifications notif; + std::string_view event_id, msg; - auto cursor = lmdb::cursor::open(txn, db); + auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(event_id, msg, MDB_NEXT)) { - auto obj = json::parse(msg); + while (cursor.get(event_id, msg, MDB_NEXT)) { + auto obj = json::parse(msg); - if (obj.count("event") == 0) - continue; + if (obj.count("event") == 0) + continue; - mtx::responses::Notification notification; - mtx::responses::from_json(obj, notification); + mtx::responses::Notification notification; + mtx::responses::from_json(obj, notification); - notif.notifications.push_back(notification); - } - cursor.close(); + notif.notifications.push_back(notification); + } + cursor.close(); - std::reverse(notif.notifications.begin(), notif.notifications.end()); + std::reverse(notif.notifications.begin(), notif.notifications.end()); - return notif; + return notif; } //! Add all notifications containing a user mention to the db. void Cache::saveTimelineMentions(const mtx::responses::Notifications &res) { - QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom; + QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom; - // Sort into room-specific 'buckets' - for (const auto ¬if : res.notifications) { - json val = notif; - notifsByRoom[notif.room_id].push_back(notif); - } + // Sort into room-specific 'buckets' + for (const auto ¬if : res.notifications) { + json val = notif; + notifsByRoom[notif.room_id].push_back(notif); + } - auto txn = lmdb::txn::begin(env_); - // Insert the entire set of mentions for each room at a time. - QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it = - notifsByRoom.constBegin(); - auto end = notifsByRoom.constEnd(); - while (it != end) { - nhlog::db()->debug("Storing notifications for " + it.key()); - saveTimelineMentions(txn, it.key(), std::move(it.value())); - ++it; - } + auto txn = lmdb::txn::begin(env_); + // Insert the entire set of mentions for each room at a time. + QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it = + notifsByRoom.constBegin(); + auto end = notifsByRoom.constEnd(); + while (it != end) { + nhlog::db()->debug("Storing notifications for " + it.key()); + saveTimelineMentions(txn, it.key(), std::move(it.value())); + ++it; + } - txn.commit(); + txn.commit(); } void @@ -3148,139 +3254,138 @@ Cache::saveTimelineMentions(lmdb::txn &txn, const std::string &room_id, const QList<mtx::responses::Notification> &res) { - auto db = getMentionsDb(txn, room_id); + auto db = getMentionsDb(txn, room_id); - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - for (const auto ¬if : res) { - const auto event_id = mtx::accessors::event_id(notif.event); + for (const auto ¬if : res) { + const auto event_id = mtx::accessors::event_id(notif.event); - // double check that we have the correct room_id... - if (room_id.compare(notif.room_id) != 0) { - return; - } + // double check that we have the correct room_id... + if (room_id.compare(notif.room_id) != 0) { + return; + } - json obj = notif; + json obj = notif; - db.put(txn, event_id, obj.dump()); - } + db.put(txn, event_id, obj.dump()); + } } void Cache::markSentNotification(const std::string &event_id) { - auto txn = lmdb::txn::begin(env_); - notificationsDb_.put(txn, event_id, ""); - txn.commit(); + auto txn = lmdb::txn::begin(env_); + notificationsDb_.put(txn, event_id, ""); + txn.commit(); } void Cache::removeReadNotification(const std::string &event_id) { - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - notificationsDb_.del(txn, event_id); + notificationsDb_.del(txn, event_id); - txn.commit(); + txn.commit(); } bool Cache::isNotificationSent(const std::string &event_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::string_view value; - bool res = notificationsDb_.get(txn, event_id, value); + std::string_view value; + bool res = notificationsDb_.get(txn, event_id, value); - return res; + return res; } std::vector<std::string> Cache::getRoomIds(lmdb::txn &txn) { - auto db = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE); - auto cursor = lmdb::cursor::open(txn, db); + auto cursor = lmdb::cursor::open(txn, roomsDb_); - std::vector<std::string> rooms; + std::vector<std::string> rooms; - std::string_view room_id, _unused; - while (cursor.get(room_id, _unused, MDB_NEXT)) - rooms.emplace_back(room_id); + std::string_view room_id, _unused; + while (cursor.get(room_id, _unused, MDB_NEXT)) + rooms.emplace_back(room_id); - cursor.close(); + cursor.close(); - return rooms; + return rooms; } void Cache::deleteOldMessages() { - std::string_view indexVal, val; + std::string_view indexVal, val; - auto txn = lmdb::txn::begin(env_); - auto room_ids = getRoomIds(txn); + auto txn = lmdb::txn::begin(env_); + auto room_ids = getRoomIds(txn); - for (const auto &room_id : room_ids) { - auto orderDb = getEventOrderDb(txn, room_id); - auto evToOrderDb = getEventToOrderDb(txn, room_id); - auto o2m = getOrderToMessageDb(txn, room_id); - auto m2o = getMessageToOrderDb(txn, room_id); - auto eventsDb = getEventsDb(txn, room_id); - auto relationsDb = getRelationsDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); + for (const auto &room_id : room_ids) { + auto orderDb = getEventOrderDb(txn, room_id); + auto evToOrderDb = getEventToOrderDb(txn, room_id); + auto o2m = getOrderToMessageDb(txn, room_id); + auto m2o = getMessageToOrderDb(txn, room_id); + auto eventsDb = getEventsDb(txn, room_id); + auto relationsDb = getRelationsDb(txn, room_id); + auto cursor = lmdb::cursor::open(txn, orderDb); - uint64_t first, last; - if (cursor.get(indexVal, val, MDB_LAST)) { - last = lmdb::from_sv<uint64_t>(indexVal); - } else { - continue; - } - if (cursor.get(indexVal, val, MDB_FIRST)) { - first = lmdb::from_sv<uint64_t>(indexVal); - } else { - continue; - } + uint64_t first, last; + if (cursor.get(indexVal, val, MDB_LAST)) { + last = lmdb::from_sv<uint64_t>(indexVal); + } else { + continue; + } + if (cursor.get(indexVal, val, MDB_FIRST)) { + first = lmdb::from_sv<uint64_t>(indexVal); + } else { + continue; + } - size_t message_count = static_cast<size_t>(last - first); - if (message_count < MAX_RESTORED_MESSAGES) - continue; - - 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) { - std::string event_id = obj["event_id"].get<std::string>(); - evToOrderDb.del(txn, event_id); - eventsDb.del(txn, event_id); - - relationsDb.del(txn, event_id); - - std::string_view order{}; - bool exists = m2o.get(txn, event_id, order); - if (exists) { - o2m.del(txn, order); - m2o.del(txn, event_id); - } - } - cursor.del(); + size_t message_count = static_cast<size_t>(last - first); + if (message_count < MAX_RESTORED_MESSAGES) + continue; + + 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) { + std::string event_id = obj["event_id"].get<std::string>(); + evToOrderDb.del(txn, event_id); + eventsDb.del(txn, event_id); + + relationsDb.del(txn, event_id); + + std::string_view order{}; + bool exists = m2o.get(txn, event_id, order); + if (exists) { + o2m.del(txn, order); + m2o.del(txn, event_id); } - cursor.close(); + } + cursor.del(); } - txn.commit(); + cursor.close(); + } + txn.commit(); } void Cache::deleteOldData() noexcept { - try { - deleteOldMessages(); - } catch (const lmdb::error &e) { - nhlog::db()->error("failed to delete old messages: {}", e.what()); - } + try { + deleteOldMessages(); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to delete old messages: {}", e.what()); + } } void @@ -3288,241 +3393,245 @@ Cache::updateSpaces(lmdb::txn &txn, const std::set<std::string> &spaces_with_updates, std::set<std::string> rooms_with_updates) { - if (spaces_with_updates.empty() && rooms_with_updates.empty()) - return; + if (spaces_with_updates.empty() && rooms_with_updates.empty()) + return; - for (const auto &space : spaces_with_updates) { - // delete old entries - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view sp = space, space_child = ""; - - if (cursor.get(sp, space_child, MDB_SET)) { - while (cursor.get( - sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - spacesParentsDb_.del(txn, space_child, space); - } - } - cursor.close(); - spacesChildrenDb_.del(txn, space); + for (const auto &space : spaces_with_updates) { + // delete old entries + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view sp = space, space_child = ""; + + if (cursor.get(sp, space_child, MDB_SET)) { + while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + spacesParentsDb_.del(txn, space_child, space); } + } + cursor.close(); + spacesChildrenDb_.del(txn, space); + } - for (const auto &event : - getStateEventsWithType<mtx::events::state::space::Child>(txn, space)) { - if (event.content.via.has_value() && event.state_key.size() > 3 && - event.state_key.at(0) == '!') { - spacesChildrenDb_.put(txn, space, event.state_key); - spacesParentsDb_.put(txn, event.state_key, space); - } - } + for (const auto &event : + getStateEventsWithType<mtx::events::state::space::Child>(txn, space)) { + if (event.content.via.has_value() && event.state_key.size() > 3 && + event.state_key.at(0) == '!') { + spacesChildrenDb_.put(txn, space, event.state_key); + spacesParentsDb_.put(txn, event.state_key, space); + } } - const auto space_event_type = to_string(mtx::events::EventType::RoomPowerLevels); + for (const auto &r : getRoomIds(txn)) { + if (auto parent = getStateEvent<mtx::events::state::space::Parent>(txn, r, space)) { + rooms_with_updates.insert(r); + } + } + } - for (const auto &room : rooms_with_updates) { - for (const auto &event : - getStateEventsWithType<mtx::events::state::space::Parent>(txn, room)) { - if (event.content.via.has_value() && event.state_key.size() > 3 && - event.state_key.at(0) == '!') { - const std::string &space = event.state_key; + const auto space_event_type = to_string(mtx::events::EventType::SpaceChild); - auto pls = - getStateEvent<mtx::events::state::PowerLevels>(txn, space); + for (const auto &room : rooms_with_updates) { + for (const auto &event : + getStateEventsWithType<mtx::events::state::space::Parent>(txn, room)) { + if (event.content.via.has_value() && event.state_key.size() > 3 && + event.state_key.at(0) == '!') { + const std::string &space = event.state_key; - if (!pls) - continue; + auto pls = getStateEvent<mtx::events::state::PowerLevels>(txn, space); - if (pls->content.user_level(event.sender) >= - pls->content.state_level(space_event_type)) { - spacesChildrenDb_.put(txn, space, room); - spacesParentsDb_.put(txn, room, space); - } - } + if (!pls) + continue; + + if (pls->content.user_level(event.sender) >= + pls->content.state_level(space_event_type)) { + spacesChildrenDb_.put(txn, space, room); + spacesParentsDb_.put(txn, room, space); + } else { + nhlog::db()->debug("Skipping {} in {} because of missing PL. {}: {} < {}", + room, + space, + event.sender, + pls->content.user_level(event.sender), + pls->content.state_level(space_event_type)); } + } } + } } QMap<QString, std::optional<RoomInfo>> Cache::spaces() { - auto txn = ro_txn(env_); - - QMap<QString, std::optional<RoomInfo>> ret; - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view space_id, space_child; - while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) { - first = false; - - if (!space_child.empty()) { - std::string_view room_data; - if (roomsDb_.get(txn, space_id, room_data)) { - RoomInfo tmp = json::parse(std::move(room_data)); - ret.insert( - QString::fromUtf8(space_id.data(), space_id.size()), tmp); - } else { - ret.insert( - QString::fromUtf8(space_id.data(), space_id.size()), - std::nullopt); - } - } + auto txn = ro_txn(env_); + + QMap<QString, std::optional<RoomInfo>> ret; + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view space_id, space_child; + while (cursor.get(space_id, space_child, first ? MDB_FIRST : MDB_NEXT)) { + first = false; + + if (!space_child.empty()) { + std::string_view room_data; + if (roomsDb_.get(txn, space_id, room_data)) { + RoomInfo tmp = json::parse(std::move(room_data)); + ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), tmp); + } else { + ret.insert(QString::fromUtf8(space_id.data(), space_id.size()), std::nullopt); } - cursor.close(); + } } + cursor.close(); + } - return ret; + return ret; } std::vector<std::string> Cache::getParentRoomIds(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector<std::string> roomids; - { - auto cursor = lmdb::cursor::open(txn, spacesParentsDb_); - bool first = true; - std::string_view sp = room_id, space_parent; - if (cursor.get(sp, space_parent, MDB_SET)) { - while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - - if (!space_parent.empty()) - roomids.emplace_back(space_parent); - } - } - cursor.close(); + std::vector<std::string> roomids; + { + auto cursor = lmdb::cursor::open(txn, spacesParentsDb_); + bool first = true; + std::string_view sp = room_id, space_parent; + if (cursor.get(sp, space_parent, MDB_SET)) { + while (cursor.get(sp, space_parent, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + + if (!space_parent.empty()) + roomids.emplace_back(space_parent); + } } + cursor.close(); + } - return roomids; + return roomids; } std::vector<std::string> Cache::getChildRoomIds(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector<std::string> roomids; - { - auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); - bool first = true; - std::string_view sp = room_id, space_child; - if (cursor.get(sp, space_child, MDB_SET)) { - while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { - first = false; - - if (!space_child.empty()) - roomids.emplace_back(space_child); - } - } - cursor.close(); + std::vector<std::string> roomids; + { + auto cursor = lmdb::cursor::open(txn, spacesChildrenDb_); + bool first = true; + std::string_view sp = room_id, space_child; + if (cursor.get(sp, space_child, MDB_SET)) { + while (cursor.get(sp, space_child, first ? MDB_FIRST_DUP : MDB_NEXT_DUP)) { + first = false; + + if (!space_child.empty()) + roomids.emplace_back(space_child); + } } + cursor.close(); + } - return roomids; + return roomids; } std::vector<ImagePackInfo> Cache::getImagePacks(const std::string &room_id, std::optional<bool> stickers) { - auto txn = ro_txn(env_); - std::vector<ImagePackInfo> infos; - - auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, - const std::string &source_room, - const std::string &state_key) { - if (!pack.pack || !stickers.has_value() || - (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { - ImagePackInfo info; - info.source_room = source_room; - info.state_key = state_key; - info.pack.pack = pack.pack; - - for (const auto &img : pack.images) { - if (stickers.has_value() && img.second.overrides_usage() && - (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) - continue; - - info.pack.images.insert(img); - } - - if (!info.pack.images.empty()) - infos.push_back(std::move(info)); + auto txn = ro_txn(env_); + std::vector<ImagePackInfo> infos; + + auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, + const std::string &source_room, + const std::string &state_key) { + if (!pack.pack || !stickers.has_value() || + (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { + ImagePackInfo info; + info.source_room = source_room; + info.state_key = state_key; + info.pack.pack = pack.pack; + + for (const auto &img : pack.images) { + if (stickers.has_value() && img.second.overrides_usage() && + (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) + continue; + + info.pack.images.insert(img); + } + + if (!info.pack.images.empty()) + infos.push_back(std::move(info)); + } + }; + + // packs from account data + if (auto accountpack = + getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) { + auto tmp = + std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(&*accountpack); + if (tmp) + addPack(tmp->content, "", ""); + } + + // packs from rooms, that were enabled globally + if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) { + auto tmp = std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>( + &*roomPacks); + if (tmp) { + for (const auto &[room_id2, state_to_d] : tmp->content.rooms) { + // don't add stickers from this room twice + if (room_id2 == room_id) + continue; + + for (const auto &[state_id, d] : state_to_d) { + (void)d; + if (auto pack = + getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id2, state_id)) + addPack(pack->content, room_id2, state_id); } - }; - - // packs from account data - if (auto accountpack = - getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) { - auto tmp = - std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>( - &*accountpack); - if (tmp) - addPack(tmp->content, "", ""); + } } + } - // packs from rooms, that were enabled globally - if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) { - auto tmp = - std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>( - &*roomPacks); - if (tmp) { - for (const auto &[room_id2, state_to_d] : tmp->content.rooms) { - // don't add stickers from this room twice - if (room_id2 == room_id) - continue; - - for (const auto &[state_id, d] : state_to_d) { - (void)d; - if (auto pack = - getStateEvent<mtx::events::msc2545::ImagePack>( - txn, room_id2, state_id)) - addPack(pack->content, room_id2, state_id); - } - } - } - } + // packs from current room + if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) { + addPack(pack->content, room_id, ""); + } + for (const auto &pack : getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) { + addPack(pack.content, room_id, pack.state_key); + } - // packs from current room - if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) { - addPack(pack->content, room_id, ""); - } - for (const auto &pack : - getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) { - addPack(pack.content, room_id, pack.state_key); - } - - return infos; + return infos; } std::optional<mtx::events::collections::RoomAccountDataEvents> Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) { - auto txn = ro_txn(env_); - return getAccountData(txn, type, room_id); + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); } std::optional<mtx::events::collections::RoomAccountDataEvents> Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { - try { - auto db = getAccountDataDb(txn, room_id); - - std::string_view data; - if (db.get(txn, to_string(type), data)) { - mtx::responses::utils::RoomAccountDataEvents events; - json j = json::array({ - json::parse(data), - }); - mtx::responses::utils::parse_room_account_data_events(j, events); - if (events.size() == 1) - return events.front(); - } - } catch (...) { + try { + auto db = getAccountDataDb(txn, room_id); + + std::string_view data; + if (db.get(txn, to_string(type), data)) { + mtx::responses::utils::RoomAccountDataEvents events; + json j = json::array({ + json::parse(data), + }); + mtx::responses::utils::parse_room_account_data_events(j, events); + if (events.size() == 1) + return events.front(); } - return std::nullopt; + } catch (...) { + } + return std::nullopt; } bool @@ -3530,470 +3639,446 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes const std::string &room_id, const std::string &user_id) { - using namespace mtx::events; - using namespace mtx::events::state; + using namespace mtx::events; + using namespace mtx::events::state; - auto txn = lmdb::txn::begin(env_); - auto db = getStatesDb(txn, room_id); + auto txn = lmdb::txn::begin(env_); + auto db = getStatesDb(txn, room_id); - int64_t min_event_level = std::numeric_limits<int64_t>::max(); - int64_t user_level = std::numeric_limits<int64_t>::min(); + int64_t min_event_level = std::numeric_limits<int64_t>::max(); + int64_t user_level = std::numeric_limits<int64_t>::min(); - std::string_view event; - bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); + std::string_view event; + bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); - if (res) { - try { - StateEvent<PowerLevels> msg = - json::parse(std::string_view(event.data(), event.size())); + if (res) { + try { + StateEvent<PowerLevels> msg = json::parse(std::string_view(event.data(), event.size())); - user_level = msg.content.user_level(user_id); + user_level = msg.content.user_level(user_id); - for (const auto &ty : eventTypes) - min_event_level = - std::min(min_event_level, msg.content.state_level(to_string(ty))); - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.power_levels event: {}", - e.what()); - } + for (const auto &ty : eventTypes) + min_event_level = std::min(min_event_level, msg.content.state_level(to_string(ty))); + } catch (const json::exception &e) { + nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); } + } - txn.commit(); + txn.commit(); - return user_level >= min_event_level; + return user_level >= min_event_level; } std::vector<std::string> Cache::roomMembers(const std::string &room_id) { - auto txn = ro_txn(env_); + auto txn = ro_txn(env_); - std::vector<std::string> members; - std::string_view user_id, unused; + std::vector<std::string> members; + std::string_view user_id, unused; - auto db = getMembersDb(txn, room_id); + auto db = getMembersDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) - members.emplace_back(user_id); - cursor.close(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) + members.emplace_back(user_id); + cursor.close(); - return members; + return members; } crypto::Trust Cache::roomVerificationStatus(const std::string &room_id) { - crypto::Trust trust = crypto::Verified; + crypto::Trust trust = crypto::Verified; - try { - auto txn = lmdb::txn::begin(env_); - - auto db = getMembersDb(txn, room_id); - auto keysDb = getUserKeysDb(txn); - std::vector<std::string> keysToRequest; - - std::string_view user_id, unused; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) { - auto verif = verificationStatus_(std::string(user_id), txn); - if (verif.unverified_device_count) { - trust = crypto::Unverified; - if (verif.verified_devices.empty() && verif.no_keys) { - // we probably don't have the keys yet, so query them - keysToRequest.push_back(std::string(user_id)); - } - } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified) - trust = crypto::TOFU; - } + try { + auto txn = lmdb::txn::begin(env_); - if (!keysToRequest.empty()) - markUserKeysOutOfDate(txn, keysDb, keysToRequest, ""); + auto db = getMembersDb(txn, room_id); + auto keysDb = getUserKeysDb(txn); + std::vector<std::string> keysToRequest; - } catch (std::exception &e) { - nhlog::db()->error( - "Failed to calculate verification status for {}: {}", room_id, e.what()); + std::string_view user_id, unused; + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) { + auto verif = verificationStatus_(std::string(user_id), txn); + if (verif.unverified_device_count) { trust = crypto::Unverified; + if (verif.verified_devices.empty() && verif.no_keys) { + // we probably don't have the keys yet, so query them + keysToRequest.push_back(std::string(user_id)); + } + } else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified) + trust = crypto::TOFU; } - return trust; + if (!keysToRequest.empty()) + markUserKeysOutOfDate(txn, keysDb, keysToRequest, ""); + + } catch (std::exception &e) { + nhlog::db()->error("Failed to calculate verification status for {}: {}", room_id, e.what()); + trust = crypto::Unverified; + } + + return trust; } std::map<std::string, std::optional<UserKeyCache>> Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) { - std::string_view keys; + std::string_view keys; - try { - auto txn = ro_txn(env_); - std::map<std::string, std::optional<UserKeyCache>> members; - - auto db = getMembersDb(txn, room_id); - auto keysDb = getUserKeysDb(txn); - - std::string_view user_id, unused; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) { - auto res = keysDb.get(txn, user_id, keys); - - if (res) { - auto k = json::parse(keys).get<UserKeyCache>(); - if (verified_only) { - auto verif = verificationStatus(std::string(user_id)); - if (verif.user_verified == crypto::Trust::Verified || - !verif.verified_devices.empty()) { - auto keyCopy = k; - keyCopy.device_keys.clear(); - - std::copy_if( - k.device_keys.begin(), - k.device_keys.end(), - std::inserter(keyCopy.device_keys, - keyCopy.device_keys.end()), - [&verif](const auto &key) { - auto curve25519 = key.second.keys.find( - "curve25519:" + key.first); - if (curve25519 == key.second.keys.end()) - return false; - if (auto t = - verif.verified_device_keys.find( - curve25519->second); - t == - verif.verified_device_keys.end() || - t->second != crypto::Trust::Verified) - return false; - - return key.first == - key.second.device_id && - std::find( - verif.verified_devices.begin(), - verif.verified_devices.end(), - key.first) != - verif.verified_devices.end(); - }); - - if (!keyCopy.device_keys.empty()) - members[std::string(user_id)] = - std::move(keyCopy); - } - } else { - members[std::string(user_id)] = std::move(k); - } - } else { - if (!verified_only) - members[std::string(user_id)] = {}; - } - } - cursor.close(); + try { + auto txn = ro_txn(env_); + std::map<std::string, std::optional<UserKeyCache>> members; - return members; - } catch (std::exception &) { - return {}; + auto db = getMembersDb(txn, room_id); + auto keysDb = getUserKeysDb(txn); + + std::string_view user_id, unused; + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(user_id, unused, MDB_NEXT)) { + auto res = keysDb.get(txn, user_id, keys); + + if (res) { + auto k = json::parse(keys).get<UserKeyCache>(); + if (verified_only) { + auto verif = verificationStatus_(std::string(user_id), txn); + + if (verif.user_verified == crypto::Trust::Verified || + !verif.verified_devices.empty()) { + auto keyCopy = k; + keyCopy.device_keys.clear(); + + std::copy_if( + k.device_keys.begin(), + k.device_keys.end(), + std::inserter(keyCopy.device_keys, keyCopy.device_keys.end()), + [&verif](const auto &key) { + auto curve25519 = key.second.keys.find("curve25519:" + key.first); + if (curve25519 == key.second.keys.end()) + return false; + if (auto t = verif.verified_device_keys.find(curve25519->second); + t == verif.verified_device_keys.end() || + t->second != crypto::Trust::Verified) + return false; + + return key.first == key.second.device_id && + std::find(verif.verified_devices.begin(), + verif.verified_devices.end(), + key.first) != verif.verified_devices.end(); + }); + + if (!keyCopy.device_keys.empty()) + members[std::string(user_id)] = std::move(keyCopy); + } + } else { + members[std::string(user_id)] = std::move(k); + } + } else { + if (!verified_only) + members[std::string(user_id)] = {}; + } } + cursor.close(); + + return members; + } catch (std::exception &e) { + nhlog::db()->debug("Error retrieving members: {}", e.what()); + return {}; + } } QString Cache::displayName(const QString &room_id, const QString &user_id) { - if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); - info && !info->name.empty()) - return QString::fromStdString(info->name); + if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); + info && !info->name.empty()) + return QString::fromStdString(info->name); - return user_id; + return user_id; } std::string Cache::displayName(const std::string &room_id, const std::string &user_id) { - if (auto info = getMember(room_id, user_id); info && !info->name.empty()) - return info->name; + if (auto info = getMember(room_id, user_id); info && !info->name.empty()) + return info->name; - return user_id; + return user_id; } QString Cache::avatarUrl(const QString &room_id, const QString &user_id) { - if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); - info && !info->avatar_url.empty()) - return QString::fromStdString(info->avatar_url); + if (auto info = getMember(room_id.toStdString(), user_id.toStdString()); + info && !info->avatar_url.empty()) + return QString::fromStdString(info->avatar_url); - return ""; + return ""; } mtx::presence::PresenceState Cache::presenceState(const std::string &user_id) { - if (user_id.empty()) - return {}; + if (user_id.empty()) + return {}; - std::string_view presenceVal; + std::string_view presenceVal; - auto txn = lmdb::txn::begin(env_); - auto db = getPresenceDb(txn); - auto res = db.get(txn, user_id, presenceVal); + auto txn = lmdb::txn::begin(env_); + auto db = getPresenceDb(txn); + auto res = db.get(txn, user_id, presenceVal); - mtx::presence::PresenceState state = mtx::presence::offline; + mtx::presence::PresenceState state = mtx::presence::offline; - if (res) { - mtx::events::presence::Presence presence = - json::parse(std::string_view(presenceVal.data(), presenceVal.size())); - state = presence.presence; - } + if (res) { + mtx::events::presence::Presence presence = + json::parse(std::string_view(presenceVal.data(), presenceVal.size())); + state = presence.presence; + } - txn.commit(); + txn.commit(); - return state; + return state; } std::string Cache::statusMessage(const std::string &user_id) { - if (user_id.empty()) - return {}; + if (user_id.empty()) + return {}; - std::string_view presenceVal; + std::string_view presenceVal; - auto txn = lmdb::txn::begin(env_); - auto db = getPresenceDb(txn); - auto res = db.get(txn, user_id, presenceVal); + auto txn = lmdb::txn::begin(env_); + auto db = getPresenceDb(txn); + auto res = db.get(txn, user_id, presenceVal); - std::string status_msg; + std::string status_msg; - if (res) { - mtx::events::presence::Presence presence = json::parse(presenceVal); - status_msg = presence.status_msg; - } + if (res) { + mtx::events::presence::Presence presence = json::parse(presenceVal); + status_msg = presence.status_msg; + } - txn.commit(); + txn.commit(); - return status_msg; + return status_msg; } void to_json(json &j, const UserKeyCache &info) { - j["device_keys"] = info.device_keys; - j["seen_device_keys"] = info.seen_device_keys; - j["seen_device_ids"] = info.seen_device_ids; - j["master_keys"] = info.master_keys; - j["master_key_changed"] = info.master_key_changed; - j["user_signing_keys"] = info.user_signing_keys; - j["self_signing_keys"] = info.self_signing_keys; - j["updated_at"] = info.updated_at; - j["last_changed"] = info.last_changed; + j["device_keys"] = info.device_keys; + j["seen_device_keys"] = info.seen_device_keys; + j["seen_device_ids"] = info.seen_device_ids; + j["master_keys"] = info.master_keys; + j["master_key_changed"] = info.master_key_changed; + j["user_signing_keys"] = info.user_signing_keys; + j["self_signing_keys"] = info.self_signing_keys; + j["updated_at"] = info.updated_at; + j["last_changed"] = info.last_changed; } void from_json(const json &j, UserKeyCache &info) { - info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{}); - info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{}); - info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{}); - info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); - info.master_key_changed = j.value("master_key_changed", false); - info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); - info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); - info.updated_at = j.value("updated_at", ""); - info.last_changed = j.value("last_changed", ""); + info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{}); + info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{}); + info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{}); + info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{}); + info.master_key_changed = j.value("master_key_changed", false); + info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.self_signing_keys = j.value("self_signing_keys", mtx::crypto::CrossSigningKeys{}); + info.updated_at = j.value("updated_at", ""); + info.last_changed = j.value("last_changed", ""); } std::optional<UserKeyCache> Cache::userKeys(const std::string &user_id) { - auto txn = ro_txn(env_); - return userKeys_(user_id, txn); + auto txn = ro_txn(env_); + return userKeys_(user_id, txn); } std::optional<UserKeyCache> Cache::userKeys_(const std::string &user_id, lmdb::txn &txn) { - std::string_view keys; + std::string_view keys; - try { - auto db = getUserKeysDb(txn); - auto res = db.get(txn, user_id, keys); + try { + auto db = getUserKeysDb(txn); + auto res = db.get(txn, user_id, keys); - if (res) { - return json::parse(keys).get<UserKeyCache>(); - } else { - return {}; - } - } catch (std::exception &e) { - nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what()); - return {}; + if (res) { + return json::parse(keys).get<UserKeyCache>(); + } else { + return {}; } + } catch (std::exception &e) { + nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what()); + return {}; + } } void Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - auto txn = lmdb::txn::begin(env_); - auto db = getUserKeysDb(txn); + auto txn = lmdb::txn::begin(env_); + auto db = getUserKeysDb(txn); - std::map<std::string, UserKeyCache> updates; - - for (const auto &[user, keys] : keyQuery.device_keys) - updates[user].device_keys = keys; - for (const auto &[user, keys] : keyQuery.master_keys) - updates[user].master_keys = keys; - for (const auto &[user, keys] : keyQuery.user_signing_keys) - updates[user].user_signing_keys = keys; - for (const auto &[user, keys] : keyQuery.self_signing_keys) - updates[user].self_signing_keys = keys; - - for (auto &[user, update] : updates) { - nhlog::db()->debug("Updated user keys: {}", user); - - auto updateToWrite = update; - - std::string_view oldKeys; - auto res = db.get(txn, user, oldKeys); - - if (res) { - updateToWrite = json::parse(oldKeys).get<UserKeyCache>(); - auto last_changed = updateToWrite.last_changed; - // skip if we are tracking this and expect it to be up to date with the last - // sync token - if (!last_changed.empty() && last_changed != sync_token) { - nhlog::db()->debug("Not storing update for user {}, because " - "last_changed {}, but we fetched update for {}", - user, - last_changed, - sync_token); - continue; - } + std::map<std::string, UserKeyCache> updates; + + for (const auto &[user, keys] : keyQuery.device_keys) + updates[user].device_keys = keys; + for (const auto &[user, keys] : keyQuery.master_keys) + updates[user].master_keys = keys; + for (const auto &[user, keys] : keyQuery.user_signing_keys) + updates[user].user_signing_keys = keys; + for (const auto &[user, keys] : keyQuery.self_signing_keys) + updates[user].self_signing_keys = keys; - if (!updateToWrite.master_keys.keys.empty() && - update.master_keys.keys != updateToWrite.master_keys.keys) { - nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}", - user, - updateToWrite.master_keys.keys.size(), - update.master_keys.keys.size()); - updateToWrite.master_key_changed = true; + for (auto &[user, update] : updates) { + nhlog::db()->debug("Updated user keys: {}", user); + + auto updateToWrite = update; + + std::string_view oldKeys; + auto res = db.get(txn, user, oldKeys); + + if (res) { + updateToWrite = json::parse(oldKeys).get<UserKeyCache>(); + auto last_changed = updateToWrite.last_changed; + // skip if we are tracking this and expect it to be up to date with the last + // sync token + if (!last_changed.empty() && last_changed != sync_token) { + nhlog::db()->debug("Not storing update for user {}, because " + "last_changed {}, but we fetched update for {}", + user, + last_changed, + sync_token); + continue; + } + + if (!updateToWrite.master_keys.keys.empty() && + update.master_keys.keys != updateToWrite.master_keys.keys) { + nhlog::db()->debug("Master key of {} changed:\nold: {}\nnew: {}", + user, + updateToWrite.master_keys.keys.size(), + update.master_keys.keys.size()); + updateToWrite.master_key_changed = true; + } + + updateToWrite.master_keys = update.master_keys; + updateToWrite.self_signing_keys = update.self_signing_keys; + updateToWrite.user_signing_keys = update.user_signing_keys; + + auto oldDeviceKeys = std::move(updateToWrite.device_keys); + updateToWrite.device_keys.clear(); + + // Don't insert keys, which we have seen once already + for (const auto &[device_id, device_keys] : update.device_keys) { + if (oldDeviceKeys.count(device_id) && + oldDeviceKeys.at(device_id).keys == device_keys.keys) { + // this is safe, since the keys are the same + updateToWrite.device_keys[device_id] = device_keys; + } else { + bool keyReused = false; + for (const auto &[key_id, key] : device_keys.keys) { + (void)key_id; + if (updateToWrite.seen_device_keys.count(key)) { + nhlog::crypto()->warn( + "Key '{}' reused by ({}: {})", key, user, device_id); + keyReused = true; + break; + } + if (updateToWrite.seen_device_ids.count(device_id)) { + nhlog::crypto()->warn("device_id '{}' reused by ({})", device_id, user); + keyReused = true; + break; + } + } + + if (!keyReused && !oldDeviceKeys.count(device_id)) { + // ensure the key has a valid signature from itself + std::string device_signing_key = "ed25519:" + device_keys.device_id; + if (device_id != device_keys.device_id) { + nhlog::crypto()->warn("device {}:{} has a different device id " + "in the body: {}", + user, + device_id, + device_keys.device_id); + continue; + } + if (!device_keys.signatures.count(user) || + !device_keys.signatures.at(user).count(device_signing_key)) { + nhlog::crypto()->warn("device {}:{} has no signature", user, device_id); + continue; } - updateToWrite.master_keys = update.master_keys; - updateToWrite.self_signing_keys = update.self_signing_keys; - updateToWrite.user_signing_keys = update.user_signing_keys; - - auto oldDeviceKeys = std::move(updateToWrite.device_keys); - updateToWrite.device_keys.clear(); - - // Don't insert keys, which we have seen once already - for (const auto &[device_id, device_keys] : update.device_keys) { - if (oldDeviceKeys.count(device_id) && - oldDeviceKeys.at(device_id).keys == device_keys.keys) { - // this is safe, since the keys are the same - updateToWrite.device_keys[device_id] = device_keys; - } else { - bool keyReused = false; - for (const auto &[key_id, key] : device_keys.keys) { - (void)key_id; - if (updateToWrite.seen_device_keys.count(key)) { - nhlog::crypto()->warn( - "Key '{}' reused by ({}: {})", - key, - user, - device_id); - keyReused = true; - break; - } - if (updateToWrite.seen_device_ids.count( - device_id)) { - nhlog::crypto()->warn( - "device_id '{}' reused by ({})", - device_id, - user); - keyReused = true; - break; - } - } - - if (!keyReused && !oldDeviceKeys.count(device_id)) { - // ensure the key has a valid signature from itself - std::string device_signing_key = - "ed25519:" + device_keys.device_id; - if (device_id != device_keys.device_id) { - nhlog::crypto()->warn( - "device {}:{} has a different device id " - "in the body: {}", - user, - device_id, - device_keys.device_id); - continue; - } - if (!device_keys.signatures.count(user) || - !device_keys.signatures.at(user).count( - device_signing_key)) { - nhlog::crypto()->warn( - "device {}:{} has no signature", - user, - device_id); - continue; - } - - if (!mtx::crypto::ed25519_verify_signature( - device_keys.keys.at(device_signing_key), - json(device_keys), - device_keys.signatures.at(user).at( - device_signing_key))) { - nhlog::crypto()->warn( - "device {}:{} has an invalid signature", - user, - device_id); - continue; - } - - updateToWrite.device_keys[device_id] = device_keys; - } - } - - for (const auto &[key_id, key] : device_keys.keys) { - (void)key_id; - updateToWrite.seen_device_keys.insert(key); - } - updateToWrite.seen_device_ids.insert(device_id); + if (!mtx::crypto::ed25519_verify_signature( + device_keys.keys.at(device_signing_key), + json(device_keys), + device_keys.signatures.at(user).at(device_signing_key))) { + nhlog::crypto()->warn( + "device {}:{} has an invalid signature", user, device_id); + continue; } + + updateToWrite.device_keys[device_id] = device_keys; + } + } + + for (const auto &[key_id, key] : device_keys.keys) { + (void)key_id; + updateToWrite.seen_device_keys.insert(key); } - updateToWrite.updated_at = sync_token; - db.put(txn, user, json(updateToWrite).dump()); + updateToWrite.seen_device_ids.insert(device_id); + } } + updateToWrite.updated_at = sync_token; + db.put(txn, user, json(updateToWrite).dump()); + } - txn.commit(); + txn.commit(); - std::map<std::string, VerificationStatus> tmp; - const auto local_user = utils::localUser().toStdString(); + std::map<std::string, VerificationStatus> tmp; + const auto local_user = utils::localUser().toStdString(); - { - std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); - for (auto &[user_id, update] : updates) { - (void)update; - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + { + std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); + for (auto &[user_id, update] : updates) { + (void)update; + if (user_id == local_user) { + std::swap(tmp, verification_storage.status); + } else { + verification_storage.status.erase(user_id); + } } + } - for (auto &[user_id, update] : updates) { - (void)update; - if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } - } - emit verificationStatusChanged(user_id); + for (auto &[user_id, update] : updates) { + (void)update; + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); + } } + emit verificationStatusChanged(user_id); + } } void -Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids) +Cache::markUserKeysOutOfDate(const std::vector<std::string> &user_ids) { - for (const auto &user_id : user_ids) - db.del(txn, user_id); + auto currentBatchToken = nextBatchToken(); + auto txn = lmdb::txn::begin(env_); + auto db = getUserKeysDb(txn); + markUserKeysOutOfDate(txn, db, user_ids, currentBatchToken); + txn.commit(); } void @@ -4002,752 +4087,771 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn, const std::vector<std::string> &user_ids, const std::string &sync_token) { - mtx::requests::QueryKeys query; - query.token = sync_token; + mtx::requests::QueryKeys query; + query.token = sync_token; - for (const auto &user : user_ids) { - nhlog::db()->debug("Marking user keys out of date: {}", user); + for (const auto &user : user_ids) { + nhlog::db()->debug("Marking user keys out of date: {}", user); - std::string_view oldKeys; + std::string_view oldKeys; - UserKeyCache cacheEntry; - auto res = db.get(txn, user, oldKeys); - if (res) { - cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size())) - .get<UserKeyCache>(); - } - cacheEntry.last_changed = sync_token; - - db.put(txn, user, json(cacheEntry).dump()); - - query.device_keys[user] = {}; + UserKeyCache cacheEntry; + auto res = db.get(txn, user, oldKeys); + if (res) { + cacheEntry = + json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get<UserKeyCache>(); } + cacheEntry.last_changed = sync_token; + + db.put(txn, user, json(cacheEntry).dump()); - if (!query.device_keys.empty()) - http::client()->query_keys(query, - [this, sync_token](const mtx::responses::QueryKeys &keys, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to query device keys: {} {}", - err->matrix_error.error, - static_cast<int>(err->status_code)); - return; - } + query.device_keys[user] = {}; + } - emit userKeysUpdate(sync_token, keys); - }); + if (!query.device_keys.empty()) + http::client()->query_keys( + query, + [this, sync_token](const mtx::responses::QueryKeys &keys, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + emit userKeysUpdate(sync_token, keys); + }); } void Cache::query_keys(const std::string &user_id, std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb) { - mtx::requests::QueryKeys req; - std::string last_changed; - { - auto txn = ro_txn(env_); - auto cache_ = userKeys_(user_id, txn); - - if (cache_.has_value()) { - if (cache_->updated_at == cache_->last_changed) { - cb(cache_.value(), {}); - return; - } else - nhlog::db()->info("Keys outdated for {}: {} vs {}", - user_id, - cache_->updated_at, - cache_->last_changed); - } else - nhlog::db()->info("No keys found for {}", user_id); - - req.device_keys[user_id] = {}; - - if (cache_) - last_changed = cache_->last_changed; - req.token = last_changed; - } - - // use context object so that we can disconnect again - QObject *context{new QObject(this)}; - QObject::connect( - this, - &Cache::verificationStatusChanged, - context, - [cb, user_id, context_ = context, this](std::string updated_user) mutable { - if (user_id == updated_user) { - context_->deleteLater(); - auto txn = ro_txn(env_); - auto keys = this->userKeys_(user_id, txn); - cb(keys.value_or(UserKeyCache{}), {}); - } - }, - Qt::QueuedConnection); + mtx::requests::QueryKeys req; + std::string last_changed; + { + auto txn = ro_txn(env_); + auto cache_ = userKeys_(user_id, txn); - http::client()->query_keys( - req, - [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - mtx::errors::to_string(err->matrix_error.errcode), - static_cast<int>(err->status_code)); - cb({}, err); - return; - } - - emit userKeysUpdate(last_changed, res); - }); + if (cache_.has_value()) { + if (cache_->updated_at == cache_->last_changed) { + cb(cache_.value(), {}); + return; + } else + nhlog::db()->info("Keys outdated for {}: {} vs {}", + user_id, + cache_->updated_at, + cache_->last_changed); + } else + nhlog::db()->info("No keys found for {}", user_id); + + req.device_keys[user_id] = {}; + + if (cache_) + last_changed = cache_->last_changed; + req.token = last_changed; + } + + // use context object so that we can disconnect again + QObject *context{new QObject(this)}; + QObject::connect( + this, + &Cache::verificationStatusChanged, + context, + [cb, user_id, context_ = context, this](std::string updated_user) mutable { + if (user_id == updated_user) { + context_->deleteLater(); + auto txn = ro_txn(env_); + auto keys = this->userKeys_(user_id, txn); + cb(keys.value_or(UserKeyCache{}), {}); + } + }, + Qt::QueuedConnection); + + http::client()->query_keys( + req, + [cb, user_id, last_changed, this](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + mtx::errors::to_string(err->matrix_error.errcode), + static_cast<int>(err->status_code)); + cb({}, err); + return; + } + + emit userKeysUpdate(last_changed, res); + }); } void to_json(json &j, const VerificationCache &info) { - j["device_verified"] = info.device_verified; - j["device_blocked"] = info.device_blocked; + j["device_verified"] = info.device_verified; + j["device_blocked"] = info.device_blocked; } void from_json(const json &j, VerificationCache &info) { - info.device_verified = j.at("device_verified").get<std::set<std::string>>(); - info.device_blocked = j.at("device_blocked").get<std::set<std::string>>(); + info.device_verified = j.at("device_verified").get<std::set<std::string>>(); + info.device_blocked = j.at("device_blocked").get<std::set<std::string>>(); +} + +void +to_json(json &j, const OnlineBackupVersion &info) +{ + j["v"] = info.version; + j["a"] = info.algorithm; +} + +void +from_json(const json &j, OnlineBackupVersion &info) +{ + info.version = j.at("v").get<std::string>(); + info.algorithm = j.at("a").get<std::string>(); } std::optional<VerificationCache> Cache::verificationCache(const std::string &user_id, lmdb::txn &txn) { - std::string_view verifiedVal; + std::string_view verifiedVal; - auto db = getVerificationDb(txn); + auto db = getVerificationDb(txn); - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, verifiedVal); - if (res) { - verified_state = json::parse(verifiedVal); - return verified_state; - } else { - return {}; - } - } catch (std::exception &) { - return {}; + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, verifiedVal); + if (res) { + verified_state = json::parse(verifiedVal); + return verified_state; + } else { + return {}; } + } catch (std::exception &) { + return {}; + } } void Cache::markDeviceVerified(const std::string &user_id, const std::string &key) { - { - std::string_view val; - - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); - - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, val); - if (res) { - verified_state = json::parse(val); - } + { + std::string_view val; - for (const auto &device : verified_state.device_verified) - if (device == key) - return; + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); - verified_state.device_verified.insert(key); - db.put(txn, user_id, json(verified_state).dump()); - txn.commit(); - } catch (std::exception &) { - } + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, val); + if (res) { + verified_state = json::parse(val); + } + + for (const auto &device : verified_state.device_verified) + if (device == key) + return; + + verified_state.device_verified.insert(key); + db.put(txn, user_id, json(verified_state).dump()); + txn.commit(); + } catch (std::exception &) { } + } - const auto local_user = utils::localUser().toStdString(); - std::map<std::string, VerificationStatus> tmp; - { - std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + const auto local_user = utils::localUser().toStdString(); + std::map<std::string, VerificationStatus> tmp; + { + std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } + std::swap(tmp, verification_storage.status); + verification_storage.status.clear(); } else { - emit verificationStatusChanged(user_id); + verification_storage.status.erase(user_id); } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); + } + } + emit verificationStatusChanged(user_id); } void Cache::markDeviceUnverified(const std::string &user_id, const std::string &key) { - std::string_view val; + std::string_view val; - auto txn = lmdb::txn::begin(env_); - auto db = getVerificationDb(txn); + auto txn = lmdb::txn::begin(env_); + auto db = getVerificationDb(txn); - try { - VerificationCache verified_state; - auto res = db.get(txn, user_id, val); - if (res) { - verified_state = json::parse(val); - } + try { + VerificationCache verified_state; + auto res = db.get(txn, user_id, val); + if (res) { + verified_state = json::parse(val); + } - verified_state.device_verified.erase(key); + verified_state.device_verified.erase(key); - db.put(txn, user_id, json(verified_state).dump()); - txn.commit(); - } catch (std::exception &) { - } + db.put(txn, user_id, json(verified_state).dump()); + txn.commit(); + } catch (std::exception &) { + } - const auto local_user = utils::localUser().toStdString(); - std::map<std::string, VerificationStatus> tmp; - { - std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); - if (user_id == local_user) { - std::swap(tmp, verification_storage.status); - } else { - verification_storage.status.erase(user_id); - } - } + const auto local_user = utils::localUser().toStdString(); + std::map<std::string, VerificationStatus> tmp; + { + std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); if (user_id == local_user) { - for (const auto &[user, status] : tmp) { - (void)status; - emit verificationStatusChanged(user); - } + std::swap(tmp, verification_storage.status); } else { - emit verificationStatusChanged(user_id); + verification_storage.status.erase(user_id); + } + } + if (user_id == local_user) { + for (const auto &[user, status] : tmp) { + (void)status; + emit verificationStatusChanged(user); } + } + emit verificationStatusChanged(user_id); } VerificationStatus Cache::verificationStatus(const std::string &user_id) { - auto txn = ro_txn(env_); - return verificationStatus_(user_id, txn); + auto txn = ro_txn(env_); + return verificationStatus_(user_id, txn); } VerificationStatus Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn) { - std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); - if (verification_storage.status.count(user_id)) - return verification_storage.status.at(user_id); + std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx); + if (verification_storage.status.count(user_id)) + return verification_storage.status.at(user_id); - VerificationStatus status; + VerificationStatus status; - // assume there is at least one unverified device until we have checked we have the device - // list for that user. - status.unverified_device_count = 1; - status.no_keys = true; + // assume there is at least one unverified device until we have checked we have the device + // list for that user. + status.unverified_device_count = 1; + status.no_keys = true; - if (auto verifCache = verificationCache(user_id, txn)) { - status.verified_devices = verifCache->device_verified; - } + if (auto verifCache = verificationCache(user_id, txn)) { + status.verified_devices = verifCache->device_verified; + } - const auto local_user = utils::localUser().toStdString(); + const auto local_user = utils::localUser().toStdString(); - crypto::Trust trustlevel = crypto::Trust::Unverified; - if (user_id == local_user) { - status.verified_devices.insert(http::client()->device_id()); - trustlevel = crypto::Trust::Verified; - } + crypto::Trust trustlevel = crypto::Trust::Unverified; + if (user_id == local_user) { + status.verified_devices.insert(http::client()->device_id()); + trustlevel = crypto::Trust::Verified; + } - auto verifyAtLeastOneSig = [](const auto &toVerif, - const std::map<std::string, std::string> &keys, - const std::string &keyOwner) { - if (!toVerif.signatures.count(keyOwner)) - return false; + auto verifyAtLeastOneSig = [](const auto &toVerif, + const std::map<std::string, std::string> &keys, + const std::string &keyOwner) { + if (!toVerif.signatures.count(keyOwner)) + return false; - for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { - if (!keys.count(key_id)) - continue; + for (const auto &[key_id, signature] : toVerif.signatures.at(keyOwner)) { + if (!keys.count(key_id)) + continue; - if (mtx::crypto::ed25519_verify_signature( - keys.at(key_id), json(toVerif), signature)) - return true; - } - return false; - }; + if (mtx::crypto::ed25519_verify_signature(keys.at(key_id), json(toVerif), signature)) + return true; + } + return false; + }; + + auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) { + int currentVerifiedDevices = 0; + for (auto device_id : status.verified_devices) { + if (theirDeviceKeys.count(device_id)) + currentVerifiedDevices++; + } + status.unverified_device_count = + static_cast<int>(theirDeviceKeys.size()) - currentVerifiedDevices; + }; + + try { + // for local user verify this device_key -> our master_key -> our self_signing_key + // -> our device_keys + // + // for other user verify this device_key -> our master_key -> our user_signing_key + // -> their master_key -> their self_signing_key -> their device_keys + // + // This means verifying the other user adds 2 extra steps,verifying our user_signing + // key and their master key + auto ourKeys = userKeys_(local_user, txn); + auto theirKeys = userKeys_(user_id, txn); + if (theirKeys) + status.no_keys = false; + + if (!ourKeys || !theirKeys) { + verification_storage.status[user_id] = status; + return status; + } + + // Update verified devices count to count without cross-signing + updateUnverifiedDevices(theirKeys->device_keys); - auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) { - int currentVerifiedDevices = 0; - for (auto device_id : status.verified_devices) { - if (theirDeviceKeys.count(device_id)) - currentVerifiedDevices++; - } - status.unverified_device_count = - static_cast<int>(theirDeviceKeys.size()) - currentVerifiedDevices; - }; + { + auto &mk = ourKeys->master_keys; + std::string dev_id = "ed25519:" + http::client()->device_id(); + if (!mk.signatures.count(local_user) || !mk.signatures.at(local_user).count(dev_id) || + !mtx::crypto::ed25519_verify_signature(olm::client()->identity_keys().ed25519, + json(mk), + mk.signatures.at(local_user).at(dev_id))) { + nhlog::crypto()->debug("We have not verified our own master key"); + verification_storage.status[user_id] = status; + return status; + } + } - try { - // for local user verify this device_key -> our master_key -> our self_signing_key - // -> our device_keys - // - // for other user verify this device_key -> our master_key -> our user_signing_key - // -> their master_key -> their self_signing_key -> their device_keys - // - // This means verifying the other user adds 2 extra steps,verifying our user_signing - // key and their master key - auto ourKeys = userKeys_(local_user, txn); - auto theirKeys = userKeys_(user_id, txn); - if (theirKeys) - status.no_keys = false; - - if (!ourKeys || !theirKeys) { - verification_storage.status[user_id] = status; - return status; - } + auto master_keys = ourKeys->master_keys.keys; - // Update verified devices count to count without cross-signing - updateUnverifiedDevices(theirKeys->device_keys); + if (user_id != local_user) { + bool theirMasterKeyVerified = + verifyAtLeastOneSig(ourKeys->user_signing_keys, master_keys, local_user) && + verifyAtLeastOneSig( + theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user); - if (!mtx::crypto::ed25519_verify_signature( - olm::client()->identity_keys().ed25519, - json(ourKeys->master_keys), - ourKeys->master_keys.signatures.at(local_user) - .at("ed25519:" + http::client()->device_id()))) { - verification_storage.status[user_id] = status; - return status; - } + if (theirMasterKeyVerified) + trustlevel = crypto::Trust::Verified; + else if (!theirKeys->master_key_changed) + trustlevel = crypto::Trust::TOFU; + else { + verification_storage.status[user_id] = status; + return status; + } - auto master_keys = ourKeys->master_keys.keys; - - if (user_id != local_user) { - bool theirMasterKeyVerified = - verifyAtLeastOneSig( - ourKeys->user_signing_keys, master_keys, local_user) && - verifyAtLeastOneSig( - theirKeys->master_keys, ourKeys->user_signing_keys.keys, local_user); - - if (theirMasterKeyVerified) - trustlevel = crypto::Trust::Verified; - else if (!theirKeys->master_key_changed) - trustlevel = crypto::Trust::TOFU; - else { - verification_storage.status[user_id] = status; - return status; - } + master_keys = theirKeys->master_keys.keys; + } - master_keys = theirKeys->master_keys.keys; - } + status.user_verified = trustlevel; - status.user_verified = trustlevel; + verification_storage.status[user_id] = status; + if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) + return status; - verification_storage.status[user_id] = status; - if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id)) - return status; - - for (const auto &[device, device_key] : theirKeys->device_keys) { - (void)device; - try { - auto identkey = - device_key.keys.at("curve25519:" + device_key.device_id); - if (verifyAtLeastOneSig( - device_key, theirKeys->self_signing_keys.keys, user_id)) { - status.verified_devices.insert(device_key.device_id); - status.verified_device_keys[identkey] = trustlevel; - } - } catch (...) { - } + for (const auto &[device, device_key] : theirKeys->device_keys) { + (void)device; + try { + auto identkey = device_key.keys.at("curve25519:" + device_key.device_id); + if (verifyAtLeastOneSig(device_key, theirKeys->self_signing_keys.keys, user_id)) { + status.verified_devices.insert(device_key.device_id); + status.verified_device_keys[identkey] = trustlevel; } - - updateUnverifiedDevices(theirKeys->device_keys); - verification_storage.status[user_id] = status; - return status; - } catch (std::exception &e) { - nhlog::db()->error( - "Failed to calculate verification status of {}: {}", user_id, e.what()); - return status; + } catch (...) { + } } + + updateUnverifiedDevices(theirKeys->device_keys); + verification_storage.status[user_id] = status; + return status; + } catch (std::exception &e) { + nhlog::db()->error("Failed to calculate verification status of {}: {}", user_id, e.what()); + return status; + } } void to_json(json &j, const RoomInfo &info) { - j["name"] = info.name; - j["topic"] = info.topic; - j["avatar_url"] = info.avatar_url; - j["version"] = info.version; - j["is_invite"] = info.is_invite; - j["is_space"] = info.is_space; - j["join_rule"] = info.join_rule; - j["guest_access"] = info.guest_access; + j["name"] = info.name; + j["topic"] = info.topic; + j["avatar_url"] = info.avatar_url; + j["version"] = info.version; + j["is_invite"] = info.is_invite; + j["is_space"] = info.is_space; + j["join_rule"] = info.join_rule; + j["guest_access"] = info.guest_access; - if (info.member_count != 0) - j["member_count"] = info.member_count; + if (info.member_count != 0) + j["member_count"] = info.member_count; - if (info.tags.size() != 0) - j["tags"] = info.tags; + if (info.tags.size() != 0) + j["tags"] = info.tags; } void from_json(const json &j, RoomInfo &info) { - info.name = j.at("name"); - info.topic = j.at("topic"); - info.avatar_url = j.at("avatar_url"); - info.version = j.value( - "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString()); - info.is_invite = j.at("is_invite"); - info.is_space = j.value("is_space", false); - info.join_rule = j.at("join_rule"); - info.guest_access = j.at("guest_access"); + info.name = j.at("name"); + info.topic = j.at("topic"); + info.avatar_url = j.at("avatar_url"); + info.version = j.value( + "version", QCoreApplication::translate("RoomInfo", "no version stored").toStdString()); + info.is_invite = j.at("is_invite"); + info.is_space = j.value("is_space", false); + info.join_rule = j.at("join_rule"); + info.guest_access = j.at("guest_access"); - if (j.count("member_count")) - info.member_count = j.at("member_count"); + if (j.count("member_count")) + info.member_count = j.at("member_count"); - if (j.count("tags")) - info.tags = j.at("tags").get<std::vector<std::string>>(); + if (j.count("tags")) + info.tags = j.at("tags").get<std::vector<std::string>>(); } void to_json(json &j, const ReadReceiptKey &key) { - j = json{{"event_id", key.event_id}, {"room_id", key.room_id}}; + j = json{{"event_id", key.event_id}, {"room_id", key.room_id}}; } void from_json(const json &j, ReadReceiptKey &key) { - key.event_id = j.at("event_id").get<std::string>(); - key.room_id = j.at("room_id").get<std::string>(); + key.event_id = j.at("event_id").get<std::string>(); + key.room_id = j.at("room_id").get<std::string>(); } void to_json(json &j, const MemberInfo &info) { - j["name"] = info.name; - j["avatar_url"] = info.avatar_url; + j["name"] = info.name; + j["avatar_url"] = info.avatar_url; } void from_json(const json &j, MemberInfo &info) { - info.name = j.at("name"); - info.avatar_url = j.at("avatar_url"); + info.name = j.at("name"); + info.avatar_url = j.at("avatar_url"); } void to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg) { - obj["deviceids"] = msg.deviceids; + obj["deviceids"] = msg.deviceids; } void from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg) { - msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>(); + msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>(); } void to_json(nlohmann::json &obj, const SharedWithUsers &msg) { - obj["keys"] = msg.keys; + obj["keys"] = msg.keys; } void from_json(const nlohmann::json &obj, SharedWithUsers &msg) { - msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>(); + msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>(); } void to_json(nlohmann::json &obj, const GroupSessionData &msg) { - obj["message_index"] = msg.message_index; - obj["ts"] = msg.timestamp; + obj["message_index"] = msg.message_index; + obj["ts"] = msg.timestamp; + obj["trust"] = msg.trusted; - obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; - obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; + obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key; + obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; - obj["currently"] = msg.currently; + obj["currently"] = msg.currently; - obj["indices"] = msg.indices; + obj["indices"] = msg.indices; } void from_json(const nlohmann::json &obj, GroupSessionData &msg) { - msg.message_index = obj.at("message_index"); - msg.timestamp = obj.value("ts", 0ULL); + msg.message_index = obj.at("message_index"); + msg.timestamp = obj.value("ts", 0ULL); + msg.trusted = obj.value("trust", true); - msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); - msg.forwarding_curve25519_key_chain = - obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{}); + msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", ""); + msg.forwarding_curve25519_key_chain = + obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{}); - msg.currently = obj.value("currently", SharedWithUsers{}); + msg.currently = obj.value("currently", SharedWithUsers{}); - msg.indices = obj.value("indices", std::map<uint32_t, std::string>()); + msg.indices = obj.value("indices", std::map<uint32_t, std::string>()); } void to_json(nlohmann::json &obj, const DevicePublicKeys &msg) { - obj["ed25519"] = msg.ed25519; - obj["curve25519"] = msg.curve25519; + obj["ed25519"] = msg.ed25519; + obj["curve25519"] = msg.curve25519; } void from_json(const nlohmann::json &obj, DevicePublicKeys &msg) { - msg.ed25519 = obj.at("ed25519"); - msg.curve25519 = obj.at("curve25519"); + msg.ed25519 = obj.at("ed25519"); + msg.curve25519 = obj.at("curve25519"); } void to_json(nlohmann::json &obj, const MegolmSessionIndex &msg) { - obj["room_id"] = msg.room_id; - obj["session_id"] = msg.session_id; - obj["sender_key"] = msg.sender_key; + obj["room_id"] = msg.room_id; + obj["session_id"] = msg.session_id; + obj["sender_key"] = msg.sender_key; } void from_json(const nlohmann::json &obj, MegolmSessionIndex &msg) { - msg.room_id = obj.at("room_id"); - msg.session_id = obj.at("session_id"); - msg.sender_key = obj.at("sender_key"); + msg.room_id = obj.at("room_id"); + msg.session_id = obj.at("session_id"); + msg.sender_key = obj.at("sender_key"); } void to_json(nlohmann::json &obj, const StoredOlmSession &msg) { - obj["ts"] = msg.last_message_ts; - obj["s"] = msg.pickled_session; + obj["ts"] = msg.last_message_ts; + obj["s"] = msg.pickled_session; } void from_json(const nlohmann::json &obj, StoredOlmSession &msg) { - msg.last_message_ts = obj.at("ts").get<uint64_t>(); - msg.pickled_session = obj.at("s").get<std::string>(); + msg.last_message_ts = obj.at("ts").get<uint64_t>(); + msg.pickled_session = obj.at("s").get<std::string>(); } namespace cache { void init(const QString &user_id) { - qRegisterMetaType<RoomMember>(); - qRegisterMetaType<RoomSearchResult>(); - qRegisterMetaType<RoomInfo>(); - qRegisterMetaType<QMap<QString, RoomInfo>>(); - qRegisterMetaType<std::map<QString, RoomInfo>>(); - qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>(); - qRegisterMetaType<mtx::responses::QueryKeys>(); + qRegisterMetaType<RoomMember>(); + qRegisterMetaType<RoomSearchResult>(); + qRegisterMetaType<RoomInfo>(); + qRegisterMetaType<QMap<QString, RoomInfo>>(); + qRegisterMetaType<std::map<QString, RoomInfo>>(); + qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>(); + qRegisterMetaType<mtx::responses::QueryKeys>(); - instance_ = std::make_unique<Cache>(user_id); + instance_ = std::make_unique<Cache>(user_id); } Cache * client() { - return instance_.get(); + return instance_.get(); } std::string displayName(const std::string &room_id, const std::string &user_id) { - return instance_->displayName(room_id, user_id); + return instance_->displayName(room_id, user_id); } QString displayName(const QString &room_id, const QString &user_id) { - return instance_->displayName(room_id, user_id); + return instance_->displayName(room_id, user_id); } QString avatarUrl(const QString &room_id, const QString &user_id) { - return instance_->avatarUrl(room_id, user_id); + return instance_->avatarUrl(room_id, user_id); } mtx::presence::PresenceState presenceState(const std::string &user_id) { - if (!instance_) - return {}; - return instance_->presenceState(user_id); + if (!instance_) + return {}; + return instance_->presenceState(user_id); } std::string statusMessage(const std::string &user_id) { - return instance_->statusMessage(user_id); + return instance_->statusMessage(user_id); } // user cache stores user keys std::optional<UserKeyCache> userKeys(const std::string &user_id) { - return instance_->userKeys(user_id); + return instance_->userKeys(user_id); } void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery) { - instance_->updateUserKeys(sync_token, keyQuery); + instance_->updateUserKeys(sync_token, keyQuery); } // device & user verification cache std::optional<VerificationStatus> verificationStatus(const std::string &user_id) { - return instance_->verificationStatus(user_id); + return instance_->verificationStatus(user_id); } void markDeviceVerified(const std::string &user_id, const std::string &device) { - instance_->markDeviceVerified(user_id, device); + instance_->markDeviceVerified(user_id, device); } void markDeviceUnverified(const std::string &user_id, const std::string &device) { - instance_->markDeviceUnverified(user_id, device); + instance_->markDeviceUnverified(user_id, device); } std::vector<std::string> joinedRooms() { - return instance_->joinedRooms(); + return instance_->joinedRooms(); } QMap<QString, RoomInfo> roomInfo(bool withInvites) { - return instance_->roomInfo(withInvites); + return instance_->roomInfo(withInvites); } QHash<QString, RoomInfo> invites() { - return instance_->invites(); + return instance_->invites(); } QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - return instance_->getRoomName(txn, statesdb, membersdb); + return instance_->getRoomName(txn, statesdb, membersdb); } mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomJoinRule(txn, statesdb); + return instance_->getRoomJoinRule(txn, statesdb); } bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomGuestAccess(txn, statesdb); + return instance_->getRoomGuestAccess(txn, statesdb); } QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb) { - return instance_->getRoomTopic(txn, statesdb); + return instance_->getRoomTopic(txn, statesdb); } QString getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) { - return instance_->getRoomAvatarUrl(txn, statesdb, membersdb); + return instance_->getRoomAvatarUrl(txn, statesdb, membersdb); } std::vector<RoomMember> getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len) { - return instance_->getMembers(room_id, startIndex, len); + return instance_->getMembers(room_id, startIndex, len); +} + +std::vector<RoomMember> +getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len) +{ + return instance_->getMembersFromInvite(room_id, startIndex, len); } void saveState(const mtx::responses::Sync &res) { - instance_->saveState(res); + instance_->saveState(res); } bool isInitialized() { - return instance_->isInitialized(); + return instance_->isInitialized(); } std::string nextBatchToken() { - return instance_->nextBatchToken(); + return instance_->nextBatchToken(); } void deleteData() { - instance_->deleteData(); + instance_->deleteData(); } void removeInvite(lmdb::txn &txn, const std::string &room_id) { - instance_->removeInvite(txn, room_id); + instance_->removeInvite(txn, room_id); } void removeInvite(const std::string &room_id) { - instance_->removeInvite(room_id); + instance_->removeInvite(room_id); } void removeRoom(lmdb::txn &txn, const std::string &roomid) { - instance_->removeRoom(txn, roomid); + instance_->removeRoom(txn, roomid); } void removeRoom(const std::string &roomid) { - instance_->removeRoom(roomid); + instance_->removeRoom(roomid); } void removeRoom(const QString &roomid) { - instance_->removeRoom(roomid.toStdString()); + instance_->removeRoom(roomid.toStdString()); } void setup() { - instance_->setup(); + instance_->setup(); } bool runMigrations() { - return instance_->runMigrations(); + return instance_->runMigrations(); } cache::CacheVersion formatVersion() { - return instance_->formatVersion(); + return instance_->formatVersion(); } void setCurrentFormat() { - instance_->setCurrentFormat(); + instance_->setCurrentFormat(); } std::vector<QString> roomIds() { - return instance_->roomIds(); + return instance_->roomIds(); } QMap<QString, mtx::responses::Notifications> getTimelineMentions() { - return instance_->getTimelineMentions(); + return instance_->getTimelineMentions(); } //! Retrieve all the user ids from a room. std::vector<std::string> roomMembers(const std::string &room_id) { - return instance_->roomMembers(room_id); + return instance_->roomMembers(room_id); } //! Check if the given user has power leve greater than than @@ -4757,48 +4861,48 @@ hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes, const std::string &room_id, const std::string &user_id) { - return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id); + return instance_->hasEnoughPowerLevel(eventTypes, room_id, user_id); } void updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts) { - instance_->updateReadReceipt(txn, room_id, receipts); + instance_->updateReadReceipt(txn, room_id, receipts); } UserReceipts readReceipts(const QString &event_id, const QString &room_id) { - return instance_->readReceipts(event_id, room_id); + return instance_->readReceipts(event_id, room_id); } std::optional<uint64_t> getEventIndex(const std::string &room_id, std::string_view event_id) { - return instance_->getEventIndex(room_id, event_id); + return instance_->getEventIndex(room_id, event_id); } std::optional<std::pair<uint64_t, std::string>> lastInvisibleEventAfter(const std::string &room_id, std::string_view event_id) { - return instance_->lastInvisibleEventAfter(room_id, event_id); + return instance_->lastInvisibleEventAfter(room_id, event_id); } RoomInfo singleRoomInfo(const std::string &room_id) { - return instance_->singleRoomInfo(room_id); + return instance_->singleRoomInfo(room_id); } std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res) { - return instance_->roomsWithStateUpdates(res); + return instance_->roomsWithStateUpdates(res); } std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms) { - return instance_->getRoomInfo(rooms); + return instance_->getRoomInfo(rooms); } //! Calculates which the read status of a room. @@ -4806,74 +4910,74 @@ getRoomInfo(const std::vector<std::string> &rooms) bool calculateRoomReadStatus(const std::string &room_id) { - return instance_->calculateRoomReadStatus(room_id); + return instance_->calculateRoomReadStatus(room_id); } void calculateRoomReadStatus() { - instance_->calculateRoomReadStatus(); + instance_->calculateRoomReadStatus(); } void markSentNotification(const std::string &event_id) { - instance_->markSentNotification(event_id); + instance_->markSentNotification(event_id); } //! Removes an event from the sent notifications. void removeReadNotification(const std::string &event_id) { - instance_->removeReadNotification(event_id); + instance_->removeReadNotification(event_id); } //! Check if we have sent a desktop notification for the given event id. bool isNotificationSent(const std::string &event_id) { - return instance_->isNotificationSent(event_id); + return instance_->isNotificationSent(event_id); } //! Add all notifications containing a user mention to the db. void saveTimelineMentions(const mtx::responses::Notifications &res) { - instance_->saveTimelineMentions(res); + instance_->saveTimelineMentions(res); } //! Remove old unused data. void deleteOldMessages() { - instance_->deleteOldMessages(); + instance_->deleteOldMessages(); } void deleteOldData() noexcept { - instance_->deleteOldData(); + instance_->deleteOldData(); } //! Retrieve all saved room ids. std::vector<std::string> getRoomIds(lmdb::txn &txn) { - return instance_->getRoomIds(txn); + return instance_->getRoomIds(txn); } //! Mark a room that uses e2e encryption. void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) { - instance_->setEncryptedRoom(txn, room_id); + instance_->setEncryptedRoom(txn, room_id); } bool isRoomEncrypted(const std::string &room_id) { - return instance_->isRoomEncrypted(room_id); + return instance_->isRoomEncrypted(room_id); } //! Check if a user is a member of the room. bool isRoomMember(const std::string &user_id, const std::string &room_id) { - return instance_->isRoomMember(user_id, room_id); + return instance_->isRoomMember(user_id, room_id); } // @@ -4884,40 +4988,40 @@ saveOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { - instance_->saveOutboundMegolmSession(room_id, data, session); + instance_->saveOutboundMegolmSession(room_id, data, session); } OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id) { - return instance_->getOutboundMegolmSession(room_id); + return instance_->getOutboundMegolmSession(room_id); } bool outboundMegolmSessionExists(const std::string &room_id) noexcept { - return instance_->outboundMegolmSessionExists(room_id); + return instance_->outboundMegolmSessionExists(room_id); } void updateOutboundMegolmSession(const std::string &room_id, const GroupSessionData &data, mtx::crypto::OutboundGroupSessionPtr &session) { - instance_->updateOutboundMegolmSession(room_id, data, session); + instance_->updateOutboundMegolmSession(room_id, data, session); } void dropOutboundMegolmSession(const std::string &room_id) { - instance_->dropOutboundMegolmSession(room_id); + instance_->dropOutboundMegolmSession(room_id); } void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys) { - instance_->importSessionKeys(keys); + instance_->importSessionKeys(keys); } mtx::crypto::ExportedSessionKeys exportSessionKeys() { - return instance_->exportSessionKeys(); + return instance_->exportSessionKeys(); } // @@ -4928,22 +5032,22 @@ saveInboundMegolmSession(const MegolmSessionIndex &index, mtx::crypto::InboundGroupSessionPtr session, const GroupSessionData &data) { - instance_->saveInboundMegolmSession(index, std::move(session), data); + instance_->saveInboundMegolmSession(index, std::move(session), data); } mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(const MegolmSessionIndex &index) { - return instance_->getInboundMegolmSession(index); + return instance_->getInboundMegolmSession(index); } bool inboundMegolmSessionExists(const MegolmSessionIndex &index) { - return instance_->inboundMegolmSessionExists(index); + return instance_->inboundMegolmSessionExists(index); } std::optional<GroupSessionData> getMegolmSessionData(const MegolmSessionIndex &index) { - return instance_->getMegolmSessionData(index); + return instance_->getMegolmSessionData(index); } // @@ -4954,43 +5058,43 @@ saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session, uint64_t timestamp) { - instance_->saveOlmSession(curve25519, std::move(session), timestamp); + instance_->saveOlmSession(curve25519, std::move(session), timestamp); } std::vector<std::string> getOlmSessions(const std::string &curve25519) { - return instance_->getOlmSessions(curve25519); + return instance_->getOlmSessions(curve25519); } std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519, const std::string &session_id) { - return instance_->getOlmSession(curve25519, session_id); + return instance_->getOlmSession(curve25519, session_id); } std::optional<mtx::crypto::OlmSessionPtr> getLatestOlmSession(const std::string &curve25519) { - return instance_->getLatestOlmSession(curve25519); + return instance_->getLatestOlmSession(curve25519); } void saveOlmAccount(const std::string &pickled) { - instance_->saveOlmAccount(pickled); + instance_->saveOlmAccount(pickled); } std::string restoreOlmAccount() { - return instance_->restoreOlmAccount(); + return instance_->restoreOlmAccount(); } void storeSecret(const std::string &name, const std::string &secret) { - instance_->storeSecret(name, secret); + instance_->storeSecret(name, secret); } std::optional<std::string> secret(const std::string &name) { - return instance_->secret(name); + return instance_->secret(name); } } // namespace cache |