summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/AvatarProvider.cpp14
-rw-r--r--src/Cache.cpp561
-rw-r--r--src/Cache.h744
-rw-r--r--src/Cache_p.h494
-rw-r--r--src/ChatPage.cpp87
-rw-r--r--src/CommunitiesList.cpp4
-rw-r--r--src/MainWindow.cpp6
-rw-r--r--src/MxcImageProvider.cpp10
-rw-r--r--src/Olm.cpp27
-rw-r--r--src/QuickSwitcher.cpp3
-rw-r--r--src/TextInputWidget.cpp4
-rw-r--r--src/UserSettingsPage.cpp4
-rw-r--r--src/Utils.cpp2
-rw-r--r--src/Utils.h2
-rw-r--r--src/dialogs/MemberList.cpp4
-rw-r--r--src/dialogs/ReadReceipts.cpp2
-rw-r--r--src/dialogs/RoomSettings.cpp12
-rw-r--r--src/dialogs/RoomSettings.h4
-rw-r--r--src/dialogs/UserProfile.cpp8
-rw-r--r--src/popups/PopupItem.cpp6
-rw-r--r--src/popups/PopupItem.h3
-rw-r--r--src/popups/ReplyPopup.cpp1
-rw-r--r--src/popups/ReplyPopup.h9
-rw-r--r--src/popups/SuggestionsPopup.h7
-rw-r--r--src/popups/UserMentions.cpp2
-rw-r--r--src/timeline/TimelineModel.cpp23
28 files changed, 1403 insertions, 643 deletions
diff --git a/.gitignore b/.gitignore
index 2d772e58..6d178679 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@ moc_*.cpp
 qrc_*.cpp
 ui_*.h
 *-build-*
+/.clangd/
 
 # QtCreator
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e86ee14..5c68d614 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -357,7 +357,7 @@ qt5_wrap_cpp(MOC_HEADERS
     src/notifications/Manager.h
 
     src/AvatarProvider.h
-    src/Cache.h
+    src/Cache_p.h
     src/ChatPage.h
     src/CommunitiesListItem.h
     src/CommunitiesList.h
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp
index 68b6901e..1587a9a1 100644
--- a/src/AvatarProvider.cpp
+++ b/src/AvatarProvider.cpp
@@ -35,9 +35,6 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
 
         const auto cacheKey = avatarUrl + "_size_" + size;
 
-        if (!cache::client())
-                return;
-
         if (avatarUrl.isEmpty())
                 return;
 
@@ -47,7 +44,7 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
                 return;
         }
 
-        auto data = cache::client()->image(avatarUrl);
+        auto data = cache::image(avatarUrl);
         if (!data.isNull()) {
                 pixmap.loadFromData(data);
                 avatar_cache.insert(cacheKey, pixmap);
@@ -82,7 +79,7 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
                           return;
                   }
 
-                  cache::client()->saveImage(opts.mxc_url, res);
+                  cache::saveImage(opts.mxc_url, res);
 
                   emit proxy->avatarDownloaded(QByteArray(res.data(), res.size()));
           });
@@ -95,12 +92,7 @@ resolve(const QString &room_id,
         QObject *receiver,
         AvatarCallback callback)
 {
-        const auto key       = QString("%1 %2").arg(room_id).arg(user_id);
-        const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
-        const auto cacheKey  = avatarUrl + "_size_" + size;
-
-        if (!Cache::AvatarUrls.contains(key) || !cache::client())
-                return;
+        const auto avatarUrl = cache::avatarUrl(room_id, user_id);
 
         resolve(avatarUrl, size, receiver, callback);
 }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index ee53ffbd..79425fa1 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -30,6 +30,7 @@
 #include <mtx/responses/common.hpp>
 
 #include "Cache.h"
+#include "Cache_p.h"
 #include "Utils.h"
 
 //! Should be changed when a breaking change occurs in the cache format.
@@ -89,29 +90,20 @@ namespace {
 std::unique_ptr<Cache> instance_ = nullptr;
 }
 
-namespace cache {
-void
-init(const QString &user_id)
+int
+numeric_key_comparison(const MDB_val *a, const MDB_val *b)
 {
-        qRegisterMetaType<SearchResult>();
-        qRegisterMetaType<QVector<SearchResult>>();
-        qRegisterMetaType<RoomMember>();
-        qRegisterMetaType<RoomSearchResult>();
-        qRegisterMetaType<RoomInfo>();
-        qRegisterMetaType<QMap<QString, RoomInfo>>();
-        qRegisterMetaType<QMap<QString, mtx::responses::Notifications>>();
-        qRegisterMetaType<std::map<QString, RoomInfo>>();
-        qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>();
+        auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
+        auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
 
-        instance_ = std::make_unique<Cache>(user_id);
-}
+        if (lhs < rhs)
+                return 1;
+        else if (lhs == rhs)
+                return 0;
 
-Cache *
-client()
-{
-        return instance_.get();
+        return -1;
 }
-} // namespace cache
+
 
 Cache::Cache(const QString &userId, QObject *parent)
   : QObject{parent}
@@ -2321,20 +2313,6 @@ from_json(const json &j, RoomInfo &info)
                 info.tags = j.at("tags").get<std::vector<std::string>>();
 }
 
-int
-numeric_key_comparison(const MDB_val *a, const MDB_val *b)
-{
-        auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
-        auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
-
-        if (lhs < rhs)
-                return 1;
-        else if (lhs == rhs)
-                return 0;
-
-        return -1;
-}
-
 void
 to_json(json &j, const ReadReceiptKey &key)
 {
@@ -2407,3 +2385,520 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
         msg.session_id = obj.at("session_id");
         msg.sender_key = obj.at("sender_key");
 }
