Cache room avatars (#139)

fixes #107
This commit is contained in:
Konstantinos Sideris 2017-12-22 00:00:48 +02:00
parent ece20dd917
commit 33f534c6f8
7 changed files with 107 additions and 14 deletions

View file

@ -48,6 +48,9 @@ public:
bool isFormatValid(); bool isFormatValid();
void setCurrentFormat(); void setCurrentFormat();
QByteArray image(const QString &url) const;
void saveImage(const QString &url, const QByteArray &data);
private: private:
void setNextBatchToken(lmdb::txn &txn, const QString &token); void setNextBatchToken(lmdb::txn &txn, const QString &token);
void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state); void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state);
@ -56,6 +59,7 @@ private:
lmdb::dbi stateDb_; lmdb::dbi stateDb_;
lmdb::dbi roomDb_; lmdb::dbi roomDb_;
lmdb::dbi invitesDb_; lmdb::dbi invitesDb_;
lmdb::dbi imagesDb_;
bool isMounted_; bool isMounted_;

View file

@ -98,7 +98,10 @@ signals:
void fileUploaded(const QString &roomid, const QString &filename, const QString &url); void fileUploaded(const QString &roomid, const QString &filename, const QString &url);
void audioUploaded(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 userAvatarRetrieved(const QString &userId, const QImage &img);
void ownAvatarRetrieved(const QPixmap &img); void ownAvatarRetrieved(const QPixmap &img);
void imageDownloaded(const QString &event_id, const QPixmap &img); void imageDownloaded(const QString &event_id, const QPixmap &img);

View file

@ -30,6 +30,7 @@
class LeaveRoomDialog; class LeaveRoomDialog;
class MatrixClient; class MatrixClient;
class Cache;
class OverlayModal; class OverlayModal;
class RoomInfoListItem; class RoomInfoListItem;
class RoomSettings; class RoomSettings;
@ -45,6 +46,7 @@ public:
RoomList(QSharedPointer<MatrixClient> client, QWidget *parent = 0); RoomList(QSharedPointer<MatrixClient> client, QWidget *parent = 0);
~RoomList(); ~RoomList();
void setCache(QSharedPointer<Cache> cache) { cache_ = cache; }
void setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &settings, void setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &settings,
const QMap<QString, RoomState> &states); const QMap<QString, RoomState> &states);
void sync(const QMap<QString, RoomState> &states, void sync(const QMap<QString, RoomState> &states,
@ -52,6 +54,7 @@ public:
void syncInvites(const std::map<std::string, mtx::responses::InvitedRoom> &rooms); void syncInvites(const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
void clear(); void clear();
void updateAvatar(const QString &room_id, const QString &url);
void addRoom(const QMap<QString, QSharedPointer<RoomSettings>> &settings, void addRoom(const QMap<QString, QSharedPointer<RoomSettings>> &settings,
const RoomState &state, const RoomState &state,
@ -64,6 +67,7 @@ signals:
void totalUnreadMessageCountUpdated(int count); void totalUnreadMessageCountUpdated(int count);
void acceptInvite(const QString &room_id); void acceptInvite(const QString &room_id);
void declineInvite(const QString &room_id); void declineInvite(const QString &room_id);
void roomAvatarChanged(const QString &room_id, const QPixmap &img);
public slots: public slots:
void updateRoomAvatar(const QString &roomid, const QPixmap &img); void updateRoomAvatar(const QString &roomid, const QPixmap &img);
@ -96,4 +100,5 @@ private:
QMap<QString, QSharedPointer<RoomInfoListItem>> rooms_; QMap<QString, QSharedPointer<RoomInfoListItem>> rooms_;
QSharedPointer<MatrixClient> client_; QSharedPointer<MatrixClient> client_;
QSharedPointer<Cache> cache_;
}; };

View file

@ -17,6 +17,7 @@
#include <stdexcept> #include <stdexcept>
#include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QStandardPaths> #include <QStandardPaths>
@ -34,6 +35,7 @@ Cache::Cache(const QString &userId)
, stateDb_{0} , stateDb_{0}
, roomDb_{0} , roomDb_{0}
, invitesDb_{0} , invitesDb_{0}
, imagesDb_{0}
, isMounted_{false} , isMounted_{false}
, userId_{userId} , userId_{userId}
{} {}
@ -54,7 +56,7 @@ Cache::setup()
bool isInitial = !QFile::exists(statePath); bool isInitial = !QFile::exists(statePath);
env_ = lmdb::env::create(); 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); env_.set_max_dbs(1024UL);
if (isInitial) { if (isInitial) {
@ -91,12 +93,60 @@ Cache::setup()
stateDb_ = lmdb::dbi::open(txn, "state", MDB_CREATE); stateDb_ = lmdb::dbi::open(txn, "state", MDB_CREATE);
roomDb_ = lmdb::dbi::open(txn, "rooms", MDB_CREATE); roomDb_ = lmdb::dbi::open(txn, "rooms", MDB_CREATE);
invitesDb_ = lmdb::dbi::open(txn, "invites", MDB_CREATE); invitesDb_ = lmdb::dbi::open(txn, "invites", MDB_CREATE);
imagesDb_ = lmdb::dbi::open(txn, "images", MDB_CREATE);
txn.commit(); txn.commit();
isMounted_ = true; 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 void
Cache::setState(const QString &nextBatchToken, const QMap<QString, RoomState> &states) Cache::setState(const QString &nextBatchToken, const QMap<QString, RoomState> &states)
{ {

View file

@ -232,8 +232,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
view_manager_->queueAudioMessage(roomid, filename, url); view_manager_->queueAudioMessage(roomid, filename, url);
}); });
connect( connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
client_.data(), &MatrixClient::roomAvatarRetrieved, this, &ChatPage::updateTopBarAvatar);
connect(client_.data(), connect(client_.data(),
&MatrixClient::initialSyncCompleted, &MatrixClient::initialSyncCompleted,
@ -353,6 +352,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
client_->getOwnProfile(); client_->getOwnProfile();
cache_ = QSharedPointer<Cache>(new Cache(userid)); cache_ = QSharedPointer<Cache>(new Cache(userid));
room_list_->setCache(cache_);
try { try {
cache_->setup(); cache_->setup();

View file

@ -468,7 +468,7 @@ MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url)
QNetworkRequest avatar_request(endpoint); QNetworkRequest avatar_request(endpoint);
QNetworkReply *reply = get(avatar_request); 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(); reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -486,7 +486,7 @@ MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url)
QPixmap pixmap; QPixmap pixmap;
pixmap.loadFromData(img); pixmap.loadFromData(img);
emit roomAvatarRetrieved(roomid, pixmap); emit roomAvatarRetrieved(roomid, pixmap, avatar_url.toString(), img);
}); });
} }

