From 6fae36abc404ffb7e6ae29c9edceda5231400f0a Mon Sep 17 00:00:00 2001 From: CH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com> Date: Sun, 28 Jun 2020 21:01:34 +0530 Subject: [PATCH] [WIP] Add Caching for users --- resources/qml/TimelineView.qml | 18 ++--- resources/qml/UserProfile.qml | 11 +-- src/Cache.cpp | 138 +++++++++++++++++++++++++++++++++ src/Cache.h | 17 ++++ src/CacheCryptoStructs.h | 30 +++++++ src/Cache_p.h | 19 +++++ src/ui/UserProfile.cpp | 104 ++++++++++++++----------- src/ui/UserProfile.h | 4 + 8 files changed, 282 insertions(+), 59 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index d9302ed7..3618140a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -21,6 +21,7 @@ Page { property real highlightHue: colors.highlight.hslHue property real highlightSat: colors.highlight.hslSaturation property real highlightLight: colors.highlight.hslLightness + property variant userProfile palette: colors @@ -238,6 +239,11 @@ Page { } } + Component{ + id: userProfileComponent + UserProfile{} + } + section { property: "section" } @@ -274,8 +280,6 @@ Page { } } - property variant userProfile - Row { height: userName.height spacing: 8 @@ -290,9 +294,7 @@ Page { MouseArea { anchors.fill: parent onClicked: { - if(userProfile) userProfile.destroy() - var component = Qt.createComponent("UserProfile.qml"); - userProfile = component.createObject(timelineRoot,{user_data : modelData}); + userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)}); userProfile.show(); } cursorShape: Qt.PointingHandCursor @@ -310,10 +312,8 @@ Page { anchors.fill: parent Layout.alignment: Qt.AlignHCenter onClicked: { - if(userProfile) userProfile.destroy() - var component = Qt.createComponent("UserProfile.qml") - userProfile = component.createObject(timelineRoot,{user_data : modelData}) - userProfile.show() + userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)}); + userProfile.show(); } cursorShape: Qt.PointingHandCursor propagateComposedEvents: true diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 6ef75031..a0b0f993 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -9,6 +9,7 @@ import "./device-verification" ApplicationWindow{ property var user_data + property var avatarUrl property var colors: currentActivePalette id:userProfileDialog @@ -52,11 +53,11 @@ ApplicationWindow{ Avatar{ id: userProfileAvatar - url:chat.model.avatarUrl(user_data.userId).replace("mxc://", "image://MxcImage/") + url: avatarUrl.replace("mxc://", "image://MxcImage/") height: 130 width: 130 - displayName: modelData.userName - userid: modelData.userId + displayName: user_data.userName + userid: user_data.userId Layout.alignment: Qt.AlignHCenter Layout.margins : { top: 10 @@ -68,7 +69,7 @@ ApplicationWindow{ text: user_data.userName fontSizeMode: Text.HorizontalFit font.pixelSize: 20 - color:TimelineManager.userColor(modelData.userId, colors.window) + color:TimelineManager.userColor(user_data.userId, colors.window) font.bold: true Layout.alignment: Qt.AlignHCenter } @@ -207,7 +208,7 @@ ApplicationWindow{ Layout.margins : { right : 10 - bottom : 10 + bottom: 5 } palette { diff --git a/src/Cache.cpp b/src/Cache.cpp index 0c692d07..5afeab06 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -2882,6 +2882,115 @@ Cache::statusMessage(const std::string &user_id) return status_msg; } +void +to_json(json &j, const UserCache &info) +{ + j["user_id"] = info.user_id; + j["is_user_verified"] = info.is_user_verified; + j["cross_verified"] = info.cross_verified; + j["keys"] = info.keys; +} + +void +from_json(const json &j, UserCache &info) +{ + info.user_id = j.at("user_id"); + info.is_user_verified = j.at("is_user_verified"); + info.cross_verified = j.at("cross_verified").get>(); + info.keys = j.at("keys").get(); +} + +UserCache +Cache::getUserCache(const std::string &user_id) +{ + lmdb::val verifiedVal; + + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + + UserCache verified_state; + if (res) { + verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + } + + txn.commit(); + + return verified_state; +} + +//! be careful when using make sure is_user_verified is not changed +int +Cache::setUserCache(const std::string &user_id, const UserCache &body) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + + auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + + txn.commit(); + + return res; +} + +int +Cache::deleteUserCache(const std::string &user_id) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getUserCacheDb(txn); + auto res = lmdb::dbi_del(txn, db, lmdb::val(user_id), nullptr); + + txn.commit(); + + return res; +} + +void +to_json(json &j, const DeviceVerifiedCache &info) +{ + j["user_id"] = info.user_id; + j["device_verified"] = info.device_verified; +} + +void +from_json(const json &j, DeviceVerifiedCache &info) +{ + info.user_id = j.at("user_id"); + info.device_verified = j.at("device_verified").get>(); +} + +DeviceVerifiedCache +Cache::getVerifiedCache(const std::string &user_id) +{ + lmdb::val verifiedVal; + + auto txn = lmdb::txn::begin(env_); + auto db = getDeviceVerifiedDb(txn); + auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), verifiedVal); + + DeviceVerifiedCache verified_state; + if (res) { + verified_state = json::parse(std::string(verifiedVal.data(), verifiedVal.size())); + } + + txn.commit(); + + return verified_state; +} + +int +Cache::setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +{ + auto txn = lmdb::txn::begin(env_); + auto db = getDeviceVerifiedDb(txn); + + auto res = lmdb::dbi_put(txn, db, lmdb::val(user_id), lmdb::val(json(body).dump())); + + txn.commit(); + + return res; +} + void to_json(json &j, const RoomInfo &info) { @@ -3063,6 +3172,35 @@ statusMessage(const std::string &user_id) { return instance_->statusMessage(user_id); } +UserCache +getUserCache(const std::string &user_id) +{ + return instance_->getUserCache(user_id); +} + +int +setUserCache(const std::string &user_id, const UserCache &body) +{ + return instance_->setUserCache(user_id, body); +} + +int +deleteUserCache(const std::string &user_id) +{ + return instance_->deleteUserCache(user_id); +} + +DeviceVerifiedCache +getVerifiedCache(const std::string &user_id) +{ + return instance_->getVerifiedCache(user_id); +} + +int +setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body) +{ + return instance_->setVerifiedCache(user_id, body); +} //! Load saved data for the display names & avatars. void diff --git a/src/Cache.h b/src/Cache.h index b5275623..34e79ab5 100644 --- a/src/Cache.h +++ b/src/Cache.h @@ -60,6 +60,23 @@ presenceState(const std::string &user_id); std::string statusMessage(const std::string &user_id); +//! user Cache +UserCache +getUserCache(const std::string &user_id); + +int +setUserCache(const std::string &user_id, const UserCache &body); + +int +deleteUserCache(const std::string &user_id); + +//! verified Cache +DeviceVerifiedCache +getVerifiedCache(const std::string &user_id); + +int +setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); + //! Load saved data for the display names & avatars. void populateMembers(); diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 14c9c86b..7344aef9 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -65,3 +65,33 @@ struct OlmSessionStorage std::mutex group_outbound_mtx; std::mutex group_inbound_mtx; }; + +struct UserCache +{ + //! user_id of the user + std::string user_id; + //! this stores if the user is verified (with cross-signing) + bool is_user_verified = false; + //! list of verified device_ids with cross-signing + std::vector cross_verified; + //! map of public key key_ids and their public_key + mtx::responses::QueryKeys keys; +}; + +void +to_json(nlohmann::json &j, const UserCache &info); +void +from_json(const nlohmann::json &j, UserCache &info); + +struct DeviceVerifiedCache +{ + //! user_id of the user + std::string user_id; + //! list of verified device_ids with device-verification + std::vector device_verified; +}; + +void +to_json(nlohmann::json &j, const DeviceVerifiedCache &info); +void +from_json(const nlohmann::json &j, DeviceVerifiedCache &info); diff --git a/src/Cache_p.h b/src/Cache_p.h index 61d91b0c..cf4416ce 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -54,6 +54,15 @@ public: mtx::presence::PresenceState presenceState(const std::string &user_id); std::string statusMessage(const std::string &user_id); + // user cache stores user keys + UserCache getUserCache(const std::string &user_id); + int setUserCache(const std::string &user_id, const UserCache &body); + int deleteUserCache(const std::string &user_id); + + // device verified cache + DeviceVerifiedCache getVerifiedCache(const std::string &user_id); + int setVerifiedCache(const std::string &user_id, const DeviceVerifiedCache &body); + static void removeDisplayName(const QString &room_id, const QString &user_id); static void removeAvatarUrl(const QString &room_id, const QString &user_id); @@ -510,6 +519,16 @@ private: return lmdb::dbi::open(txn, "presence", MDB_CREATE); } + lmdb::dbi getUserCacheDb(lmdb::txn &txn) + { + return lmdb::dbi::open(txn, std::string("user_cache").c_str(), MDB_CREATE); + } + + lmdb::dbi getDeviceVerifiedDb(lmdb::txn &txn) + { + return lmdb::dbi::open(txn, std::string("verified").c_str(), MDB_CREATE); + } + //! Retrieves or creates the database that stores the open OLM sessions between our device //! and the given curve25519 key which represents another device. //! diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 6aa4deff..c637280b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -1,9 +1,12 @@ #include "UserProfile.h" +#include "Cache.h" #include "ChatPage.h" #include "Logging.h" #include "Utils.h" #include "mtx/responses/crypto.hpp" +#include // only for debugging + UserProfile::UserProfile(QObject *parent) : QObject(parent) {} @@ -31,55 +34,66 @@ UserProfile::setUserId(const QString &user_id) } } +void +UserProfile::callback_fn(const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err, + std::string user_id) +{ + if (err) { + nhlog::net()->warn("failed to query device keys: {},{}", + err->matrix_error.errcode, + static_cast(err->status_code)); + return; + } + + if (res.device_keys.empty() || (res.device_keys.find(user_id) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } + + auto devices = res.device_keys.at(user_id); + QVector deviceInfo; + + for (const auto &d : devices) { + auto device = d.second; + + // TODO: Verify signatures and ignore those that don't pass. + DeviceInfo newdevice( + QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name)); + QString::fromStdString(device.unsigned_info.device_display_name); + + deviceInfo.append(std::move(newdevice)); + } + + std::sort( + deviceInfo.begin(), deviceInfo.end(), [](const DeviceInfo &a, const DeviceInfo &b) { + return a.device_id > b.device_id; + }); + + this->deviceList = std::move(deviceInfo); + emit UserProfile::deviceListUpdated(); +} + void UserProfile::fetchDeviceList(const QString &userID) { - auto localUser = utils::localUser(); - mtx::requests::QueryKeys req; - mtx::responses::QueryKeys res; - req.device_keys[userID.toStdString()] = {}; + auto localUser = utils::localUser(); + auto user_cache = cache::getUserCache(userID.toStdString()); - http::client()->query_keys( - req, - [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {},{}", - err->matrix_error.errcode, - static_cast(err->status_code)); - return; - } - - if (res.device_keys.empty() || - (res.device_keys.find(user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } - - auto devices = res.device_keys.at(user_id); - QVector deviceInfo; - - for (const auto &d : devices) { - auto device = d.second; - - // TODO: Verify signatures and ignore those that don't pass. - DeviceInfo newdevice( - QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name)); - QString::fromStdString(device.unsigned_info.device_display_name); - - deviceInfo.append(std::move(newdevice)); - } - - std::sort(deviceInfo.begin(), - deviceInfo.end(), - [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); - - this->deviceList = std::move(deviceInfo); - emit UserProfile::deviceListUpdated(); - }); + if (user_cache.user_id == userID.toStdString()) { + mtx::http::ClientError error; + this->callback_fn(user_cache.keys, std::move(error), userID.toStdString()); + } else { + mtx::requests::QueryKeys req; + req.device_keys[userID.toStdString()] = {}; + http::client()->query_keys( + req, + [user_id = userID.toStdString(), this](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { + this->callback_fn(res, err, user_id); + }); + } } void diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index ad92d182..befd82ec 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -47,4 +47,8 @@ signals: private: QVector deviceList; QString userId; + + void callback_fn(const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err, + std::string user_id); }; \ No newline at end of file