+
+namespace cache {
+void
+init(const QString &user_id)
+{
+        qRegisterMetaType<SearchResult>();
+        qRegisterMetaType<QVector<SearchResult>>();
+        qRegisterMetaType<RoomMember>();
+        qRegisterMetaType<RoomSearchResult>();
+        qRegisterMetaType<RoomInfo>();
+        qRegisterMetaType<QMap<QString, RoomInfo>>();
+        qRegisterMetaType<QMap<QString, mtx::responses::Notifications>>();
+        qRegisterMetaType<std::map<QString, RoomInfo>>();
+        qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>();
+
+        instance_ = std::make_unique<Cache>(user_id);
+}
+
+Cache *
+client()
+{
+        return instance_.get();
+}
+
+std::string
+displayName(const std::string &room_id, const std::string &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);
+}
+QString
+avatarUrl(const QString &room_id, const QString &user_id)
+{
+        return instance_->avatarUrl(room_id, user_id);
+}
+
+QString
+userColor(const QString &user_id)
+{
+        return instance_->userColor(user_id);
+}
+
+void
+removeDisplayName(const QString &room_id, const QString &user_id)
+{
+        instance_->removeDisplayName(room_id, user_id);
+}
+void
+removeAvatarUrl(const QString &room_id, const QString &user_id)
+{
+        instance_->removeAvatarUrl(room_id, user_id);
+}
+void
+removeUserColor(const QString &user_id)
+{
+        instance_->removeUserColor(user_id);
+}
+
+void
+insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name)
+{
+        instance_->insertDisplayName(room_id, user_id, display_name);
+}
+void
+insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
+{
+        instance_->insertAvatarUrl(room_id, user_id, avatar_url);
+}
+void
+insertUserColor(const QString &user_id, const QString &color_name)
+{
+        instance_->insertUserColor(user_id, color_name);
+}
+
+void
+clearUserColors()
+{
+        instance_->clearUserColors();
+}
+
+//! Load saved data for the display names & avatars.
+void
+populateMembers()
+{
+        instance_->populateMembers();
+}
+
+std::vector<std::string>
+joinedRooms()
+{
+        return instance_->joinedRooms();
+}
+
+QMap<QString, RoomInfo>
+roomInfo(bool withInvites)
+{
+        return instance_->roomInfo(withInvites);
+}
+std::map<QString, bool>
+invites()
+{
+        return instance_->invites();
+}
+
+QString
+getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
+{
+        return instance_->getRoomName(txn, statesdb, membersdb);
+}
+mtx::events::state::JoinRule
+getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomJoinRule(txn, statesdb);
+}
+bool
+getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomGuestAccess(txn, statesdb);
+}
+QString
+getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomTopic(txn, statesdb);
+}
+QString
+getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb, const QString &room_id)
+{
+        return instance_->getRoomAvatarUrl(txn, statesdb, membersdb, room_id);
+}
+
+QString
+getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+        return instance_->getRoomVersion(txn, statesdb);
+}
+
+std::vector<RoomMember>
+getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
+{
+        return instance_->getMembers(room_id, startIndex, len);
+}
+
+void
+saveState(const mtx::responses::Sync &res)
+{
+        instance_->saveState(res);
+}
+bool
+isInitialized()
+{
+        return instance_->isInitialized();
+}
+
+std::string
+nextBatchToken()
+{
+        return instance_->nextBatchToken();
+}
+
+void
+deleteData()
+{
+        instance_->deleteData();
+}
+
+void
+removeInvite(lmdb::txn &txn, const std::string &room_id)
+{
+        instance_->removeInvite(txn, room_id);
+}
+void
+removeInvite(const std::string &room_id)
+{
+        instance_->removeInvite(room_id);
+}
+void
+removeRoom(lmdb::txn &txn, const std::string &roomid)
+{
+        instance_->removeRoom(txn, roomid);
+}
+void
+removeRoom(const std::string &roomid)
+{
+        instance_->removeRoom(roomid);
+}
+void
+removeRoom(const QString &roomid)
+{
+        instance_->removeRoom(roomid.toStdString());
+}
+void
+setup()
+{
+        instance_->setup();
+}
+
+bool
+isFormatValid()
+{
+        return instance_->isFormatValid();
+}
+void
+setCurrentFormat()
+{
+        instance_->setCurrentFormat();
+}
+
+std::map<QString, mtx::responses::Timeline>
+roomMessages()
+{
+        return instance_->roomMessages();
+}
+
+QMap<QString, mtx::responses::Notifications>
+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);
+}
+
+//! Check if the given user has power leve greater than than
+//! lowest power level of the given events.
+bool
+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);
+}
+
+//! Retrieves the saved room avatar.
+QImage
+getRoomAvatar(const QString &id)
+{
+        return instance_->getRoomAvatar(id);
+}
+QImage
+getRoomAvatar(const std::string &id)
+{
+        return instance_->getRoomAvatar(id);
+}
+
+void
+updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts)
+{
+        instance_->updateReadReceipt(txn, room_id, receipts);
+}
+
+UserReceipts
+readReceipts(const QString &event_id, const QString &room_id)
+{
+        return instance_->readReceipts(event_id, room_id);
+}
+
+//! Filter the events that have at least one read receipt.
+std::vector<QString>
+filterReadEvents(const QString &room_id,
+                 const std::vector<QString> &event_ids,
+                 const std::string &excluded_user)
+{
+        return instance_->filterReadEvents(room_id, event_ids, excluded_user);
+}
+//! Add event for which we are expecting some read receipts.
+void
+addPendingReceipt(const QString &room_id, const QString &event_id)
+{
+        instance_->addPendingReceipt(room_id, event_id);
+}
+void
+removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id)
+{
+        instance_->removePendingReceipt(txn, room_id, event_id);
+}
+void
+notifyForReadReceipts(const std::string &room_id)
+{
+        instance_->notifyForReadReceipts(room_id);
+}
+std::vector<QString>
+pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id)
+{
+        return instance_->pendingReceiptsEvents(txn, room_id);
+}
+
+QByteArray
+image(const QString &url)
+{
+        return instance_->image(url);
+}
+QByteArray
+image(lmdb::txn &txn, const std::string &url)
+{
+        return instance_->image(txn, url);
+}
+void
+saveImage(const std::string &url, const std::string &data)
+{
+        instance_->saveImage(url, data);
+}
+void
+saveImage(const QString &url, const QByteArray &data)
+{
+        instance_->saveImage(url, data);
+}
+
+RoomInfo
+singleRoomInfo(const std::string &room_id)
+{
+        return instance_->singleRoomInfo(room_id);
+}
+std::vector<std::string>
+roomsWithStateUpdates(const mtx::responses::Sync &res)
+{
+        return instance_->roomsWithStateUpdates(res);
+}
+std::vector<std::string>
+roomsWithTagUpdates(const mtx::responses::Sync &res)
+{
+        return instance_->roomsWithTagUpdates(res);
+}
+std::map<QString, RoomInfo>
+getRoomInfo(const std::vector<std::string> &rooms)
+{
+        return instance_->getRoomInfo(rooms);
+}
+
+//! Calculates which the read status of a room.
+//! Whether all the events in the timeline have been read.
+bool
+calculateRoomReadStatus(const std::string &room_id)
+{
+        return instance_->calculateRoomReadStatus(room_id);
+}
+void
+calculateRoomReadStatus()
+{
+        instance_->calculateRoomReadStatus();
+}
+
+QVector<SearchResult>
+searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items)
+{
+        return instance_->searchUsers(room_id, query, max_items);
+}
+std::vector<RoomSearchResult>
+searchRooms(const std::string &query, std::uint8_t max_items)
+{
+        return instance_->searchRooms(query, max_items);
+}
+
+void
+markSentNotification(const std::string &event_id)
+{
+        instance_->markSentNotification(event_id);
+}
+//! Removes an event from the sent notifications.
+void
+removeReadNotification(const std::string &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);
+}
+
+//! Add all notifications containing a user mention to the db.
+void
+saveTimelineMentions(const mtx::responses::Notifications &res)
+{
+        instance_->saveTimelineMentions(res);
+}
+
+//! Remove old unused data.
+void
+deleteOldMessages()
+{
+        instance_->deleteOldMessages();
+}
+void
+deleteOldData() noexcept
+{
+        instance_->deleteOldData();
+}
+//! Retrieve all saved room ids.
+std::vector<std::string>
+getRoomIds(lmdb::txn &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);
+}
+bool
+isRoomEncrypted(const std::string &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);
+}
+
+//
+// Outbound Megolm Sessions
+//
+void
+saveOutboundMegolmSession(const std::string &room_id,
+                          const OutboundGroupSessionData &data,
+                          mtx::crypto::OutboundGroupSessionPtr session)
+{
+        instance_->saveOutboundMegolmSession(room_id, data, std::move(session));
+}
+OutboundGroupSessionDataRef
+getOutboundMegolmSession(const std::string &room_id)
+{
+        return instance_->getOutboundMegolmSession(room_id);
+}
+bool
+outboundMegolmSessionExists(const std::string &room_id) noexcept
+{
+        return instance_->outboundMegolmSessionExists(room_id);
+}
+void
+updateOutboundMegolmSession(const std::string &room_id, int message_index)
+{
+        instance_->updateOutboundMegolmSession(room_id, message_index);
+}
+
+void
+importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
+{
+        instance_->importSessionKeys(keys);
+}
+mtx::crypto::ExportedSessionKeys
+exportSessionKeys()
+{
+        return instance_->exportSessionKeys();
+}
+
+//
+// Inbound Megolm Sessions
+//
+void
+saveInboundMegolmSession(const MegolmSessionIndex &index,
+                         mtx::crypto::InboundGroupSessionPtr session)
+{
+        instance_->saveInboundMegolmSession(index, std::move(session));
+}
+OlmInboundGroupSession *
+getInboundMegolmSession(const MegolmSessionIndex &index)
+{
+        return instance_->getInboundMegolmSession(index);
+}
+bool
+inboundMegolmSessionExists(const MegolmSessionIndex &index)
+{
+        return instance_->inboundMegolmSessionExists(index);
+}
+
+//
+// Olm Sessions
+//
+void
+saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session)
+{
+        instance_->saveOlmSession(curve25519, std::move(session));
+}
+std::vector<std::string>
+getOlmSessions(const std::string &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);
+}
+
+void
+saveOlmAccount(const std::string &pickled)
+{
+        instance_->saveOlmAccount(pickled);
+}
+std::string
+restoreOlmAccount()
+{
+        return instance_->restoreOlmAccount();
+}
+
+void
+restoreSessions()
+{
+        return instance_->restoreSessions();
+}
+} // namespace cache
+
diff --git a/src/Cache.h b/src/Cache.h
index 02346287..da4e2040 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -17,501 +17,285 @@
 
 #pragma once
 
-#include <mutex>
-#include <optional>
-
 #include <QDateTime>
 #include <QDir>
 #include <QImage>
 #include <QString>
 
 #include <lmdb++.h>
-#include <nlohmann/json.hpp>
 
 #include <mtx/responses.hpp>
-#include <mtxclient/crypto/client.hpp>
 
 #include "CacheCryptoStructs.h"
 #include "CacheStructs.h"
-#include "Logging.h"
-#include "MatrixClient.h"
 
-int
-numeric_key_comparison(const MDB_val *a, const MDB_val *b);
+namespace cache {
+void
+init(const QString &user_id);
+
+std::string
+displayName(const std::string &room_id, const std::string &user_id);
+QString
+displayName(const QString &room_id, const QString &user_id);
+QString
+avatarUrl(const QString &room_id, const QString &user_id);
+QString
+userColor(const QString &user_id);
+
+void
+removeDisplayName(const QString &room_id, const QString &user_id);
+void
+removeAvatarUrl(const QString &room_id, const QString &user_id);
+void
+removeUserColor(const QString &user_id);
+
+void
+insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name);
+void
+insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
+void
+insertUserColor(const QString &user_id, const QString &color_name);
+
+void
+clearUserColors();
+
+//! Load saved data for the display names & avatars.
+void
+populateMembers();
+std::vector<std::string>
+joinedRooms();
+
+QMap<QString, RoomInfo>
+roomInfo(bool withInvites = true);
+std::map<QString, bool>
+invites();
+
+//! Calculate & return the name of the room.
+QString
+getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+//! Get room join rules
+mtx::events::state::JoinRule
+getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
+bool
+getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
+//! 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);
+//! Retrieve the version of the room if any.
+QString
+getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
+
+//! Retrieve member info from a room.
+std::vector<RoomMember>
+getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
 