View file

@ -15,9 +15,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QBuffer>
#include <QDebug> #include <QDebug>
#include <QObject> #include <QObject>
#include "Cache.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "OverlayModal.h" #include "OverlayModal.h"
@ -53,9 +55,17 @@ RoomList::RoomList(QSharedPointer<MatrixClient> client, QWidget *parent)
topLayout_->addWidget(scrollArea_); topLayout_->addWidget(scrollArea_);
connect(client_.data(), connect(client_.data(),
SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), &MatrixClient::roomAvatarRetrieved,
this, 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() {} RoomList::~RoomList() {}
@ -79,12 +89,33 @@ RoomList::addRoom(const QMap<QString, QSharedPointer<RoomSettings>> &settings,
rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item)); rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));
if (!state.getAvatar().toString().isEmpty()) if (!state.getAvatar().toString().isEmpty())
client_->fetchRoomAvatar(room_id, state.getAvatar()); updateAvatar(room_id, state.getAvatar().toString());
int pos = contentsLayout_->count() - 1; int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item); 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 void
RoomList::removeRoom(const QString &room_id, bool reset) RoomList::removeRoom(const QString &room_id, bool reset)
{ {
@ -194,7 +225,7 @@ RoomList::sync(const QMap<QString, RoomState> &states,
auto new_avatar = state.getAvatar(); auto new_avatar = state.getAvatar();
if (current_avatar != new_avatar && !new_avatar.toString().isEmpty()) if (current_avatar != new_avatar && !new_avatar.toString().isEmpty())
client_->fetchRoomAvatar(room_id, new_avatar); updateAvatar(room_id, new_avatar.toString());
room->setState(state); room->setState(state);
} }
@ -246,6 +277,9 @@ RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img)
} }
rooms_.value(roomid)->setAvatar(img.toImage()); rooms_.value(roomid)->setAvatar(img.toImage());
// Used to inform other widgets for the new image data.
emit roomAvatarChanged(roomid, img);
} }
void void
@ -308,10 +342,7 @@ RoomList::addInvitedRoom(const QString &room_id, const mtx::responses::InvitedRo
rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item)); rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));
auto avatarUrl = QString::fromStdString(room.avatar()); updateAvatar(room_id, QString::fromStdString(room.avatar()));
if (!avatarUrl.isEmpty())
client_->fetchRoomAvatar(room_id, avatarUrl);
int pos = contentsLayout_->count() - 1; int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item); contentsLayout_->insertWidget(pos, room_item);