diff --git a/include/Cache.h b/include/Cache.h
index 93668b8c..b3bc085b 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -17,12 +17,23 @@
#pragma once
+#include <QDebug>
#include <QDir>
#include <json.hpp>
#include <lmdb++.h>
#include <mtx/responses.hpp>
#include "RoomState.h"
+#include "Utils.h"
+
+struct SearchResult
+{
+ QString user_id;
+ QString display_name;
+};
+
+Q_DECLARE_METATYPE(SearchResult)
+Q_DECLARE_METATYPE(QVector<SearchResult>)
//! Used to uniquely identify a list of read receipts.
struct ReadReceiptKey
@@ -44,6 +55,60 @@ from_json(const json &j, ReadReceiptKey &key)
key.room_id = j.at("room_id").get<std::string>();
}
+//! UI info associated with a room.
+struct RoomInfo
+{
+ //! The calculated name of the room.
+ std::string name;
+ //! The topic of the room.
+ std::string topic;
+ //! The calculated avatar url of the room.
+ std::string avatar_url;
+ //! Whether or not the room is an invite.
+ bool is_invite = false;
+};
+
+inline void
+to_json(json &j, const RoomInfo &info)
+{
+ j["name"] = info.name;
+ j["topic"] = info.topic;
+ j["avatar_url"] = info.avatar_url;
+ j["is_invite"] = info.is_invite;
+}
+
+inline 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.is_invite = j.at("is_invite");
+}
+
+//! Basic information per member;
+struct MemberInfo
+{
+ std::string name;
+ std::string avatar_url;
+};
+
+inline void
+to_json(json &j, const MemberInfo &info)
+{
+ j["name"] = info.name;
+ j["avatar_url"] = info.avatar_url;
+}
+
+inline void
+from_json(const json &j, MemberInfo &info)
+{
+ info.name = j.at("name");
+ info.avatar_url = j.at("avatar_url");
+}
+
+Q_DECLARE_METATYPE(RoomInfo)
+
class Cache : public QObject
{
Q_OBJECT
@@ -51,22 +116,50 @@ class Cache : public QObject
public:
Cache(const QString &userId, QObject *parent = nullptr);
- void setState(const QString &nextBatchToken,
- const std::map<QString, QSharedPointer<RoomState>> &states);
+ static QHash<QString, QString> DisplayNames;
+ static QHash<QString, QString> AvatarUrls;
+
+ static std::string displayName(const std::string &room_id, const std::string &user_id);
+ static QString displayName(const QString &room_id, const QString &user_id);
+ static QString avatarUrl(const QString &room_id, const QString &user_id);
+
+ static void removeDisplayName(const QString &room_id, const QString &user_id);
+ static void removeAvatarUrl(const QString &room_id, const QString &user_id);
+
+ static void insertDisplayName(const QString &room_id,
+ const QString &user_id,
+ const QString &display_name);
+ static void insertAvatarUrl(const QString &room_id,
+ const QString &user_id,
+ const QString &avatar_url);
+
+ //! Load saved data for the display names & avatars.
+ void populateMembers();
+ std::vector<std::string> joinedRooms();
+
+ QMap<QString, RoomInfo> roomInfo();
+
+ //! Calculate & return the name of the room.
+ QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+ //! Retrieve the topic of the room if any.
+ QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+ //! Retrieve the room avatar's url if any.
+ QString getRoomAvatarUrl(lmdb::txn &txn,
+ lmdb::dbi &statesdb,
+ lmdb::dbi &membersdb,
+ const QString &room_id);
+
+ void saveState(const mtx::responses::Sync &res);
bool isInitialized() const;
QString nextBatchToken() const;
- void states();
-
- using Invites = std::map<std::string, mtx::responses::InvitedRoom>;
- Invites invites();
- void setInvites(const Invites &invites);
void deleteData();
- void unmount() { isMounted_ = false; };
- void removeRoom(const QString &roomid);
- void removeInvite(const QString &roomid);
+ void removeInvite(const std::string &room_id);
+ void removeRoom(lmdb::txn &txn, const std::string &roomid);
+ void removeRoom(const std::string &roomid);
+ void removeRoom(const QString &roomid) { removeRoom(roomid.toStdString()); };
void setup();
bool isFormatValid();
@@ -88,24 +181,206 @@ public:
QByteArray image(const QString &url) const;
void saveImage(const QString &url, const QByteArray &data);
-signals:
- void statesLoaded(std::map<QString, RoomState> states);
+ std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
+ std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
+ std::map<QString, RoomInfo> roomUpdates(const mtx::responses::Sync &sync)
+ {
+ return getRoomInfo(roomsWithStateUpdates(sync));
+ }
+
+ QVector<SearchResult> getAutocompleteMatches(const std::string &room_id,
+ const std::string &query,
+ std::uint8_t max_items = 5);
private:
+ //! Save an invited room.
+ void saveInvite(lmdb::txn &txn,
+ lmdb::dbi &statesdb,
+ lmdb::dbi &membersdb,
+ const mtx::responses::InvitedRoom &room);
+
+ QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+ QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
+ QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+
+ //! Remove a room from the cache.
+ // void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
+ template<class T>
+ void saveStateEvents(lmdb::txn &txn,
+ const lmdb::dbi &statesdb,
+ const lmdb::dbi &membersdb,
+ const std::string &room_id,
+ const std::vector<T> &events)
+ {
+ for (const auto &e : events)
+ saveStateEvent(txn, statesdb, membersdb, room_id, e);
+ }
+
+ template<class T>
+ void saveStateEvent(lmdb::txn &txn,
+ const lmdb::dbi &statesdb,
+ const lmdb::dbi &membersdb,
+ const std::string &room_id,
+ const T &event)
+ {
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ if (mpark::holds_alternative<StateEvent<Member>>(event)) {
+ const auto e = mpark::get<StateEvent<Member>>(event);
+
+ switch (e.content.membership) {
+ //
+ // We only keep users with invite or join membership.
+ //
+ case Membership::Invite:
+ case Membership::Join: {
+ auto display_name = e.content.display_name.empty()
+ ? e.state_key
+ : e.content.display_name;
+
+ // Lightweight representation of a member.
+ MemberInfo tmp{display_name, e.content.avatar_url};
+
+ lmdb::dbi_put(txn,
+ membersdb,
+ lmdb::val(e.state_key),
+ lmdb::val(json(tmp).dump()));
+
+ insertDisplayName(QString::fromStdString(room_id),
+ QString::fromStdString(e.state_key),
+ QString::fromStdString(display_name));
+
+ insertAvatarUrl(QString::fromStdString(room_id),
+ QString::fromStdString(e.state_key),
+ QString::fromStdString(e.content.avatar_url));
+
+ break;
+ }
+ default: {
+ lmdb::dbi_del(
+ txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
+
+ removeDisplayName(QString::fromStdString(room_id),
+ QString::fromStdString(e.state_key));
+ removeAvatarUrl(QString::fromStdString(room_id),
+ QString::fromStdString(e.state_key));
+
+ break;
+ }
+ }
+
+ return;
+ }
+
+ if (!isStateEvent(event))
+ return;
+
+ mpark::visit(
+ [&txn, &statesdb](auto e) {
+ lmdb::dbi_put(
+ txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
+ },
+ event);
+ }
+
+ template<class T>
+ bool isStateEvent(const T &e)
+ {
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ return mpark::holds_alternative<StateEvent<Aliases>>(e) ||
+ mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
+ mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+ mpark::holds_alternative<StateEvent<Create>>(e) ||
+ mpark::holds_alternative<StateEvent<GuestAccess>>(e) ||
+ mpark::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
+ mpark::holds_alternative<StateEvent<JoinRules>>(e) ||
+ mpark::holds_alternative<StateEvent<Name>>(e) ||
+ mpark::holds_alternative<StateEvent<PowerLevels>>(e) ||
+ mpark::holds_alternative<StateEvent<Topic>>(e);
+ }
+
+ template<class T>
+ bool containsStateUpdates(const T &e)
+ {
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ return mpark::holds_alternative<StateEvent<state::Avatar>>(e) ||
+ mpark::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+ mpark::holds_alternative<StateEvent<Name>>(e) ||
+ mpark::holds_alternative<StateEvent<Member>>(e) ||
+ mpark::holds_alternative<StateEvent<Topic>>(e);
+ }
+
+ bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
+ {
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ return mpark::holds_alternative<StrippedEvent<state::Avatar>>(e) ||
+ mpark::holds_alternative<StrippedEvent<CanonicalAlias>>(e) ||
+ mpark::holds_alternative<StrippedEvent<Name>>(e) ||
+ mpark::holds_alternative<StrippedEvent<Member>>(e) ||
+ mpark::holds_alternative<StrippedEvent<Topic>>(e);
+ }
+
+ void saveInvites(lmdb::txn &txn,
+ const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
+
+ //! Remove any saved invites that are not found in the input.
+ void removeStaleInvites(lmdb::txn &txn, const std::map<std::string, bool> &curr);
+
+ //! Sends signals for the rooms that are removed.
+ void removeLeftRooms(lmdb::txn &txn,
+ const std::map<std::string, mtx::responses::LeftRoom> &rooms)
+ {
+ for (const auto &room : rooms)
+ removeRoom(txn, room.first);
+ }
+
+ lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
+ {
+ return lmdb::dbi::open(
+ txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
+ }
+
+ lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
+ {
+ return lmdb::dbi::open(
+ txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
+ }
+
+ lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
+ {
+ return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
+ }
+
+ lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
+ {
+ return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
+ }
+
+ QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
+ {
+ if (!event.content.display_name.empty())
+ return QString::fromStdString(event.content.display_name);
+
+ return QString::fromStdString(event.state_key);
+ }
+
+ void setNextBatchToken(lmdb::txn &txn, const std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token);
- void insertRoomState(lmdb::txn &txn,
- const QString &roomid,
- const QSharedPointer<RoomState> &state);
lmdb::env env_;
- lmdb::dbi stateDb_;
- lmdb::dbi roomDb_;
+ lmdb::dbi syncStateDb_;
+ lmdb::dbi roomsDb_;
lmdb::dbi invitesDb_;
- lmdb::dbi imagesDb_;
+ lmdb::dbi mediaDb_;
lmdb::dbi readReceiptsDb_;
- bool isMounted_;
-
- QString userId_;
+ QString localUserId_;
QString cacheDirectory_;
};
|