-class Cache : public QObject
+void
+saveState(const mtx::responses::Sync &res);
+bool
+isInitialized();
+
+std::string
+nextBatchToken();
+
+void
+deleteData();
+
+void
+removeInvite(lmdb::txn &txn, const std::string &room_id);
+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);
+void
+setup();
+
+bool
+isFormatValid();
+void
+setCurrentFormat();
+
+std::map<QString, mtx::responses::Timeline>
+roomMessages();
+
+QMap<QString, mtx::responses::Notifications>
+getTimelineMentions();
+
+//! Retrieve all the user ids from a room.
+std::vector<std::string>
+roomMembers(const std::string &room_id);
+
+//! Check if the given user has power leve greater than than
+//! lowest power level of the given events.
+bool
+hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
+                    const std::string &room_id,
+                    const std::string &user_id);
+
+//! Retrieves the saved room avatar.
+QImage
+getRoomAvatar(const QString &id);
+QImage
+getRoomAvatar(const std::string &id);
+
+//! Adds a user to the read list for the given event.
+//!
+//! There should be only one user id present in a receipt list per room.
+//! The user id should be removed from any other lists.
+using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
+void
+updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts);
+
+//! Retrieve all the read receipts for the given event id and room.
+//!
+//! Returns a map of user ids and the time of the read receipt in milliseconds.
+using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
+UserReceipts
+readReceipts(const QString &event_id, const QString &room_id);
+
+//! Filter the events that have at least one read receipt.
+std::vector<QString>
+filterReadEvents(const QString &room_id,
+                 const std::vector<QString> &event_ids,
+                 const std::string &excluded_user);
+//! Add event for which we are expecting some read receipts.
+void
+addPendingReceipt(const QString &room_id, const QString &event_id);
+void
+removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id);
+void
+notifyForReadReceipts(const std::string &room_id);
+std::vector<QString>
+pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
+
+QByteArray
+image(const QString &url);
+QByteArray
+image(lmdb::txn &txn, const std::string &url);
+inline QByteArray
+image(const std::string &url)
+{
+        return image(QString::fromStdString(url));
+}
+void
+saveImage(const std::string &url, const std::string &data);
+void
+saveImage(const QString &url, const QByteArray &data);
+
+RoomInfo
+singleRoomInfo(const std::string &room_id);
+std::vector<std::string>
+roomsWithStateUpdates(const mtx::responses::Sync &res);
+std::vector<std::string>
+roomsWithTagUpdates(const mtx::responses::Sync &res);
+std::map<QString, RoomInfo>
+getRoomInfo(const std::vector<std::string> &rooms);
+inline std::map<QString, RoomInfo>
+roomUpdates(const mtx::responses::Sync &sync)
 {
-        Q_OBJECT
-
-public:
-        Cache(const QString &userId, QObject *parent = nullptr);
-
-        static QHash<QString, QString> DisplayNames;
-        static QHash<QString, QString> AvatarUrls;
-        static QHash<QString, QString> UserColors;
-
-        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 QString userColor(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 removeUserColor(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);
-        static void insertUserColor(const QString &user_id, const QString &color_name);
-
-        static void clearUserColors();
-
-        //! Load saved data for the display names & avatars.
-        void populateMembers();
-        std::vector<std::string> joinedRooms();
-
-        QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
-        std::map<QString, bool> invites();
-
-        //! Calculate & return the name of the room.
-        QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
-        //! Get room join rules
-        mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
-        bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
-        //! 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);
-        //! Retrieve the version of the room if any.
-        QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
-
-        //! Retrieve member info from a room.
-        std::vector<RoomMember> getMembers(const std::string &room_id,
-                                           std::size_t startIndex = 0,
-                                           std::size_t len        = 30);
-
-        void saveState(const mtx::responses::Sync &res);
-        bool isInitialized() const;
-
-        std::string nextBatchToken() const;
-
-        void deleteData();
-
-        void removeInvite(lmdb::txn &txn, const std::string &room_id);
-        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();
-        void setCurrentFormat();
-
-        std::map<QString, mtx::responses::Timeline> roomMessages();
-
-        QMap<QString, mtx::responses::Notifications> getTimelineMentions();
-
-        //! Retrieve all the user ids from a room.
-        std::vector<std::string> roomMembers(const std::string &room_id);
-
-        //! Check if the given user has power leve greater than than
-        //! lowest power level of the given events.
-        bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
-                                 const std::string &room_id,
-                                 const std::string &user_id);
-
-        //! Retrieves the saved room avatar.
-        QImage getRoomAvatar(const QString &id);
-        QImage getRoomAvatar(const std::string &id);
-
-        //! Adds a user to the read list for the given event.
-        //!
-        //! There should be only one user id present in a receipt list per room.
-        //! The user id should be removed from any other lists.
-        using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
-        void updateReadReceipt(lmdb::txn &txn,
-                               const std::string &room_id,
-                               const Receipts &receipts);
-
-        //! Retrieve all the read receipts for the given event id and room.
-        //!
-        //! Returns a map of user ids and the time of the read receipt in milliseconds.
-        using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
-        UserReceipts readReceipts(const QString &event_id, const QString &room_id);
-
-        //! Filter the events that have at least one read receipt.
-        std::vector<QString> filterReadEvents(const QString &room_id,
-                                              const std::vector<QString> &event_ids,
-                                              const std::string &excluded_user);
-        //! Add event for which we are expecting some read receipts.
-        void addPendingReceipt(const QString &room_id, const QString &event_id);
-        void removePendingReceipt(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const std::string &event_id);
-        void notifyForReadReceipts(const std::string &room_id);
-        std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
-
-        QByteArray image(const QString &url) const;
-        QByteArray image(lmdb::txn &txn, const std::string &url) const;
-        QByteArray image(const std::string &url) const
-        {
-                return image(QString::fromStdString(url));
-        }
-        void saveImage(const std::string &url, const std::string &data);
-        void saveImage(const QString &url, const QByteArray &data);
-
-        RoomInfo singleRoomInfo(const std::string &room_id);
-        std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
-        std::vector<std::string> roomsWithTagUpdates(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));
-        }
-        std::map<QString, RoomInfo> roomTagUpdates(const mtx::responses::Sync &sync)
-        {
-                return getRoomInfo(roomsWithTagUpdates(sync));
-        }
-
-        //! Calculates which the read status of a room.
-        //! Whether all the events in the timeline have been read.
-        bool calculateRoomReadStatus(const std::string &room_id);
-        void calculateRoomReadStatus();
-
-        QVector<SearchResult> searchUsers(const std::string &room_id,
-                                          const std::string &query,
-                                          std::uint8_t max_items = 5);
-        std::vector<RoomSearchResult> searchRooms(const std::string &query,
-                                                  std::uint8_t max_items = 5);
-
-        void markSentNotification(const std::string &event_id);
-        //! Removes an event from the sent notifications.
-        void removeReadNotification(const std::string &event_id);
-        //! Check if we have sent a desktop notification for the given event id.
-        bool isNotificationSent(const std::string &event_id);
-
-        //! Add all notifications containing a user mention to the db.
-        void saveTimelineMentions(const mtx::responses::Notifications &res);
-
-        //! Remove old unused data.
-        void deleteOldMessages();
-        void deleteOldData() noexcept;
-        //! Retrieve all saved room ids.
-        std::vector<std::string> getRoomIds(lmdb::txn &txn);
-
-        //! Mark a room that uses e2e encryption.
-        void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
-        bool isRoomEncrypted(const std::string &room_id);
-
-        //! Save the public keys for a device.
-        void saveDeviceKeys(const std::string &device_id);
-        void getDeviceKeys(const std::string &device_id);
-
-        //! Save the device list for a user.
-        void setDeviceList(const std::string &user_id, const std::vector<std::string> &devices);
-        std::vector<std::string> getDeviceList(const std::string &user_id);
-
-        //! Check if a user is a member of the room.
-        bool isRoomMember(const std::string &user_id, const std::string &room_id);
-
-        //
-        // Outbound Megolm Sessions
-        //
-        void saveOutboundMegolmSession(const std::string &room_id,
-                                       const OutboundGroupSessionData &data,
-                                       mtx::crypto::OutboundGroupSessionPtr session);
-        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
-        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
-        void updateOutboundMegolmSession(const std::string &room_id, int message_index);
-
-        void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
-        mtx::crypto::ExportedSessionKeys exportSessionKeys();
-
-        //
-        // Inbound Megolm Sessions
-        //
-        void saveInboundMegolmSession(const MegolmSessionIndex &index,
-                                      mtx::crypto::InboundGroupSessionPtr session);
-        OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
-        bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
-
-        //
-        // Olm Sessions
-        //
-        void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
-        std::vector<std::string> getOlmSessions(const std::string &curve25519);
-        std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
-                                                                const std::string &session_id);
-
-        void saveOlmAccount(const std::string &pickled);
-        std::string restoreOlmAccount();
-
-        void restoreSessions();
-
-        OlmSessionStorage session_storage;
-
-signals:
-        void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
-        void roomReadStatus(const std::map<QString, bool> &status);
-
-private:
-        //! Save an invited room.
-        void saveInvite(lmdb::txn &txn,
-                        lmdb::dbi &statesdb,
-                        lmdb::dbi &membersdb,
-                        const mtx::responses::InvitedRoom &room);
-
-        //! Add a notification containing a user mention to the db.
-        void saveTimelineMentions(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const QList<mtx::responses::Notification> &res);
-
-        //! Get timeline items that a user was mentions in for a given room
-        mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
-                                                                 const std::string &room_id);
-
-        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);
-
-        DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
-        void saveTimelineMessages(lmdb::txn &txn,
-                                  const std::string &room_id,
-                                  const mtx::responses::Timeline &res);
-
-        mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
-
-        //! 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 (auto e = std::get_if<StateEvent<Member>>(&event); e != nullptr) {
-                        switch (e->content.membership) {
-                        //
-                        // We only keep users with invite or join membership.
-                        //
-                        case Membership::Invite:
-                        case Membership::Join: {
-                                auto display_name = e->content.display_name.empty()
-                                                      ? e->state_key
-                                                      : e->content.display_name;
-
-                                // 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;
-                } else if (std::holds_alternative<StateEvent<Encryption>>(event)) {
-                        setEncryptedRoom(txn, room_id);
-                        return;
-                }
-
-                if (!isStateEvent(event))
-                        return;
-
-                std::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 std::holds_alternative<StateEvent<Aliases>>(e) ||
-                       std::holds_alternative<StateEvent<state::Avatar>>(e) ||
-                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
-                       std::holds_alternative<StateEvent<Create>>(e) ||
-                       std::holds_alternative<StateEvent<GuestAccess>>(e) ||
-                       std::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
-                       std::holds_alternative<StateEvent<JoinRules>>(e) ||
-                       std::holds_alternative<StateEvent<Name>>(e) ||
-                       std::holds_alternative<StateEvent<Member>>(e) ||
-                       std::holds_alternative<StateEvent<PowerLevels>>(e) ||
-                       std::holds_alternative<StateEvent<Topic>>(e);
-        }
-
-        template<class T>
-        bool containsStateUpdates(const T &e)
-        {
-                using namespace mtx::events;
-                using namespace mtx::events::state;
-
-                return std::holds_alternative<StateEvent<state::Avatar>>(e) ||
-                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
-                       std::holds_alternative<StateEvent<Name>>(e) ||
-                       std::holds_alternative<StateEvent<Member>>(e) ||
-                       std::holds_alternative<StateEvent<Topic>>(e);
-        }
-
-        bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
-        {
-                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);
-        }
-
-        void saveInvites(lmdb::txn &txn,
-                         const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
-
-        //! 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);
-
-                        // Clean up leftover invites.
-                        removeInvite(txn, room.first);
-                }
-        }
-
-        lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
-        {
-                return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
-        }
-
-        lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                auto db =
-                  lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
-                lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
-
-                return db;
-        }
-
-        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);
-        }
-
-        lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
-        {
-                return lmdb::dbi::open(txn, std::string(room_id + "/mentions").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.
-        //!
-        //! Each entry is a map from the session_id to the pickled representation of the session.
-        lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
-        {
-                return lmdb::dbi::open(
-                  txn, std::string("olm_sessions/" + curve25519_key).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);
-
-        lmdb::env env_;
-        lmdb::dbi syncStateDb_;
-        lmdb::dbi roomsDb_;
-        lmdb::dbi invitesDb_;
-        lmdb::dbi mediaDb_;
-        lmdb::dbi readReceiptsDb_;
-        lmdb::dbi notificationsDb_;
-
-        lmdb::dbi devicesDb_;
-        lmdb::dbi deviceKeysDb_;
-
-        lmdb::dbi inboundMegolmSessionDb_;
-        lmdb::dbi outboundMegolmSessionDb_;
-
-        QString localUserId_;
-        QString cacheDirectory_;
-};
+        return getRoomInfo(roomsWithStateUpdates(sync));
+}
+inline std::map<QString, RoomInfo>
+roomTagUpdates(const mtx::responses::Sync &sync)
+{
+        return getRoomInfo(roomsWithTagUpdates(sync));
+}
 
-namespace cache {
+//! Calculates which the read status of a room.
+//! Whether all the events in the timeline have been read.
+bool
+calculateRoomReadStatus(const std::string &room_id);
 void
-init(const QString &user_id);
+calculateRoomReadStatus();
+
+QVector<SearchResult>
+searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items = 5);
+std::vector<RoomSearchResult>
+searchRooms(const std::string &query, std::uint8_t max_items = 5);
+
+void
+markSentNotification(const std::string &event_id);
+//! Removes an event from the sent notifications.
+void
+removeReadNotification(const std::string &event_id);
+//! Check if we have sent a desktop notification for the given event id.
+bool
+isNotificationSent(const std::string &event_id);
+
+//! Add all notifications containing a user mention to the db.
+void
+saveTimelineMentions(const mtx::responses::Notifications &res);
 
-Cache *
-client();
+//! Remove old unused data.
+void
+deleteOldMessages();
+void
+deleteOldData() noexcept;
+//! Retrieve all saved room ids.
+std::vector<std::string>
+getRoomIds(lmdb::txn &txn);
+
+//! Mark a room that uses e2e encryption.
+void
+setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
+bool
+isRoomEncrypted(const std::string &room_id);
+
+//! Check if a user is a member of the room.
+bool
+isRoomMember(const std::string &user_id, const std::string &room_id);
+
+//
+// Outbound Megolm Sessions
+//
+void
+saveOutboundMegolmSession(const std::string &room_id,
+                          const OutboundGroupSessionData &data,
+                          mtx::crypto::OutboundGroupSessionPtr session);
+OutboundGroupSessionDataRef
+getOutboundMegolmSession(const std::string &room_id);
+bool
+outboundMegolmSessionExists(const std::string &room_id) noexcept;
+void
+updateOutboundMegolmSession(const std::string &room_id, int message_index);
+
+void
+importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
+mtx::crypto::ExportedSessionKeys
+exportSessionKeys();
+
+//
+// Inbound Megolm Sessions
+//
+void
+saveInboundMegolmSession(const MegolmSessionIndex &index,
+                         mtx::crypto::InboundGroupSessionPtr session);
+OlmInboundGroupSession *
+getInboundMegolmSession(const MegolmSessionIndex &index);
+bool
+inboundMegolmSessionExists(const MegolmSessionIndex &index);
+
+//
+// Olm Sessions
+//
+void
+saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+std::vector<std::string>
+getOlmSessions(const std::string &curve25519);
+std::optional<mtx::crypto::OlmSessionPtr>
+getOlmSession(const std::string &curve25519, const std::string &session_id);
+
+void
+saveOlmAccount(const std::string &pickled);
+std::string
+restoreOlmAccount();
+
+void
+restoreSessions();
 }
