diff --git a/include/Cache.h b/include/Cache.h index c141a42..1f6c59f 100644 --- a/include/Cache.h +++ b/include/Cache.h @@ -48,6 +48,9 @@ public: bool isFormatValid(); void setCurrentFormat(); + QByteArray image(const QString &url) const; + void saveImage(const QString &url, const QByteArray &data); + private: void setNextBatchToken(lmdb::txn &txn, const QString &token); void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state); @@ -56,6 +59,7 @@ private: lmdb::dbi stateDb_; lmdb::dbi roomDb_; lmdb::dbi invitesDb_; + lmdb::dbi imagesDb_; bool isMounted_; diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 2e76061..2627f57 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -98,7 +98,10 @@ signals: void fileUploaded(const QString &roomid, const QString &filename, const QString &url); void audioUploaded(const QString &roomid, const QString &filename, const QString &url); - void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); + void roomAvatarRetrieved(const QString &roomid, + const QPixmap &img, + const QString &url, + const QByteArray &data); void userAvatarRetrieved(const QString &userId, const QImage &img); void ownAvatarRetrieved(const QPixmap &img); void imageDownloaded(const QString &event_id, const QPixmap &img); diff --git a/include/RoomList.h b/include/RoomList.h index 8487df1..ed05e0b 100644 --- a/include/RoomList.h +++ b/include/RoomList.h @@ -30,6 +30,7 @@ class LeaveRoomDialog; class MatrixClient; +class Cache; class OverlayModal; class RoomInfoListItem; class RoomSettings; @@ -45,6 +46,7 @@ public: RoomList(QSharedPointer client, QWidget *parent = 0); ~RoomList(); + void setCache(QSharedPointer cache) { cache_ = cache; } void setInitialRooms(const QMap> &settings, const QMap &states); void sync(const QMap &states, @@ -52,6 +54,7 @@ public: void syncInvites(const std::map &rooms); void clear(); + void updateAvatar(const QString &room_id, const QString &url); void addRoom(const QMap> &settings, const RoomState &state, @@ -64,6 +67,7 @@ signals: void totalUnreadMessageCountUpdated(int count); void acceptInvite(const QString &room_id); void declineInvite(const QString &room_id); + void roomAvatarChanged(const QString &room_id, const QPixmap &img); public slots: void updateRoomAvatar(const QString &roomid, const QPixmap &img); @@ -96,4 +100,5 @@ private: QMap> rooms_; QSharedPointer client_; + QSharedPointer cache_; }; diff --git a/src/Cache.cc b/src/Cache.cc index 4e2f32a..06e45f1 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -34,6 +35,7 @@ Cache::Cache(const QString &userId) , stateDb_{0} , roomDb_{0} , invitesDb_{0} + , imagesDb_{0} , isMounted_{false} , userId_{userId} {} @@ -54,7 +56,7 @@ Cache::setup() bool isInitial = !QFile::exists(statePath); env_ = lmdb::env::create(); - env_.set_mapsize(128UL * 1024UL * 1024UL); /* 128 MB */ + env_.set_mapsize(256UL * 1024UL * 1024UL); /* 256 MB */ env_.set_max_dbs(1024UL); if (isInitial) { @@ -91,12 +93,60 @@ Cache::setup() stateDb_ = lmdb::dbi::open(txn, "state", MDB_CREATE); roomDb_ = lmdb::dbi::open(txn, "rooms", MDB_CREATE); invitesDb_ = lmdb::dbi::open(txn, "invites", MDB_CREATE); + imagesDb_ = lmdb::dbi::open(txn, "images", MDB_CREATE); txn.commit(); isMounted_ = true; } +void +Cache::saveImage(const QString &url, const QByteArray &image) +{ + if (!isMounted_) + return; + + auto key = url.toUtf8(); + + try { + auto txn = lmdb::txn::begin(env_); + + lmdb::dbi_put(txn, + imagesDb_, + lmdb::val(key.data(), key.size()), + lmdb::val(image.data(), image.size())); + + txn.commit(); + } catch (const lmdb::error &e) { + qCritical() << "saveImage:" << e.what(); + } +} + +QByteArray +Cache::image(const QString &url) const +{ + auto key = url.toUtf8(); + + try { + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + + lmdb::val image; + + bool res = lmdb::dbi_get(txn, imagesDb_, lmdb::val(key.data(), key.size()), image); + + txn.commit(); + + if (!res) + return QByteArray(); + + return QByteArray(image.data(), image.size()); + } catch (const lmdb::error &e) { + qCritical() << "image:" << e.what(); + } + + return QByteArray(); +} + void Cache::setState(const QString &nextBatchToken, const QMap &states) { diff --git a/src/ChatPage.cc b/src/ChatPage.cc index a5b36d8..8161d62 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -232,8 +232,7 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) view_manager_->queueAudioMessage(roomid, filename, url); }); - connect( - client_.data(), &MatrixClient::roomAvatarRetrieved, this, &ChatPage::updateTopBarAvatar); + connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); connect(client_.data(), &MatrixClient::initialSyncCompleted, @@ -353,6 +352,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) client_->getOwnProfile(); cache_ = QSharedPointer(new Cache(userid)); + room_list_->setCache(cache_); try { cache_->setup(); diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index b5dfe51..1b2e020 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -468,7 +468,7 @@ MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url) QNetworkRequest avatar_request(endpoint); QNetworkReply *reply = get(avatar_request); - connect(reply, &QNetworkReply::finished, this, [this, reply, roomid]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, avatar_url]() { reply->deleteLater(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -486,7 +486,7 @@ MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url) QPixmap pixmap; pixmap.loadFromData(img); - emit roomAvatarRetrieved(roomid, pixmap); + emit roomAvatarRetrieved(roomid, pixmap, avatar_url.toString(), img); }); } diff --git a/src/RoomList.cc b/src/RoomList.cc index 1e63983..ea13d70 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc @@ -15,9 +15,11 @@ * along with this program. If not, see . */ +#include #include #include +#include "Cache.h" #include "MainWindow.h" #include "MatrixClient.h" #include "OverlayModal.h" @@ -53,9 +55,17 @@ RoomList::RoomList(QSharedPointer client, QWidget *parent) topLayout_->addWidget(scrollArea_); connect(client_.data(), - SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), + &MatrixClient::roomAvatarRetrieved, this, - SLOT(updateRoomAvatar(const QString &, const QPixmap &))); + [=](const QString &room_id, + const QPixmap &img, + const QString &url, + const QByteArray &data) { + if (!cache_.isNull()) + cache_->saveImage(url, data); + + updateRoomAvatar(room_id, img); + }); } RoomList::~RoomList() {} @@ -79,12 +89,33 @@ RoomList::addRoom(const QMap> &settings, rooms_.insert(room_id, QSharedPointer(room_item)); if (!state.getAvatar().toString().isEmpty()) - client_->fetchRoomAvatar(room_id, state.getAvatar()); + updateAvatar(room_id, state.getAvatar().toString()); int pos = contentsLayout_->count() - 1; contentsLayout_->insertWidget(pos, room_item); } +void +RoomList::updateAvatar(const QString &room_id, const QString &url) +{ + if (url.isEmpty()) + return; + + QByteArray savedImgData; + + if (!cache_.isNull()) + savedImgData = cache_->image(url); + + if (savedImgData.isEmpty()) { + client_->fetchRoomAvatar(room_id, url); + } else { + QPixmap img; + img.loadFromData(savedImgData); + + updateRoomAvatar(room_id, img); + } +} + void RoomList::removeRoom(const QString &room_id, bool reset) { @@ -194,7 +225,7 @@ RoomList::sync(const QMap &states, auto new_avatar = state.getAvatar(); if (current_avatar != new_avatar && !new_avatar.toString().isEmpty()) - client_->fetchRoomAvatar(room_id, new_avatar); + updateAvatar(room_id, new_avatar.toString()); room->setState(state); } @@ -246,6 +277,9 @@ RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) } rooms_.value(roomid)->setAvatar(img.toImage()); + + // Used to inform other widgets for the new image data. + emit roomAvatarChanged(roomid, img); } void @@ -308,10 +342,7 @@ RoomList::addInvitedRoom(const QString &room_id, const mtx::responses::InvitedRo rooms_.insert(room_id, QSharedPointer(room_item)); - auto avatarUrl = QString::fromStdString(room.avatar()); - - if (!avatarUrl.isEmpty()) - client_->fetchRoomAvatar(room_id, avatarUrl); + updateAvatar(room_id, QString::fromStdString(room.avatar())); int pos = contentsLayout_->count() - 1; contentsLayout_->insertWidget(pos, room_item);