summary refs log tree commit diff
path: root/src/Cache.cc
diff options
context:
space:
mode:
authorKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
committerKonstantinos Sideris <sideris.konstantin@gmail.com>2018-07-17 16:37:25 +0300
commit0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a (patch)
tree21f655d30630fe77ba48d07e4b357e2b6c6a5730 /src/Cache.cc
parentMerge pull request #372 from bebehei/notification (diff)
downloadnheko-0e814da91c8e041897a4c3f7e6e9234bbc7c6f7a.tar.xz
Move all files under src/
Diffstat (limited to 'src/Cache.cc')
-rw-r--r--src/Cache.cc1786
1 files changed, 0 insertions, 1786 deletions
diff --git a/src/Cache.cc b/src/Cache.cc
deleted file mode 100644

index 614e8a90..00000000 --- a/src/Cache.cc +++ /dev/null
@@ -1,1786 +0,0 @@ -/* - * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <limits> -#include <stdexcept> - -#include <QByteArray> -#include <QFile> -#include <QHash> -#include <QSettings> -#include <QStandardPaths> - -#include <mtx/responses/common.hpp> -#include <variant.hpp> - -#include "Cache.h" -#include "Logging.hpp" -#include "Utils.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("2018.06.10"); -static const std::string SECRET("secret"); - -static const lmdb::val NEXT_BATCH_KEY("next_batch"); -static const lmdb::val OLM_ACCOUNT_KEY("olm_account"); -static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version"); - -constexpr size_t MAX_RESTORED_MESSAGES = 30; - -//! Cache databases and their format. -//! -//! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc). -//! Format: room_id -> RoomInfo -constexpr auto ROOMS_DB("rooms"); -constexpr auto INVITES_DB("invites"); -//! Keeps already downloaded media for reuse. -//! Format: matrix_url -> binary data. -constexpr auto MEDIA_DB("media"); -//! Information that must be kept between sync requests. -constexpr auto SYNC_STATE_DB("sync_state"); -//! Read receipts per room/event. -constexpr auto READ_RECEIPTS_DB("read_receipts"); -constexpr auto NOTIFICATIONS_DB("sent_notifications"); - -//! Encryption related databases. - -//! user_id -> list of devices -constexpr auto DEVICES_DB("devices"); -//! device_id -> device keys -constexpr auto DEVICE_KEYS_DB("device_keys"); -//! room_ids that have encryption enabled. -constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms"); - -//! room_id -> pickled OlmInboundGroupSession -constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions"); -//! MegolmSessionIndex -> pickled OlmOutboundGroupSession -constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions"); - -using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>; -using Receipts = std::map<std::string, std::map<std::string, uint64_t>>; - -namespace { -std::unique_ptr<Cache> instance_ = nullptr; -} - -namespace cache { -void -init(const QString &user_id) -{ - qRegisterMetaType<SearchResult>(); - qRegisterMetaType<QVector<SearchResult>>(); - qRegisterMetaType<RoomMember>(); - qRegisterMetaType<RoomSearchResult>(); - qRegisterMetaType<RoomInfo>(); - qRegisterMetaType<QMap<QString, RoomInfo>>(); - qRegisterMetaType<std::map<QString, RoomInfo>>(); - qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>(); - - instance_ = std::make_unique<Cache>(user_id); -} - -Cache * -client() -{ - return instance_.get(); -} -} // namespace cache - -Cache::Cache(const QString &userId, QObject *parent) - : QObject{parent} - , env_{nullptr} - , syncStateDb_{0} - , roomsDb_{0} - , invitesDb_{0} - , mediaDb_{0} - , readReceiptsDb_{0} - , notificationsDb_{0} - , devicesDb_{0} - , deviceKeysDb_{0} - , inboundMegolmSessionDb_{0} - , outboundMegolmSessionDb_{0} - , localUserId_{userId} -{ - setup(); -} - -void -Cache::setup() -{ - nhlog::db()->debug("setting up cache"); - - auto statePath = QString("%1/%2") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())); - - cacheDirectory_ = QString("%1/%2") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(QString::fromUtf8(localUserId_.toUtf8().toHex())); - - bool isInitial = !QFile::exists(statePath); - - env_ = lmdb::env::create(); - env_.set_mapsize(256UL * 1024UL * 1024UL); /* 256 MB */ - env_.set_max_dbs(1024UL); - - if (isInitial) { - nhlog::db()->info("initializing LMDB"); - - if (!QDir().mkpath(statePath)) { - throw std::runtime_error( - ("Unable to create state directory:" + statePath).toStdString().c_str()); - } - } - - try { - env_.open(statePath.toStdString().c_str()); - } 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()); - - QDir stateDir(statePath); - - 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(statePath.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); - invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE); - mediaDb_ = lmdb::dbi::open(txn, MEDIA_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); - - txn.commit(); -} - -void -Cache::setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) -{ - nhlog::db()->info("mark room {} as encrypted", room_id); - - auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - lmdb::dbi_put(txn, db, lmdb::val(room_id), lmdb::val("0")); -} - -bool -Cache::isRoomEncrypted(const std::string &room_id) -{ - lmdb::val unused; - - auto txn = lmdb::txn::begin(env_); - auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - auto res = lmdb::dbi_get(txn, db, lmdb::val(room_id), unused); - txn.commit(); - - return res; -} - -// -// Device Management -// - -// -// Session Management -// - -void -Cache::saveInboundMegolmSession(const MegolmSessionIndex &index, - mtx::crypto::InboundGroupSessionPtr session) -{ - using namespace mtx::crypto; - const auto key = index.to_hash(); - const auto pickled = pickle<InboundSessionObject>(session.get(), SECRET); - - auto txn = lmdb::txn::begin(env_); - lmdb::dbi_put(txn, inboundMegolmSessionDb_, lmdb::val(key), lmdb::val(pickled)); - txn.commit(); - - { - std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx); - session_storage.group_inbound_sessions[key] = std::move(session); - } -} - -OlmInboundGroupSession * -Cache::getInboundMegolmSession(const MegolmSessionIndex &index) -{ - std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx); - return session_storage.group_inbound_sessions[index.to_hash()].get(); -} - -bool -Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept -{ - std::unique_lock<std::mutex> lock(session_storage.group_inbound_mtx); - return session_storage.group_inbound_sessions.find(index.to_hash()) != - session_storage.group_inbound_sessions.end(); -} - -void -Cache::updateOutboundMegolmSession(const std::string &room_id, int message_index) -{ - using namespace mtx::crypto; - - if (!outboundMegolmSessionExists(room_id)) - return; - - OutboundGroupSessionData data; - OlmOutboundGroupSession *session; - { - std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx); - data = session_storage.group_outbound_session_data[room_id]; - session = session_storage.group_outbound_sessions[room_id].get(); - - // Update with the current message. - data.message_index = message_index; - session_storage.group_outbound_session_data[room_id] = data; - } - - // Save the updated pickled data for the session. - json j; - j["data"] = data; - j["session"] = pickle<OutboundSessionObject>(session, SECRET); - - auto txn = lmdb::txn::begin(env_); - lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump())); - txn.commit(); -} - -void -Cache::saveOutboundMegolmSession(const std::string &room_id, - const OutboundGroupSessionData &data, - mtx::crypto::OutboundGroupSessionPtr session) -{ - using namespace mtx::crypto; - const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET); - - json j; - j["data"] = data; - j["session"] = pickled; - - auto txn = lmdb::txn::begin(env_); - lmdb::dbi_put(txn, outboundMegolmSessionDb_, lmdb::val(room_id), lmdb::val(j.dump())); - txn.commit(); - - { - std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx); - session_storage.group_outbound_session_data[room_id] = data; - session_storage.group_outbound_sessions[room_id] = std::move(session); - } -} - -bool -Cache::outboundMegolmSessionExists(const std::string &room_id) noexcept -{ - std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx); - return (session_storage.group_outbound_sessions.find(room_id) != - session_storage.group_outbound_sessions.end()) && - (session_storage.group_outbound_session_data.find(room_id) != - session_storage.group_outbound_session_data.end()); -} - -OutboundGroupSessionDataRef -Cache::getOutboundMegolmSession(const std::string &room_id) -{ - std::unique_lock<std::mutex> lock(session_storage.group_outbound_mtx); - return OutboundGroupSessionDataRef{session_storage.group_outbound_sessions[room_id].get(), - session_storage.group_outbound_session_data[room_id]}; -} - -// -// OLM sessions. -// - -void -Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session) -{ - using namespace mtx::crypto; - - 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()); - - lmdb::dbi_put(txn, db, lmdb::val(session_id), lmdb::val(pickled)); - - txn.commit(); -} - -boost::optional<mtx::crypto::OlmSessionPtr> -Cache::getOlmSession(const std::string &curve25519, const std::string &session_id) -{ - using namespace mtx::crypto; - - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); - - lmdb::val pickled; - bool found = lmdb::dbi_get(txn, db, lmdb::val(session_id), pickled); - - txn.commit(); - - if (found) { - auto data = std::string(pickled.data(), pickled.size()); - return unpickle<SessionObject>(data, SECRET); - } - - return boost::none; -} - -std::vector<std::string> -Cache::getOlmSessions(const std::string &curve25519) -{ - using namespace mtx::crypto; - - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); - - std::string 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(); - - txn.commit(); - - return res; -} - -void -Cache::saveOlmAccount(const std::string &data) -{ - auto txn = lmdb::txn::begin(env_); - lmdb::dbi_put(txn, syncStateDb_, OLM_ACCOUNT_KEY, lmdb::val(data)); - txn.commit(); -} - -void -Cache::restoreSessions() -{ - using namespace mtx::crypto; - - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - std::string key, value; - - // - // Inbound Megolm Sessions - // - { - auto cursor = lmdb::cursor::open(txn, inboundMegolmSessionDb_); - while (cursor.get(key, value, MDB_NEXT)) { - auto session = unpickle<InboundSessionObject>(value, SECRET); - session_storage.group_inbound_sessions[key] = std::move(session); - } - cursor.close(); - } - - // - // Outbound Megolm Sessions - // - { - auto cursor = lmdb::cursor::open(txn, outboundMegolmSessionDb_); - while (cursor.get(key, value, MDB_NEXT)) { - json obj; - - try { - obj = json::parse(value); - - session_storage.group_outbound_session_data[key] = - obj.at("data").get<OutboundGroupSessionData>(); - - auto session = - unpickle<OutboundSessionObject>(obj.at("session"), SECRET); - session_storage.group_outbound_sessions[key] = std::move(session); - } catch (const nlohmann::json::exception &e) { - nhlog::db()->critical( - "failed to parse outbound megolm session data: {}", e.what()); - } - } - cursor.close(); - } - - txn.commit(); - - nhlog::db()->info("sessions restored"); -} - -std::string -Cache::restoreOlmAccount() -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - lmdb::val pickled; - lmdb::dbi_get(txn, syncStateDb_, OLM_ACCOUNT_KEY, pickled); - txn.commit(); - - return std::string(pickled.data(), pickled.size()); -} - -// -// Media Management -// - -void -Cache::saveImage(const std::string &url, const std::string &img_data) -{ - if (url.empty() || img_data.empty()) - return; - - try { - auto txn = lmdb::txn::begin(env_); - - lmdb::dbi_put(txn, - mediaDb_, - lmdb::val(url.data(), url.size()), - lmdb::val(img_data.data(), img_data.size())); - - txn.commit(); - } catch (const lmdb::error &e) { - nhlog::db()->critical("saveImage: {}", e.what()); - } -} - -void -Cache::saveImage(const QString &url, const QByteArray &image) -{ - saveImage(url.toStdString(), std::string(image.constData(), image.length())); -} - -QByteArray -Cache::image(lmdb::txn &txn, const std::string &url) const -{ - if (url.empty()) - return QByteArray(); - - try { - lmdb::val image; - bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(url), image); - - if (!res) - return QByteArray(); - - return QByteArray(image.data(), image.size()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("image: {}, {}", e.what(), url); - } - - return QByteArray(); -} - -QByteArray -Cache::image(const QString &url) const -{ - if (url.isEmpty()) - return QByteArray(); - - auto key = url.toUtf8(); - - try { - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - lmdb::val image; - - bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(key.data(), key.size()), image); - - txn.commit(); - - if (!res) - return QByteArray(); - - return QByteArray(image.data(), image.size()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("image: {} {}", e.what(), url.toStdString()); - } - - return QByteArray(); -} - -void -Cache::removeInvite(lmdb::txn &txn, const std::string &room_id) -{ - lmdb::dbi_del(txn, invitesDb_, lmdb::val(room_id), nullptr); - lmdb::dbi_drop(txn, getInviteStatesDb(txn, room_id), true); - lmdb::dbi_drop(txn, getInviteMembersDb(txn, room_id), true); -} - -void -Cache::removeInvite(const std::string &room_id) -{ - auto txn = lmdb::txn::begin(env_); - removeInvite(txn, room_id); - txn.commit(); -} - -void -Cache::removeRoom(lmdb::txn &txn, const std::string &roomid) -{ - lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr); - lmdb::dbi_drop(txn, getStatesDb(txn, roomid), true); - lmdb::dbi_drop(txn, getMembersDb(txn, roomid), true); -} - -void -Cache::removeRoom(const std::string &roomid) -{ - auto txn = lmdb::txn::begin(env_, nullptr, 0); - lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr); - txn.commit(); -} - -void -Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token) -{ - lmdb::dbi_put(txn, syncStateDb_, NEXT_BATCH_KEY, lmdb::val(token.data(), token.size())); -} - -void -Cache::setNextBatchToken(lmdb::txn &txn, const QString &token) -{ - setNextBatchToken(txn, token.toStdString()); -} - -bool -Cache::isInitialized() const -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - lmdb::val token; - - bool res = lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); - - txn.commit(); - - return res; -} - -std::string -Cache::nextBatchToken() const -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - lmdb::val token; - - lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token); - - txn.commit(); - - return std::string(token.data(), token.size()); -} - -void -Cache::deleteData() -{ - // TODO: We need to remove the env_ while not accepting new requests. - if (!cacheDirectory_.isEmpty()) { - QDir(cacheDirectory_).removeRecursively(); - nhlog::db()->info("deleted cache files from disk"); - } -} - -bool -Cache::isFormatValid() -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - lmdb::val current_version; - bool res = lmdb::dbi_get(txn, syncStateDb_, CACHE_FORMAT_VERSION_KEY, current_version); - - txn.commit(); - - if (!res) - return true; - - std::string stored_version(current_version.data(), current_version.size()); - - if (stored_version != CURRENT_CACHE_FORMAT_VERSION) { - nhlog::db()->warn("breaking changes in the cache format. stored: {}, current: {}", - stored_version, - CURRENT_CACHE_FORMAT_VERSION); - return false; - } - - return true; -} - -void -Cache::setCurrentFormat() -{ - auto txn = lmdb::txn::begin(env_); - - lmdb::dbi_put( - txn, - syncStateDb_, - CACHE_FORMAT_VERSION_KEY, - lmdb::val(CURRENT_CACHE_FORMAT_VERSION.data(), CURRENT_CACHE_FORMAT_VERSION.size())); - - txn.commit(); -} - -CachedReceipts -Cache::readReceipts(const QString &event_id, const QString &room_id) -{ - CachedReceipts receipts; - - ReadReceiptKey receipt_key{event_id.toStdString(), room_id.toStdString()}; - nlohmann::json json_key = receipt_key; - - try { - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto key = json_key.dump(); - - lmdb::val value; - - bool res = - lmdb::dbi_get(txn, readReceiptsDb_, lmdb::val(key.data(), key.size()), value); - - txn.commit(); - - if (res) { - auto json_response = json::parse(std::string(value.data(), value.size())); - auto values = json_response.get<std::map<std::string, uint64_t>>(); - - for (const auto &v : values) - // timestamp, user_id - receipts.emplace(v.second, v.first); - } - - } 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) -{ - 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; - - try { - const auto key = json_key.dump(); - - lmdb::val prev_value; - - bool exists = lmdb::dbi_get( - txn, readReceiptsDb_, lmdb::val(key.data(), key.size()), prev_value); - - 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(prev_value.data(), prev_value.size())); - - // Retrieve the saved receipts. - saved_receipts = json_value.get<std::map<std::string, uint64_t>>(); - } - - // Append the new ones. - for (const auto &event_receipt : event_receipts) - saved_receipts.emplace(event_receipt.first, event_receipt.second); - - // Save back the merged (or only the new) receipts. - nlohmann::json json_updated_value = saved_receipts; - std::string merged_receipts = json_updated_value.dump(); - - lmdb::dbi_put(txn, - readReceiptsDb_, - lmdb::val(key.data(), key.size()), - lmdb::val(merged_receipts.data(), merged_receipts.size())); - - } catch (const lmdb::error &e) { - nhlog::db()->critical("updateReadReceipts: {}", e.what()); - } - } -} - -void -Cache::saveState(const mtx::responses::Sync &res) -{ - auto txn = lmdb::txn::begin(env_); - - setNextBatchToken(txn, res.next_batch); - - // Save joined rooms - for (const auto &room : res.rooms.join) { - auto statesdb = getStatesDb(txn, room.first); - auto membersdb = getMembersDb(txn, room.first); - - saveStateEvents(txn, statesdb, membersdb, room.first, room.second.state.events); - saveStateEvents(txn, statesdb, membersdb, room.first, room.second.timeline.events); - - saveTimelineMessages(txn, 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, QString::fromStdString(room.first)) - .toStdString(); - - lmdb::dbi_put( - txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump())); - - updateReadReceipt(txn, room.first, room.second.ephemeral.receipts); - - // Clean up non-valid invites. - removeInvite(txn, room.first); - } - - saveInvites(txn, res.rooms.invite); - - removeLeftRooms(txn, res.rooms.leave); - - txn.commit(); -} - -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); - - 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_invite = true; - - lmdb::dbi_put( - txn, invitesDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump())); - } -} - -void -Cache::saveInvite(lmdb::txn &txn, - lmdb::dbi &statesdb, - lmdb::dbi &membersdb, - const mtx::responses::InvitedRoom &room) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - for (const auto &e : room.invite_state) { - if (mpark::holds_alternative<StrippedEvent<Member>>(e)) { - auto msg = mpark::get<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}; - - lmdb::dbi_put( - txn, membersdb, lmdb::val(msg.state_key), lmdb::val(json(tmp).dump())); - } else { - mpark::visit( - [&txn, &statesdb](auto msg) { - bool res = lmdb::dbi_put(txn, - statesdb, - lmdb::val(to_string(msg.type)), - lmdb::val(json(msg).dump())); - - if (!res) - std::cout << "couldn't save data" << json(msg).dump() - << '\n'; - }, - e); - } - } -} - -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; - } - } - - 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; -} - -RoomInfo -Cache::singleRoomInfo(const std::string &room_id) -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto statesdb = getStatesDb(txn, room_id); - - lmdb::val data; - - // Check if the room is joined. - if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room_id), data)) { - try { - RoomInfo tmp = json::parse(std::string(data.data(), data.size())); - tmp.member_count = getMembersDb(txn, room_id).size(txn); - tmp.join_rule = getRoomJoinRule(txn, statesdb); - tmp.guest_access = getRoomGuestAccess(txn, statesdb); - - txn.commit(); - - 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())); - } - } - - txn.commit(); - - return RoomInfo(); -} - -std::map<QString, RoomInfo> -Cache::getRoomInfo(const std::vector<std::string> &rooms) -{ - std::map<QString, RoomInfo> room_info; - - // TODO This should be read only. - auto txn = lmdb::txn::begin(env_); - - for (const auto &room : rooms) { - lmdb::val data; - auto statesdb = getStatesDb(txn, room); - - // Check if the room is joined. - if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room), data)) { - try { - RoomInfo tmp = json::parse(std::string(data.data(), data.size())); - 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())); - } - } else { - // Check if the room is an invite. - if (lmdb::dbi_get(txn, invitesDb_, lmdb::val(room), data)) { - try { - RoomInfo tmp = - json::parse(std::string(data.data(), data.size())); - 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())); - } - } - } - } - - txn.commit(); - - return room_info; -} - -std::map<QString, mtx::responses::Timeline> -Cache::roomMessages() -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - std::map<QString, mtx::responses::Timeline> msgs; - std::string room_id, unused; - - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - while (roomsCursor.get(room_id, unused, MDB_NEXT)) - msgs.emplace(QString::fromStdString(room_id), mtx::responses::Timeline()); - - roomsCursor.close(); - txn.commit(); - - return msgs; -} - -mtx::responses::Timeline -Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id) -{ - auto db = getMessagesDb(txn, room_id); - - mtx::responses::Timeline timeline; - std::string timestamp, msg; - - auto cursor = lmdb::cursor::open(txn, db); - - size_t index = 0; - - while (cursor.get(timestamp, msg, MDB_NEXT) && index < MAX_RESTORED_MESSAGES) { - auto obj = json::parse(msg); - - if (obj.count("event") == 0 || obj.count("token") == 0) - continue; - - mtx::events::collections::TimelineEvent event; - mtx::events::collections::from_json(obj.at("event"), event); - - index += 1; - - timeline.events.push_back(event.data); - timeline.prev_batch = obj.at("token").get<std::string>(); - } - cursor.close(); - - std::reverse(timeline.events.begin(), timeline.events.end()); - - return timeline; -} - -QMap<QString, RoomInfo> -Cache::roomInfo(bool withInvites) -{ - QMap<QString, RoomInfo> result; - - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - std::string room_id; - std::string 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, room_id).size(txn); - tmp.msgInfo = getLastMessageInfo(txn, room_id); - - result.insert(QString::fromStdString(std::move(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, room_id).size(txn); - result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp)); - } - invitesCursor.close(); - } - - txn.commit(); - - return result; -} - -DescInfo -Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id) -{ - auto db = getMessagesDb(txn, room_id); - - if (db.size(txn) == 0) - return DescInfo{}; - - std::string timestamp, msg; - - QSettings settings; - auto local_user = settings.value("auth/user_id").toString(); - - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(timestamp, msg, MDB_NEXT)) { - auto obj = json::parse(msg); - - if (obj.count("event") == 0) - continue; - - mtx::events::collections::TimelineEvent event; - mtx::events::collections::from_json(obj.at("event"), event); - - cursor.close(); - return utils::getMessageDescription( - event.data, local_user, QString::fromStdString(room_id)); - } - cursor.close(); - - return DescInfo{}; -} - -std::map<QString, bool> -Cache::invites() -{ - std::map<QString, bool> result; - - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto cursor = lmdb::cursor::open(txn, invitesDb_); - - std::string room_id, unused; - - while (cursor.get(room_id, unused, MDB_NEXT)) - result.emplace(QString::fromStdString(std::move(room_id)), true); - - cursor.close(); - txn.commit(); - - return result; -} - -QString -Cache::getRoomAvatarUrl(lmdb::txn &txn, - lmdb::dbi &statesdb, - lmdb::dbi &membersdb, - const QString &room_id) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event); - - if (res) { - try { - StateEvent<Avatar> msg = - json::parse(std::string(event.data(), event.size())); - - 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(); - - auto cursor = lmdb::cursor::open(txn, membersdb); - std::string user_id; - std::string member_data; - - // Resolve avatar for 1-1 chats. - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; - - try { - MemberInfo m = json::parse(member_data); - - 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(); - - // Default case when there is only one member. - return avatarUrl(room_id, localUserId_); -} - -QString -Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event); - - if (res) { - try { - StateEvent<Name> msg = json::parse(std::string(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()); - } - } - - res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event); - - if (res) { - try { - StateEvent<CanonicalAlias> msg = - json::parse(std::string(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()); - } - } - - auto cursor = lmdb::cursor::open(txn, membersdb); - const int total = membersdb.size(txn); - - std::size_t ii = 0; - std::string user_id; - std::string 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++; - } - - cursor.close(); - - 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_; - }(); - - if (total == 2) - return first_member; - else if (total > 2) - return QString("%1 and %2 others").arg(first_member).arg(total); - - return "Empty Room"; -} - -JoinRule -Cache::getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomJoinRules)), event); - - if (res) { - try { - StateEvent<JoinRules> msg = - json::parse(std::string(event.data(), event.size())); - return msg.content.join_rule; - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse m.room.join_rule event: {}", e.what()); - } - } - return JoinRule::Knock; -} - -bool -Cache::getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomGuestAccess)), event); - - if (res) { - try { - StateEvent<GuestAccess> msg = - json::parse(std::string(event.data(), event.size())); - 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; -} - -QString -Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event); - - if (res) { - try { - StateEvent<Topic> msg = - json::parse(std::string(event.data(), event.size())); - - 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(); -} - -QString -Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event); - - if (res) { - try { - StrippedEvent<state::Name> msg = - json::parse(std::string(event.data(), event.size())); - 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 user_id, member_data; - - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; - - 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()); - } - } - - cursor.close(); - - 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; - - lmdb::val event; - bool res = lmdb::dbi_get( - txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event); - - if (res) { - try { - StrippedEvent<state::Avatar> msg = - json::parse(std::string(event.data(), event.size())); - 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 user_id, member_data; - - while (cursor.get(user_id, member_data, MDB_NEXT)) { - if (user_id == localUserId_.toStdString()) - continue; - - 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()); - } - } - - cursor.close(); - - return QString(); -} - -QString -Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db) -{ - using namespace mtx::events; - using namespace mtx::events::state; - - lmdb::val event; - bool res = - lmdb::dbi_get(txn, db, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event); - - if (res) { - try { - StrippedEvent<Topic> msg = - json::parse(std::string(event.data(), event.size())); - 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(); -} - -QImage -Cache::getRoomAvatar(const QString &room_id) -{ - return getRoomAvatar(room_id.toStdString()); -} - -QImage -Cache::getRoomAvatar(const std::string &room_id) -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - lmdb::val response; - - if (!lmdb::dbi_get(txn, roomsDb_, lmdb::val(room_id), response)) { - txn.commit(); - return QImage(); - } - - std::string media_url; - - try { - RoomInfo info = json::parse(std::string(response.data(), response.size())); - media_url = std::move(info.avatar_url); - - if (media_url.empty()) { - txn.commit(); - return QImage(); - } - } catch (const json::exception &e) { - nhlog::db()->warn("failed to parse room info: {}, {}", - e.what(), - std::string(response.data(), response.size())); - } - - if (!lmdb::dbi_get(txn, mediaDb_, lmdb::val(media_url), response)) { - txn.commit(); - return QImage(); - } - - txn.commit(); - - return QImage::fromData(QByteArray(response.data(), response.size())); -} - -std::vector<std::string> -Cache::joinedRooms() -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); - - std::string 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); - - roomsCursor.close(); - txn.commit(); - - return room_ids; -} - -void -Cache::populateMembers() -{ - auto rooms = joinedRooms(); - nhlog::db()->info("loading {} rooms", rooms.size()); - - auto txn = lmdb::txn::begin(env_); - - for (const auto &room : rooms) { - const auto roomid = QString::fromStdString(room); - - auto membersdb = getMembersDb(txn, room); - auto cursor = lmdb::cursor::open(txn, membersdb); - - std::string user_id, info; - while (cursor.get(user_id, info, MDB_NEXT)) { - MemberInfo m = json::parse(info); - - const auto userid = QString::fromStdString(user_id); - - insertDisplayName(roomid, userid, QString::fromStdString(m.name)); - insertAvatarUrl(roomid, userid, QString::fromStdString(m.avatar_url)); - } - - cursor.close(); - } - - txn.commit(); -} - -std::vector<RoomSearchResult> -Cache::searchRooms(const std::string &query, std::uint8_t max_items) -{ - std::multimap<int, std::pair<std::string, RoomInfo>> items; - - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto cursor = lmdb::cursor::open(txn, roomsDb_); - - std::string room_id, room_data; - while (cursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(std::move(room_data)); - - const int score = utils::levenshtein_distance( - query, QString::fromStdString(tmp.name).toLower().toStdString()); - items.emplace(score, std::make_pair(room_id, tmp)); - } - - cursor.close(); - - auto end = items.begin(); - - if (items.size() >= max_items) - std::advance(end, max_items); - else if (items.size() > 0) - std::advance(end, items.size()); - - std::vector<RoomSearchResult> results; - for (auto it = items.begin(); it != end; it++) { - results.push_back( - RoomSearchResult{it->second.first, - it->second.second, - QImage::fromData(image(txn, it->second.second.avatar_url))}); - } - - txn.commit(); - - return results; -} - -QVector<SearchResult> -Cache::searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items) -{ - std::multimap<int, std::pair<std::string, std::string>> items; - - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto cursor = lmdb::cursor::open(txn, getMembersDb(txn, room_id)); - - std::string user_id, user_data; - while (cursor.get(user_id, user_data, MDB_NEXT)) { - const auto display_name = displayName(room_id, user_id); - const int score = utils::levenshtein_distance(query, display_name); - - items.emplace(score, std::make_pair(user_id, display_name)); - } - - auto end = items.begin(); - - if (items.size() >= max_items) - std::advance(end, max_items); - else if (items.size() > 0) - std::advance(end, items.size()); - - QVector<SearchResult> results; - for (auto it = items.begin(); it != end; it++) { - const auto user = it->second; - results.push_back(SearchResult{QString::fromStdString(user.first), - QString::fromStdString(user.second)}); - } - - return results; -} - -std::vector<RoomMember> -Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len) -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto db = getMembersDb(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 user_id, user_data; - while (cursor.get(user_id, user_data, MDB_NEXT)) { - if (currentIndex < startIndex) { - currentIndex += 1; - continue; - } - - if (currentIndex >= endIndex) - break; - - try { - MemberInfo tmp = json::parse(user_data); - members.emplace_back( - RoomMember{QString::fromStdString(user_id), - QString::fromStdString(tmp.name), - QImage::fromData(image(txn, tmp.avatar_url))}); - } catch (const json::exception &e) { - nhlog::db()->warn("{}", e.what()); - } - - currentIndex += 1; - } - - cursor.close(); - txn.commit(); - - return members; -} - -void -Cache::saveTimelineMessages(lmdb::txn &txn, - const std::string &room_id, - const mtx::responses::Timeline &res) -{ - auto db = getMessagesDb(txn, room_id); - - using namespace mtx::events; - using namespace mtx::events::state; - - for (const auto &e : res.events) { - if (isStateEvent(e)) - continue; - - if (mpark::holds_alternative<RedactionEvent<msg::Redaction>>(e)) - continue; - - json obj = json::object(); - - obj["event"] = utils::serialize_event(e); - obj["token"] = res.prev_batch; - - lmdb::dbi_put(txn, - db, - lmdb::val(std::to_string(utils::event_timestamp(e))), - lmdb::val(obj.dump())); - } -} - -void -Cache::markSentNotification(const std::string &event_id) -{ - auto txn = lmdb::txn::begin(env_); - lmdb::dbi_put(txn, notificationsDb_, lmdb::val(event_id), lmdb::val(std::string(""))); - txn.commit(); -} - -void -Cache::removeReadNotification(const std::string &event_id) -{ - auto txn = lmdb::txn::begin(env_); - - lmdb::dbi_del(txn, notificationsDb_, lmdb::val(event_id), nullptr); - - txn.commit(); -} - -bool -Cache::isNotificationSent(const std::string &event_id) -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - lmdb::val value; - bool res = lmdb::dbi_get(txn, notificationsDb_, lmdb::val(event_id), value); - txn.commit(); - - return res; -} - -bool -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; - - auto txn = lmdb::txn::begin(env_); - auto db = getStatesDb(txn, room_id); - - uint16_t min_event_level = std::numeric_limits<uint16_t>::max(); - uint16_t user_level = std::numeric_limits<uint16_t>::min(); - - lmdb::val event; - bool res = lmdb::dbi_get(txn, db, lmdb::val(to_string(EventType::RoomPowerLevels)), event); - - if (res) { - try { - StateEvent<PowerLevels> msg = - json::parse(std::string(event.data(), event.size())); - - user_level = msg.content.user_level(user_id); - - for (const auto &ty : eventTypes) - min_event_level = - std::min(min_event_level, - (uint16_t)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(); - - return user_level >= min_event_level; -} - -std::vector<std::string> -Cache::roomMembers(const std::string &room_id) -{ - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - - std::vector<std::string> members; - std::string user_id, unused; - - auto db = getMembersDb(txn, room_id); - - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(user_id, unused, MDB_NEXT)) - members.emplace_back(std::move(user_id)); - cursor.close(); - - txn.commit(); - - return members; -} - -QHash<QString, QString> Cache::DisplayNames; -QHash<QString, QString> Cache::AvatarUrls; - -QString -Cache::displayName(const QString &room_id, const QString &user_id) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - if (DisplayNames.contains(fmt)) - return DisplayNames[fmt]; - - return user_id; -} - -std::string -Cache::displayName(const std::string &room_id, const std::string &user_id) -{ - auto fmt = QString::fromStdString(room_id + " " + user_id); - if (DisplayNames.contains(fmt)) - return DisplayNames[fmt].toStdString(); - - return user_id; -} - -QString -Cache::avatarUrl(const QString &room_id, const QString &user_id) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - if (AvatarUrls.contains(fmt)) - return AvatarUrls[fmt]; - - return QString(); -} - -void -Cache::insertDisplayName(const QString &room_id, - const QString &user_id, - const QString &display_name) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - DisplayNames.insert(fmt, display_name); -} - -void -Cache::removeDisplayName(const QString &room_id, const QString &user_id) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - DisplayNames.remove(fmt); -} - -void -Cache::insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - AvatarUrls.insert(fmt, avatar_url); -} - -void -Cache::removeAvatarUrl(const QString &room_id, const QString &user_id) -{ - auto fmt = QString("%1 %2").arg(room_id).arg(user_id); - AvatarUrls.remove(fmt); -}