diff --git a/src/Cache_p.h b/src/Cache_p.h
new file mode 100644
index 00000000..47dd945a
--- /dev/null
+++ b/src/Cache_p.h
@@ -0,0 +1,494 @@
+/*
+ * nheko Copyright (C) 2019  The nheko authors
+ * 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/>.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <optional>
+
+#include <QDateTime>
+#include <QDir>
+#include <QImage>
+#include <QString>
+
+#include <lmdb++.h>
+#include <nlohmann/json.hpp>
+
+#include <mtx/responses.hpp>
+#include <mtxclient/crypto/client.hpp>
+
+#include "CacheCryptoStructs.h"
+#include "CacheStructs.h"
+#include "Logging.h"
+#include "MatrixClient.h"
+
+int
+numeric_key_comparison(const MDB_val *a, const MDB_val *b);
+
+class Cache : public QObject
+{
+        Q_OBJECT
+
+public:
+        Cache(const QString &userId, QObject *parent = nullptr);
+
+        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 QString userColor(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 removeUserColor(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);
+        static void insertUserColor(const QString &user_id, const QString &color_name);
+
+        static void clearUserColors();
+
+        //! Load saved data for the display names & avatars.
+        void populateMembers();
+        std::vector<std::string> joinedRooms();
+
+        QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
+        std::map<QString, bool> invites();
+
+        //! Calculate & return the name of the room.
+        QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
+        //! Get room join rules
+        mtx::events::state::JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
+        bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
+        //! 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);
+        //! Retrieve the version of the room if any.
+        QString getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
+
+        //! Retrieve member info from a room.
+        std::vector<RoomMember> getMembers(const std::string &room_id,
+                                           std::size_t startIndex = 0,
+                                           std::size_t len        = 30);
+
+        void saveState(const mtx::responses::Sync &res);
+        bool isInitialized() const;
+
+        std::string nextBatchToken() const;
+
+        void deleteData();
+
+        void removeInvite(lmdb::txn &txn, const std::string &room_id);
+        void removeInvite(const std::string &room_id);
+        void removeRoom(lmdb::txn &txn, const std::string &roomid);
+        void removeRoom(const std::string &roomid);
+        void setup();
+
+        bool isFormatValid();
+        void setCurrentFormat();
+
+        std::map<QString, mtx::responses::Timeline> roomMessages();
+
+        QMap<QString, mtx::responses::Notifications> getTimelineMentions();
+
+        //! Retrieve all the user ids from a room.
+        std::vector<std::string> roomMembers(const std::string &room_id);
+
+        //! Check if the given user has power leve greater than than
+        //! lowest power level of the given events.
+        bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
+                                 const std::string &room_id,
+                                 const std::string &user_id);
+
+        //! Retrieves the saved room avatar.
+        QImage getRoomAvatar(const QString &id);
+        QImage getRoomAvatar(const std::string &id);
+
+        //! Adds a user to the read list for the given event.
+        //!
+        //! There should be only one user id present in a receipt list per room.
+        //! The user id should be removed from any other lists.
+        using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
+        void updateReadReceipt(lmdb::txn &txn,
+                               const std::string &room_id,
+                               const Receipts &receipts);
+
+        //! Retrieve all the read receipts for the given event id and room.
+        //!
+        //! Returns a map of user ids and the time of the read receipt in milliseconds.
+        using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
+        UserReceipts readReceipts(const QString &event_id, const QString &room_id);
+
+        //! Filter the events that have at least one read receipt.
+        std::vector<QString> filterReadEvents(const QString &room_id,
+                                              const std::vector<QString> &event_ids,
+                                              const std::string &excluded_user);
+        //! Add event for which we are expecting some read receipts.
+        void addPendingReceipt(const QString &room_id, const QString &event_id);
+        void removePendingReceipt(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const std::string &event_id);
+        void notifyForReadReceipts(const std::string &room_id);
+        std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
+
+        QByteArray image(const QString &url) const;
+        QByteArray image(lmdb::txn &txn, const std::string &url) const;
+        void saveImage(const std::string &url, const std::string &data);
+        void saveImage(const QString &url, const QByteArray &data);
+
+        RoomInfo singleRoomInfo(const std::string &room_id);
+        std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
+        std::vector<std::string> roomsWithTagUpdates(const mtx::responses::Sync &res);
+        std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
+
+        //! Calculates which the read status of a room.
+        //! Whether all the events in the timeline have been read.
+        bool calculateRoomReadStatus(const std::string &room_id);
+        void calculateRoomReadStatus();
+
+        QVector<SearchResult> searchUsers(const std::string &room_id,
+                                          const std::string &query,
+                                          std::uint8_t max_items = 5);
+        std::vector<RoomSearchResult> searchRooms(const std::string &query,
+                                                  std::uint8_t max_items = 5);
+
+        void markSentNotification(const std::string &event_id);
+        //! Removes an event from the sent notifications.
+        void removeReadNotification(const std::string &event_id);
+        //! Check if we have sent a desktop notification for the given event id.
+        bool isNotificationSent(const std::string &event_id);
+
+        //! Add all notifications containing a user mention to the db.
+        void saveTimelineMentions(const mtx::responses::Notifications &res);
+
+        //! Remove old unused data.
+        void deleteOldMessages();
+        void deleteOldData() noexcept;
+        //! Retrieve all saved room ids.
+        std::vector<std::string> getRoomIds(lmdb::txn &txn);
+
+        //! Mark a room that uses e2e encryption.
+        void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
+        bool isRoomEncrypted(const std::string &room_id);
+
+        //! Check if a user is a member of the room.
+        bool isRoomMember(const std::string &user_id, const std::string &room_id);
+
+        //
+        // Outbound Megolm Sessions
+        //
+        void saveOutboundMegolmSession(const std::string &room_id,
+                                       const OutboundGroupSessionData &data,
+                                       mtx::crypto::OutboundGroupSessionPtr session);
+        OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
+        bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
+        void updateOutboundMegolmSession(const std::string &room_id, int message_index);
+
+        void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
+        mtx::crypto::ExportedSessionKeys exportSessionKeys();
+
+        //
+        // Inbound Megolm Sessions
+        //
+        void saveInboundMegolmSession(const MegolmSessionIndex &index,
+                                      mtx::crypto::InboundGroupSessionPtr session);
+        OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
+        bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
+
+        //
+        // Olm Sessions
+        //
+        void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+        std::vector<std::string> getOlmSessions(const std::string &curve25519);
+        std::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
+                                                                const std::string &session_id);
+
+        void saveOlmAccount(const std::string &pickled);
+        std::string restoreOlmAccount();
+
+        void restoreSessions();
+
+signals:
+        void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
+        void roomReadStatus(const std::map<QString, bool> &status);
+
+private:
+        //! Save an invited room.
+        void saveInvite(lmdb::txn &txn,
+                        lmdb::dbi &statesdb,
+                        lmdb::dbi &membersdb,
+                        const mtx::responses::InvitedRoom &room);
+
+        //! Add a notification containing a user mention to the db.
+        void saveTimelineMentions(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const QList<mtx::responses::Notification> &res);
+
+        //! Get timeline items that a user was mentions in for a given room
+        mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn,
+                                                                 const std::string &room_id);
+
+        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);
+
+        DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
+        void saveTimelineMessages(lmdb::txn &txn,
+                                  const std::string &room_id,
+                                  const mtx::responses::Timeline &res);
+
+        mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
+
+        //! 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 (auto e = std::get_if<StateEvent<Member>>(&event); e != nullptr) {
+                        switch (e->content.membership) {
+                        //
+                        // We only keep users with invite or join membership.
+                        //
+                        case Membership::Invite:
+                        case Membership::Join: {
+                                auto display_name = e->content.display_name.empty()
+                                                      ? e->state_key
+                                                      : e->content.display_name;
+
+                                // 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;
+                } else if (std::holds_alternative<StateEvent<Encryption>>(event)) {
+                        setEncryptedRoom(txn, room_id);
+                        return;
+                }
+
+                if (!isStateEvent(event))
+                        return;
+
+                std::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 std::holds_alternative<StateEvent<Aliases>>(e) ||
+                       std::holds_alternative<StateEvent<state::Avatar>>(e) ||
+                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+                       std::holds_alternative<StateEvent<Create>>(e) ||
+                       std::holds_alternative<StateEvent<GuestAccess>>(e) ||
+                       std::holds_alternative<StateEvent<HistoryVisibility>>(e) ||
+                       std::holds_alternative<StateEvent<JoinRules>>(e) ||
+                       std::holds_alternative<StateEvent<Name>>(e) ||
+                       std::holds_alternative<StateEvent<Member>>(e) ||
+                       std::holds_alternative<StateEvent<PowerLevels>>(e) ||
+                       std::holds_alternative<StateEvent<Topic>>(e);
+        }
+
+        template<class T>
+        bool containsStateUpdates(const T &e)
+        {
+                using namespace mtx::events;
+                using namespace mtx::events::state;
+
+                return std::holds_alternative<StateEvent<state::Avatar>>(e) ||
+                       std::holds_alternative<StateEvent<CanonicalAlias>>(e) ||
+                       std::holds_alternative<StateEvent<Name>>(e) ||
+                       std::holds_alternative<StateEvent<Member>>(e) ||
+                       std::holds_alternative<StateEvent<Topic>>(e);
+        }
+
+        bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
+        {
+                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);
+        }
+
+        void saveInvites(lmdb::txn &txn,
+                         const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
+
+        //! 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);
+
+                        // Clean up leftover invites.
+                        removeInvite(txn, room.first);
+                }
+        }
+
+        lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
+        {
+                return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
+        }
+
+        lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                auto db =
+                  lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
+                lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
+
+                return db;
+        }
+
+        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);
+        }
+
+        lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id)
+        {
+                return lmdb::dbi::open(txn, std::string(room_id + "/mentions").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.
+        //!
+        //! Each entry is a map from the session_id to the pickled representation of the session.
+        lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
+        {
+                return lmdb::dbi::open(
+                  txn, std::string("olm_sessions/" + curve25519_key).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);
+
+        lmdb::env env_;
+        lmdb::dbi syncStateDb_;
+        lmdb::dbi roomsDb_;
+        lmdb::dbi invitesDb_;
+        lmdb::dbi mediaDb_;
+        lmdb::dbi readReceiptsDb_;
+        lmdb::dbi notificationsDb_;
+
+        lmdb::dbi devicesDb_;
+        lmdb::dbi deviceKeysDb_;
+
+        lmdb::dbi inboundMegolmSessionDb_;
+        lmdb::dbi outboundMegolmSessionDb_;
+
+        QString localUserId_;
+        QString cacheDirectory_;
+
+        static QHash<QString, QString> DisplayNames;
+        static QHash<QString, QString> AvatarUrls;
+        static QHash<QString, QString> UserColors;
+
+        OlmSessionStorage session_storage;
+};
+
+namespace cache {
+Cache *
+client();
+}
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index c496acab..ad07efdd 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -22,6 +22,7 @@
 
 #include "AvatarProvider.h"
 #include "Cache.h"
+#include "Cache_p.h"
 #include "ChatPage.h"
 #include "Logging.h"
 #include "MainWindow.h"
@@ -319,7 +320,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                   auto bin     = dev->peek(dev->size());
                   auto payload = std::string(bin.data(), bin.size());
                   std::optional<mtx::crypto::EncryptedFile> encryptedFile;
-                  if (cache::client()->isRoomEncrypted(current_room_.toStdString())) {
+                  if (cache::isRoomEncrypted(current_room_.toStdString())) {
                           mtx::crypto::BinaryBuf buf;
                           std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
                           payload                      = mtx::crypto::to_string(buf);
@@ -408,7 +409,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                 this,
                 [](const mtx::responses::Notifications &notif) {
                         try {
-                                cache::client()->saveTimelineMentions(notif);
+                                cache::saveTimelineMentions(notif);
                         } catch (const lmdb::error &e) {
                                 nhlog::db()->error("failed to save mentions: {}", e.what());
                         }
@@ -457,7 +458,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
                 &popups::UserMentions::initializeMentions);
         connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
                 try {
-                        room_list_->cleanupInvites(cache::client()->invites());
+                        room_list_->cleanupInvites(cache::invites());
                 } catch (const lmdb::error &e) {
                         nhlog::db()->error("failed to retrieve invites: {}", e.what());
                 }
@@ -575,7 +576,7 @@ ChatPage::deleteConfigs()
         settings.remove("");
         settings.endGroup();
 
-        cache::client()->deleteData();
+        cache::deleteData();
         http::client()->clear();
 }
 
@@ -610,18 +611,18 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                 connect(
                   cache::client(), &Cache::roomReadStatus, room_list_, &RoomList::updateReadStatus);
 
-                const bool isInitialized = cache::client()->isInitialized();
-                const bool isValid       = cache::client()->isFormatValid();
+                const bool isInitialized = cache::isInitialized();
+                const bool isValid       = cache::isFormatValid();
 
                 if (!isInitialized) {
-                        cache::client()->setCurrentFormat();
+                        cache::setCurrentFormat();
                 } else if (isInitialized && !isValid) {
                         // TODO: Deleting session data but keep using the
                         //	 same device doesn't work.
-                        cache::client()->deleteData();
+                        cache::deleteData();
 
                         cache::init(userid);
-                        cache::client()->setCurrentFormat();
+                        cache::setCurrentFormat();
                 } else if (isInitialized) {
                         loadStateFromCache();
                         return;
@@ -629,7 +630,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
 
         } catch (const lmdb::error &e) {
                 nhlog::db()->critical("failure during boot: {}", e.what());
-                cache::client()->deleteData();
+                cache::deleteData();
                 nhlog::net()->info("falling back to initial sync");
         }
 
@@ -638,7 +639,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
                 // There isn't a saved olm account to restore.
                 nhlog::crypto()->info("creating new olm account");
                 olm::client()->create_new_account();
-                cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
+                cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
         } catch (const lmdb::error &e) {
                 nhlog::crypto()->critical("failed to save olm account {}", e.what());
                 emit dropToLoginPageCb(QString::fromStdString(e.what()));
@@ -671,7 +672,7 @@ ChatPage::changeTopRoomInfo(const QString &room_id)
         }
 
         try {
-                auto room_info = cache::client()->getRoomInfo({room_id.toStdString()});
+                auto room_info = cache::getRoomInfo({room_id.toStdString()});
 
                 if (room_info.find(room_id) == room_info.end())
                         return;
@@ -682,7 +683,7 @@ ChatPage::changeTopRoomInfo(const QString &room_id)
                 top_bar_->updateRoomName(name);
                 top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic));
 
-                auto img = cache::client()->getRoomAvatar(room_id);
+                auto img = cache::getRoomAvatar(room_id);
 
                 if (img.isNull())
                         top_bar_->updateRoomAvatarFromName(name);
@@ -719,18 +720,17 @@ ChatPage::loadStateFromCache()
 
         QtConcurrent::run([this]() {
                 try {
-                        cache::client()->restoreSessions();
-                        olm::client()->load(cache::client()->restoreOlmAccount(),
-                                            STORAGE_SECRET_KEY);
+                        cache::restoreSessions();
+                        olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY);
 
-                        cache::client()->populateMembers();
+                        cache::populateMembers();
 
-                        emit initializeEmptyViews(cache::client()->roomMessages());
-                        emit initializeRoomList(cache::client()->roomInfo());
-                        emit initializeMentions(cache::client()->getTimelineMentions());
-                        emit syncTags(cache::client()->roomInfo().toStdMap());
+                        emit initializeEmptyViews(cache::roomMessages());
+                        emit initializeRoomList(cache::roomInfo());
+                        emit initializeMentions(cache::getTimelineMentions());
+                        emit syncTags(cache::roomInfo().toStdMap());
 
-                        cache::client()->calculateRoomReadStatus();
+                        cache::calculateRoomReadStatus();
 
                 } catch (const mtx::crypto::olm_exception &e) {
                         nhlog::crypto()->critical("failed to restore olm account: {}", e.what());
@@ -773,8 +773,8 @@ void
 ChatPage::removeRoom(const QString &room_id)
 {
         try {
-                cache::client()->removeRoom(room_id);
-                cache::client()->removeInvite(room_id.toStdString());
+                cache::removeRoom(room_id);
+                cache::removeInvite(room_id.toStdString());
         } catch (const lmdb::error &e) {
                 nhlog::db()->critical("failure while removing room: {}", e.what());
                 // TODO: Notify the user.
@@ -807,7 +807,7 @@ ChatPage::generateTypingUsers(const QString &room_id, const std::vector<std::str
                 if (remote_user == local_user)
                         continue;
 
-                users.append(Cache::displayName(room_id, remote_user));
+                users.append(cache::displayName(room_id, remote_user));
         }
 
         users.sort();
@@ -853,16 +853,16 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
 
                 try {
                         if (item.read) {
-                                cache::client()->removeReadNotification(event_id);
+                                cache::removeReadNotification(event_id);
                                 continue;
                         }
 
-                        if (!cache::client()->isNotificationSent(event_id)) {
+                        if (!cache::isNotificationSent(event_id)) {
                                 const auto room_id = QString::fromStdString(item.room_id);
                                 const auto user_id = utils::event_sender(item.event);
 
                                 // We should only sent one notification per event.
-                                cache::client()->markSentNotification(event_id);
+                                cache::markSentNotification(event_id);
 
                                 // Don't send a notification when the current room is opened.
                                 if (isRoomActive(room_id))
@@ -871,11 +871,10 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
                                 notificationsManager.postNotification(
                                   room_id,
                                   QString::fromStdString(event_id),
-                                  QString::fromStdString(
-                                    cache::client()->singleRoomInfo(item.room_id).name),
-                                  Cache::displayName(room_id, user_id),
+                                  QString::fromStdString(cache::singleRoomInfo(item.room_id).name),
+                                  cache::displayName(room_id, user_id),
                                   utils::event_body(item.event),
-                                  cache::client()->getRoomAvatar(room_id));
+                                  cache::getRoomAvatar(room_id));
                         }
                 } catch (const lmdb::error &e) {
                         nhlog::db()->warn("error while sending desktop notification: {}", e.what());
@@ -962,7 +961,7 @@ ChatPage::trySync()
                 connectivityTimer_.start();
 
         try {
-                opts.since = cache::client()->nextBatchToken();
+                opts.since = cache::nextBatchToken();
         } catch (const lmdb::error &e) {
                 nhlog::db()->error("failed to retrieve next batch token: {}", e.what());
                 return;
@@ -1015,22 +1014,22 @@ ChatPage::trySync()
 
                   // TODO: fine grained error handling
                   try {
-                          cache::client()->saveState(res);
+                          cache::saveState(res);
                           olm::handle_to_device_messages(res.to_device);
 
                           emit syncUI(res.rooms);
 
-                          auto updates = cache::client()->roomUpdates(res);
+                          auto updates = cache::roomUpdates(res);
 
                           emit syncTopBar(updates);
                           emit syncRoomlist(updates);
 
-                          emit syncTags(cache::client()->roomTagUpdates(res));
+                          emit syncTags(cache::roomTagUpdates(res));
 
-                          cache::client()->deleteOldData();
+                          cache::deleteOldData();
                   } catch (const lmdb::map_full_error &e) {
                           nhlog::db()->error("lmdb is full: {}", e.what());
-                          cache::client()->deleteOldData();
+                          cache::deleteOldData();
                   } catch (const lmdb::error &e) {
                           nhlog::db()->error("saving sync response: {}", e.what());
                   }
@@ -1058,7 +1057,7 @@ ChatPage::joinRoom(const QString &room)
 
                   // We remove any invites with the same room_id.
                   try {
-                          cache::client()->removeInvite(room_id);
+                          cache::removeInvite(room_id);
                   } catch (const lmdb::error &e) {
                           emit showNotification(
                             QString("Failed to remove invite: %1").arg(e.what()));
@@ -1156,16 +1155,16 @@ ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::Request
         nhlog::net()->info("initial sync completed");
 
         try {
-                cache::client()->saveState(res);
+                cache::saveState(res);
 
                 olm::handle_to_device_messages(res.to_device);
 
                 emit initializeViews(std::move(res.rooms));
-                emit initializeRoomList(cache::client()->roomInfo());
-                emit initializeMentions(cache::client()->getTimelineMentions());
+                emit initializeRoomList(cache::roomInfo());
+                emit initializeMentions(cache::getTimelineMentions());
 
-                cache::client()->calculateRoomReadStatus();
-                emit syncTags(cache::client()->roomInfo().toStdMap());
+                cache::calculateRoomReadStatus();
+                emit syncTags(cache::roomInfo().toStdMap());
         } catch (const lmdb::error &e) {
                 nhlog::db()->error("failed to save state after initial sync: {}", e.what());
                 startInitialSync();
diff --git a/src/CommunitiesList.cpp b/src/CommunitiesList.cpp
index 6e46741b..4ea99408 100644
--- a/src/CommunitiesList.cpp
+++ b/src/CommunitiesList.cpp
@@ -215,7 +215,7 @@ CommunitiesList::highlightSelectedCommunity(const QString &community_id)
 void
 CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUrl)
 {
-        auto savedImgData = cache::client()->image(avatarUrl);
+        auto savedImgData = cache::image(avatarUrl);
         if (!savedImgData.isNull()) {
                 QPixmap pix;
                 pix.loadFromData(savedImgData);
@@ -238,7 +238,7 @@ CommunitiesList::fetchCommunityAvatar(const QString &id, const QString &avatarUr
                           return;
                   }
 
-                  cache::client()->saveImage(opts.mxc_url, res);
+                  cache::saveImage(opts.mxc_url, res);
 
                   auto data = QByteArray(res.data(), res.size());
 
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 7d9a8902..b13f1b80 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -114,7 +114,7 @@ MainWindow::MainWindow(QWidget *parent)
         connect(
           userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
         connect(userSettingsPage_, &UserSettingsPage::themeChanged, this, []() {
-                Cache::clearUserColors();
+                cache::clearUserColors();
         });
         connect(
           userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
@@ -444,7 +444,7 @@ MainWindow::openReadReceiptsDialog(const QString &event_id)
         const auto room_id = chat_page_->currentRoom();
 
         try {
-                dialog->addUsers(cache::client()->readReceipts(event_id, room_id));
+                dialog->addUsers(cache::readReceipts(event_id, room_id));
         } catch (const lmdb::error &e) {
                 nhlog::db()->warn("failed to retrieve read receipts for {} {}",
                                   event_id.toStdString(),
@@ -507,4 +507,4 @@ MainWindow::loadJdenticonPlugin()
 
         nhlog::ui()->info("jdenticon plugin not found.");
         return false;
-}
\ No newline at end of file
+}
diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp
index edf6ceb5..02ca2806 100644
--- a/src/MxcImageProvider.cpp
+++ b/src/MxcImageProvider.cpp
@@ -1,6 +1,8 @@
 #include "MxcImageProvider.h"
 
 #include "Cache.h"
+#include "MatrixClient.h"
+#include "Logging.h"
 
 void
 MxcImageResponse::run()
@@ -11,7 +13,7 @@ MxcImageResponse::run()
                                      .arg(m_requestedSize.width())
                                      .arg(m_requestedSize.height());
 
-                auto data = cache::client()->image(fileName);
+                auto data = cache::image(fileName);
                 if (!data.isNull() && m_image.loadFromData(data)) {
                         m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio);
                         m_image.setText("mxc url", "mxc://" + m_id);
@@ -36,14 +38,14 @@ MxcImageResponse::run()
                           }
 
                           auto data = QByteArray(res.data(), res.size());
-                          cache::client()->saveImage(fileName, data);
+                          cache::saveImage(fileName, data);
                           m_image.loadFromData(data);
                           m_image.setText("mxc url", "mxc://" + m_id);
 
                           emit finished();
                   });
         } else {
-                auto data = cache::client()->image(m_id);
+                auto data = cache::image(m_id);
                 if (!data.isNull() && m_image.loadFromData(data)) {
                         m_image.setText("mxc url", "mxc://" + m_id);
                         emit finished();
@@ -75,7 +77,7 @@ MxcImageResponse::run()
                           m_image.setText("original filename",
                                           QString::fromStdString(originalFilename));
                           m_image.setText("mxc url", "mxc://" + m_id);
-                          cache::client()->saveImage(m_id, data);
+                          cache::saveImage(m_id, data);
 
                           emit finished();
                   });
diff --git a/src/Olm.cpp b/src/Olm.cpp
index 9c1a25df..5859fe8e 100644
--- a/src/Olm.cpp
+++ b/src/Olm.cpp
@@ -121,7 +121,7 @@ handle_pre_key_olm_message(const std::string &sender,
 
                 // We also remove the one time key used to establish that
                 // session so we'll have to update our copy of the account object.
-                cache::client()->saveOlmAccount(olm::client()->save("secret"));
+                cache::saveOlmAccount(olm::client()->save("secret"));
         } catch (const mtx::crypto::olm_exception &e) {
                 nhlog::crypto()->critical(
                   "failed to create inbound session with {}: {}", sender, e.what());
@@ -149,7 +149,7 @@ handle_pre_key_olm_message(const std::string &sender,
         nhlog::crypto()->debug("decrypted message: \n {}", plaintext.dump(2));
 
         try {
-                cache::client()->saveOlmSession(sender_key, std::move(inbound_session));
+                cache::saveOlmSession(sender_key, std::move(inbound_session));
         } catch (const lmdb::error &e) {
                 nhlog::db()->warn(
                   "failed to save inbound olm session from {}: {}", sender, e.what());
@@ -166,7 +166,7 @@ encrypt_group_message(const std::string &room_id,
         using namespace mtx::events;
 
         // Always chech before for existence.
-        auto res     = cache::client()->getOutboundMegolmSession(room_id);
+        auto res     = cache::getOutboundMegolmSession(room_id);
         auto payload = olm::client()->encrypt_group_message(res.session, body);
 
         // Prepare the m.room.encrypted event.
@@ -181,7 +181,7 @@ encrypt_group_message(const std::string &room_id,
         nhlog::crypto()->info("next message_index {}", message_index);
 
         // We need to re-pickle the session after we send a message to save the new message_index.
-        cache::client()->updateOutboundMegolmSession(room_id, message_index);
+        cache::updateOutboundMegolmSession(room_id, message_index);
 
         return data;
 }
@@ -189,13 +189,13 @@ encrypt_group_message(const std::string &room_id,
 nlohmann::json
 try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCipherContent &msg)
 {
-        auto session_ids = cache::client()->getOlmSessions(sender_key);
+        auto session_ids = cache::getOlmSessions(sender_key);
 
         nhlog::crypto()->info("attempt to decrypt message with {} known session_ids",
                               session_ids.size());
 
         for (const auto &id : session_ids) {
-                auto session = cache::client()->getOlmSession(sender_key, id);
+                auto session = cache::getOlmSession(sender_key, id);
 
                 if (!session)
                         continue;
@@ -204,7 +204,7 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
 
                 try {
                         text = olm::client()->decrypt_message(session->get(), msg.type, msg.body);
-                        cache::client()->saveOlmSession(id, std::move(session.value()));
+                        cache::saveOlmSession(id, std::move(session.value()));
                 } catch (const mtx::crypto::olm_exception &e) {
                         nhlog::crypto()->debug("failed to decrypt olm message ({}, {}) with {}: {}",
                                                msg.type,
@@ -252,7 +252,7 @@ create_inbound_megolm_session(const std::string &sender,
 
         try {
                 auto megolm_session = olm::client()->init_inbound_group_session(session_key);
-                cache::client()->saveInboundMegolmSession(index, std::move(megolm_session));
+                cache::saveInboundMegolmSession(index, std::move(megolm_session));
         } catch (const lmdb::error &e) {
                 nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
                 return;
@@ -268,7 +268,7 @@ void
 mark_keys_as_published()
 {
         olm::client()->mark_keys_as_published();
-        cache::client()->saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
+        cache::saveOlmAccount(olm::client()->save(STORAGE_SECRET_KEY));
 }
 
 void
@@ -355,13 +355,13 @@ handle_key_request_message(const mtx::events::msg::KeyRequest &req)
         }
 
         // Check if we have the keys for the requested session.
-        if (!cache::client()->outboundMegolmSessionExists(req.room_id)) {
+        if (!cache::outboundMegolmSessionExists(req.room_id)) {
                 nhlog::crypto()->warn("requested session not found in room: {}", req.room_id);
                 return;
         }
 
         // Check that the requested session_id and the one we have saved match.
-        const auto session = cache::client()->getOutboundMegolmSession(req.room_id);
+        const auto session = cache::getOutboundMegolmSession(req.room_id);
         if (req.session_id != session.data.session_id) {
                 nhlog::crypto()->warn("session id of retrieved session doesn't match the request: "
                                       "requested({}), ours({})",
@@ -370,7 +370,7 @@ handle_key_request_message(const mtx::events::msg::KeyRequest &req)
                 return;
         }
 
-        if (!cache::client()->isRoomMember(req.sender, req.room_id)) {
+        if (!cache::isRoomMember(req.sender, req.room_id)) {
                 nhlog::crypto()->warn(
                   "user {} that requested the session key is not member of the room {}",
                   req.sender,
@@ -509,8 +509,7 @@ send_megolm_key_to_device(const std::string &user_id,
                                     device_msg = olm::client()->create_olm_encrypted_content(
                                       olm_session.get(), room_key, pks.curve25519);
 
-                                    cache::client()->saveOlmSession(pks.curve25519,
-                                                                    std::move(olm_session));
+                                    cache::saveOlmSession(pks.curve25519, std::move(olm_session));
                             } catch (const json::exception &e) {
                                     nhlog::crypto()->warn("creating outbound session: {}",
                                                           e.what());
diff --git a/src/QuickSwitcher.cpp b/src/QuickSwitcher.cpp
index f8f6c001..29683bb3 100644
--- a/src/QuickSwitcher.cpp
+++ b/src/QuickSwitcher.cpp
@@ -93,8 +93,7 @@ QuickSwitcher::QuickSwitcher(QWidget *parent)
 
                 QtConcurrent::run([this, query = query.toLower()]() {
                         try {
-                                emit queryResults(
-                                  cache::client()->searchRooms(query.toStdString()));
+                                emit queryResults(cache::searchRooms(query.toStdString()));
                         } catch (const lmdb::error &e) {
                                 qWarning() << "room search failed:" << e.what();
                         }
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 66700dbc..5e63eca5 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -529,12 +529,12 @@ TextInputWidget::TextInputWidget(QWidget *parent)
                         emit heightChanged(widgetHeight);
                 });
         connect(input_, &FilteredTextEdit::showSuggestions, this, [this](const QString &q) {
-                if (q.isEmpty() || !cache::client())
+                if (q.isEmpty())
                         return;
 
                 QtConcurrent::run([this, q = q.toLower().toStdString()]() {
                         try {
-                                emit input_->resultsRetrieved(cache::client()->searchUsers(
+                                emit input_->resultsRetrieved(cache::searchUsers(
                                   ChatPage::instance()->currentRoom().toStdString(), q));
                         } catch (const lmdb::error &e) {
                                 std::cout << e.what() << '\n';
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 1caea449..772a8d13 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -555,7 +555,7 @@ UserSettingsPage::importSessionKeys()
         try {
                 auto sessions =
                   mtx::crypto::decrypt_exported_sessions(payload, password.toStdString());
-                cache::client()->importSessionKeys(std::move(sessions));
+                cache::importSessionKeys(std::move(sessions));
         } catch (const mtx::crypto::sodium_exception &e) {
                 QMessageBox::warning(this, tr("Error"), e.what());
         } catch (const lmdb::error &e) {
@@ -597,7 +597,7 @@ UserSettingsPage::exportSessionKeys()
         // Export sessions & save to file.
         try {
                 auto encrypted_blob = mtx::crypto::encrypt_exported_sessions(
-                  cache::client()->exportSessionKeys(), password.toStdString());
+                  cache::exportSessionKeys(), password.toStdString());
 
                 QString b64 = QString::fromStdString(mtx::crypto::bin2base64(encrypted_blob));
 
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 3d69162f..918e1996 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -142,7 +142,7 @@ utils::getMessageDescription(const TimelineEvent &event,
         } else if (auto msg = std::get_if<Encrypted>(&event); msg != nullptr) {
                 const auto sender = QString::fromStdString(msg->sender);
 
-                const auto username = Cache::displayName(room_id, sender);
+                const auto username = cache::displayName(room_id, sender);
                 const auto ts       = QDateTime::fromMSecsSinceEpoch(msg->origin_server_ts);
 
                 DescInfo info;
diff --git a/src/Utils.h b/src/Utils.h
index 6c0cf0dd..aa62b8e7 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -168,7 +168,7 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin
         const auto msg    = std::get<T>(event);
         const auto sender = QString::fromStdString(msg.sender);
 
-        const auto username = Cache::displayName(room_id, sender);
+        const auto username = cache::displayName(room_id, sender);
         const auto ts       = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
 
         return DescInfo{QString::fromStdString(msg.event_id),
diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp
index f62cf9fe..dfb3d984 100644
--- a/src/dialogs/MemberList.cpp
+++ b/src/dialogs/MemberList.cpp
@@ -110,11 +110,11 @@ MemberList::MemberList(const QString &room_id, QWidget *parent)
                 const size_t numMembers = list_->count() - 1;
 
                 if (numMembers > 0)
-                        addUsers(cache::client()->getMembers(room_id_.toStdString(), numMembers));
+                        addUsers(cache::getMembers(room_id_.toStdString(), numMembers));
         });
 
         try {
-                addUsers(cache::client()->getMembers(room_id_.toStdString()));
+                addUsers(cache::getMembers(room_id_.toStdString()));
         } catch (const lmdb::error &e) {
                 qCritical() << e.what();
         }
diff --git a/src/dialogs/ReadReceipts.cpp b/src/dialogs/ReadReceipts.cpp
index 58ad59c3..0edd1ebf 100644
--- a/src/dialogs/ReadReceipts.cpp
+++ b/src/dialogs/ReadReceipts.cpp
@@ -35,7 +35,7 @@ ReceiptItem::ReceiptItem(QWidget *parent,
         QFont nameFont;
         nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1);
 
-        auto displayName = Cache::displayName(room_id, user_id);
+        auto displayName = cache::displayName(room_id, user_id);
 
         avatar_ = new Avatar(this, 44);
         avatar_->setLetter(utils::firstChar(displayName));
diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp
index 1be33d33..fcaa4fdc 100644
--- a/src/dialogs/RoomSettings.cpp
+++ b/src/dialogs/RoomSettings.cpp
@@ -485,8 +485,8 @@ void
 RoomSettings::retrieveRoomInfo()
 {
         try {
-                usesEncryption_ = cache::client()->isRoomEncrypted(room_id_.toStdString());
-                info_           = cache::client()->singleRoomInfo(room_id_.toStdString());
+                usesEncryption_ = cache::isRoomEncrypted(room_id_.toStdString());
+                info_           = cache::singleRoomInfo(room_id_.toStdString());
                 setAvatar();
         } catch (const lmdb::error &) {
                 nhlog::db()->warn("failed to retrieve room info from cache: {}",
@@ -529,8 +529,7 @@ bool
 RoomSettings::canChangeJoinRules(const std::string &room_id, const std::string &user_id) const
 {
         try {
-                return cache::client()->hasEnoughPowerLevel(
-                  {EventType::RoomJoinRules}, room_id, user_id);
+                return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, room_id, user_id);
         } catch (const lmdb::error &e) {
                 nhlog::db()->warn("lmdb error: {}", e.what());
         }
@@ -542,7 +541,7 @@ bool
 RoomSettings::canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const
 {
         try {
-                return cache::client()->hasEnoughPowerLevel(
+                return cache::hasEnoughPowerLevel(
                   {EventType::RoomName, EventType::RoomTopic}, room_id, user_id);
         } catch (const lmdb::error &e) {
                 nhlog::db()->warn("lmdb error: {}", e.what());
@@ -555,8 +554,7 @@ bool
 RoomSettings::canChangeAvatar(const std::string &room_id, const std::string &user_id) const
 {
         try {
-                return cache::client()->hasEnoughPowerLevel(
-                  {EventType::RoomAvatar}, room_id, user_id);
+                return cache::hasEnoughPowerLevel({EventType::RoomAvatar}, room_id, user_id);
         } catch (const lmdb::error &e) {
                 nhlog::db()->warn("lmdb error: {}", e.what());
         }
diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h
index e1807ba1..d71b70db 100644
--- a/src/dialogs/RoomSettings.h
+++ b/src/dialogs/RoomSettings.h
@@ -5,7 +5,9 @@
 #include <QImage>
 #include <QLabel>
 
-#include "Cache.h"
+#include <mtx/events/guest_access.hpp>
+
+#include "CacheStructs.h"
 
 class Avatar;
 class FlatButton;
diff --git a/src/dialogs/UserProfile.cpp b/src/dialogs/UserProfile.cpp
index 5ad3afa2..50c1c990 100644
--- a/src/dialogs/UserProfile.cpp
+++ b/src/dialogs/UserProfile.cpp
@@ -203,7 +203,7 @@ UserProfile::init(const QString &userId, const QString &roomId)
 {
         resetToDefaults();
 
-        auto displayName = Cache::displayName(roomId, userId);
+        auto displayName = cache::displayName(roomId, userId);
 
         userIdLabel_->setText(userId);
         displayNameLabel_->setText(displayName);
@@ -215,9 +215,9 @@ UserProfile::init(const QString &userId, const QString &roomId)
 
         try {
                 bool hasMemberRights =
-                  cache::client()->hasEnoughPowerLevel({mtx::events::EventType::RoomMember},
-                                                       roomId.toStdString(),
-                                                       localUser.toStdString());
+                  cache::hasEnoughPowerLevel({mtx::events::EventType::RoomMember},
+                                             roomId.toStdString(),
+                                             localUser.toStdString());
                 if (!hasMemberRights) {
                         kickBtn_->hide();
                         banBtn_->hide();
diff --git a/src/popups/PopupItem.cpp b/src/popups/PopupItem.cpp
index c4d4327f..db97e4a3 100644
--- a/src/popups/PopupItem.cpp
+++ b/src/popups/PopupItem.cpp
@@ -49,7 +49,7 @@ UserItem::UserItem(QWidget *parent, const QString &user_id)
   : PopupItem(parent)
   , userId_{user_id}
 {
-        auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_);
+        auto displayName = cache::displayName(ChatPage::instance()->currentRoom(), userId_);
 
         avatar_->setLetter(utils::firstChar(displayName));
 
@@ -70,7 +70,7 @@ UserItem::updateItem(const QString &user_id)
 {
         userId_ = user_id;
 
-        auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_);
+        auto displayName = cache::displayName(ChatPage::instance()->currentRoom(), userId_);
 
         // If it's a matrix id we use the second letter.
         if (displayName.size() > 1 && displayName.at(0) == '@')
@@ -93,7 +93,7 @@ UserItem::mousePressEvent(QMouseEvent *event)
 {
         if (event->buttons() != Qt::RightButton)
                 emit clicked(
-                  Cache::displayName(ChatPage::instance()->currentRoom(), selectedText()));
+                  cache::displayName(ChatPage::instance()->currentRoom(), selectedText()));
 
         QWidget::mousePressEvent(event);
 }
diff --git a/src/popups/PopupItem.h b/src/popups/PopupItem.h
index cab73a9d..7a710fdb 100644
--- a/src/popups/PopupItem.h
+++ b/src/popups/PopupItem.h
@@ -6,7 +6,6 @@
 #include <QWidget>
 
 #include "../AvatarProvider.h"
-#include "../Cache.h"
 #include "../ChatPage.h"
 
 class Avatar;
@@ -81,4 +80,4 @@ private:
         QLabel *roomName_;
         QString roomId_;
         RoomSearchResult info_;
-};
\ No newline at end of file
+};
diff --git a/src/popups/ReplyPopup.cpp b/src/popups/ReplyPopup.cpp
index 0ebf7c88..42a5a6d3 100644
--- a/src/popups/ReplyPopup.cpp
+++ b/src/popups/ReplyPopup.cpp
@@ -8,6 +8,7 @@
 #include "../ui/Avatar.h"
 #include "../ui/DropShadow.h"
 #include "../ui/TextLabel.h"
+#include "PopupItem.h"
 #include "ReplyPopup.h"
 
 ReplyPopup::ReplyPopup(QWidget *parent)
diff --git a/src/popups/ReplyPopup.h b/src/popups/ReplyPopup.h
index b28cd0cf..1fa3bb83 100644
--- a/src/popups/ReplyPopup.h
+++ b/src/popups/ReplyPopup.h
@@ -2,17 +2,14 @@
 
 #include <QHBoxLayout>
 #include <QLabel>
-#include <QPoint>
 #include <QVBoxLayout>
 #include <QWidget>
 
-#include "../AvatarProvider.h"
-#include "../Cache.h"
-#include "../ChatPage.h"
-#include "../Utils.h"
 #include "../ui/FlatButton.h"
 #include "../ui/TextLabel.h"
-#include "PopupItem.h"
+
+struct RelatedInfo;
+class UserItem;
 
 class ReplyPopup : public QWidget
 {
diff --git a/src/popups/SuggestionsPopup.h b/src/popups/SuggestionsPopup.h
index 536c82fb..de52760a 100644
--- a/src/popups/SuggestionsPopup.h
+++ b/src/popups/SuggestionsPopup.h
@@ -5,9 +5,8 @@
 #include <QPoint>
 #include <QWidget>
 
-#include "../AvatarProvider.h"
-#include "../Cache.h"
-#include "../ChatPage.h"
+#include "CacheStructs.h"
+#include "ChatPage.h"
 #include "PopupItem.h"
 
 Q_DECLARE_METATYPE(QVector<SearchResult>)
@@ -28,7 +27,7 @@ public:
 
                 const auto &widget = qobject_cast<Item *>(item->widget());
                 emit itemSelected(
-                  Cache::displayName(ChatPage::instance()->currentRoom(), widget->selectedText()));
+                  cache::displayName(ChatPage::instance()->currentRoom(), widget->selectedText()));
 
                 resetSelection();
         }
diff --git a/src/popups/UserMentions.cpp b/src/popups/UserMentions.cpp
index 3be5c462..763eeffc 100644
--- a/src/popups/UserMentions.cpp
+++ b/src/popups/UserMentions.cpp
@@ -103,7 +103,7 @@ UserMentions::showPopup()
                 delete widget;
         }
 
-        auto notifs = cache::client()->getTimelineMentions();
+        auto notifs = cache::getTimelineMentions();
 
         initializeMentions(notifs);
         show();
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 1e069d50..d3d1ad34 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -422,7 +422,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
                 readEvent(event_id.toStdString());
 
                 // ask to be notified for read receipts
-                cache::client()->addPendingReceipt(room_id_, event_id);
+                cache::addPendingReceipt(room_id_, event_id);
 
                 isProcessingPending = false;
                 emit dataChanged(index(idx, 0), index(idx, 0));
@@ -575,8 +575,7 @@ TimelineModel::data(const QModelIndex &index, int role) const
                         return qml_mtx_events::Failed;
                 else if (pending.contains(id))
                         return qml_mtx_events::Sent;
-                else if (read.contains(id) ||
-                         cache::client()->readReceipts(id, room_id_).size() > 1)
+                else if (read.contains(id) || cache::readReceipts(id, room_id_).size() > 1)
                         return qml_mtx_events::Read;
                 else
                         return qml_mtx_events::Received;
@@ -805,13 +804,13 @@ TimelineModel::userColor(QString id, QColor background)
 QString
 TimelineModel::displayName(QString id) const
 {
-        return Cache::displayName(room_id_, id);
+        return cache::displayName(room_id_, id);
 }
 
 QString
 TimelineModel::avatarUrl(QString id) const
 {
-        return Cache::avatarUrl(room_id_, id);
+        return cache::avatarUrl(room_id_, id);
 }
 
 QString
@@ -868,7 +867,7 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
             .toStdString();
 
         try {
-                if (!cache::client()->inboundMegolmSessionExists(index)) {
+                if (!cache::inboundMegolmSessionExists(index)) {
                         nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
                                               index.room_id,
                                               index.session_id,
@@ -887,7 +886,7 @@ TimelineModel::decryptEvent(const mtx::events::EncryptedEvent<mtx::events::msg::
 
         std::string msg_str;
         try {
-                auto session = cache::client()->getInboundMegolmSession(index);
+                auto session = cache::getInboundMegolmSession(index);
                 auto res     = olm::client()->decrypt_group_message(session, e.content.ciphertext);
                 msg_str      = std::string((char *)res.data.data(), res.data.size());
         } catch (const lmdb::error &e) {
@@ -1044,7 +1043,7 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
 
         try {
                 // Check if we have already an outbound megolm session then we can use.
-                if (cache::client()->outboundMegolmSessionExists(room_id)) {
+                if (cache::outboundMegolmSessionExists(room_id)) {
                         auto data = olm::encrypt_group_message(
                           room_id, http::client()->device_id(), doc.dump());
 
@@ -1089,10 +1088,10 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
                 session_data.session_id    = session_id;
                 session_data.session_key   = session_key;
                 session_data.message_index = 0; // TODO Update me
-                cache::client()->saveOutboundMegolmSession(
+                cache::saveOutboundMegolmSession(
                   room_id, session_data, std::move(outbound_session));
 
-                const auto members = cache::client()->roomMembers(room_id);
+                const auto members = cache::roomMembers(room_id);
                 nhlog::ui()->info("retrieved {} members for {}", members.size(), room_id);
 
                 auto keeper =
@@ -1311,7 +1310,7 @@ TimelineModel::handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
                   s.get(), room_keys.at(device_id), pks.at(device_id).curve25519);
 
                 try {
-                        cache::client()->saveOlmSession(id_key, std::move(s));
+                        cache::saveOlmSession(id_key, std::move(s));
                 } catch (const lmdb::error &e) {
                         nhlog::db()->critical("failed to save outbound olm session: {}", e.what());
                 } catch (const mtx::crypto::olm_exception &e) {
@@ -1353,7 +1352,7 @@ struct SendMessageVisitor
         void operator()(const mtx::events::RoomEvent<T> &msg)
 
         {
-                if (cache::client()->isRoomEncrypted(model_->room_id_.toStdString())) {
+                if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
                         model_->sendEncryptedMessage(txn_id_qstr_.toStdString(),
                                                      nlohmann::json(msg.content));
                 } else {