diff --git a/src/AvatarProvider.cc b/src/AvatarProvider.cc
index 5089a128..9f861fdb 100644
--- a/src/AvatarProvider.cc
+++ b/src/AvatarProvider.cc
@@ -16,41 +16,33 @@
*/
#include "AvatarProvider.h"
+#include "Cache.h"
#include "MatrixClient.h"
QSharedPointer<MatrixClient> AvatarProvider::client_;
-
-std::map<QString, AvatarData> AvatarProvider::avatars_;
-
-void
-AvatarProvider::init(QSharedPointer<MatrixClient> client)
-{
- client_ = client;
-}
-
-void
-AvatarProvider::updateAvatar(const QString &uid, const QImage &img)
-{
- auto avatarData = &avatars_[uid];
- avatarData->img = img;
-}
+QHash<QString, QImage> AvatarProvider::avatars_;
void
-AvatarProvider::resolve(const QString &userId,
+AvatarProvider::resolve(const QString &room_id,
+ const QString &user_id,
QObject *receiver,
std::function<void(QImage)> callback)
{
- if (avatars_.find(userId) == avatars_.end())
+ const auto key = QString("%1 %2").arg(room_id).arg(user_id);
+
+ if (!Cache::AvatarUrls.contains(key))
return;
- auto img = avatars_[userId].img;
+ if (avatars_.contains(key)) {
+ auto img = avatars_[key];
- if (!img.isNull()) {
- callback(img);
- return;
+ if (!img.isNull()) {
+ callback(img);
+ return;
+ }
}
- auto proxy = client_->fetchUserAvatar(avatars_[userId].url);
+ auto proxy = client_->fetchUserAvatar(Cache::avatarUrl(room_id, user_id));
if (proxy.isNull())
return;
@@ -58,18 +50,9 @@ AvatarProvider::resolve(const QString &userId,
connect(proxy.data(),
&DownloadMediaProxy::avatarDownloaded,
receiver,
- [userId, proxy, callback](const QImage &img) {
+ [user_id, proxy, callback, key](const QImage &img) {
proxy->deleteLater();
- updateAvatar(userId, img);
+ avatars_.insert(key, img);
callback(img);
});
}
-
-void
-AvatarProvider::setAvatarUrl(const QString &userId, const QUrl &url)
-{
- AvatarData data;
- data.url = url;
-
- avatars_.emplace(userId, data);
-}
diff --git a/src/Cache.cc b/src/Cache.cc
index 3c8a1bb3..eca13635 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -20,6 +20,7 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
+#include <QHash>
#include <QStandardPaths>
#include <variant.hpp>
@@ -27,24 +28,39 @@
#include "Cache.h"
#include "RoomState.h"
+//! Should be changed when a breaking change occurs in the cache format.
+//! This will reset client's data.
static const std::string CURRENT_CACHE_FORMAT_VERSION("2018.01.14");
static const lmdb::val NEXT_BATCH_KEY("next_batch");
static const lmdb::val CACHE_FORMAT_VERSION_KEY("cache_format_version");
+//! Cache databases and their format.
+//!
+//! Contains UI information for the joined rooms. (i.e name, topic, avatar url etc).
+//! Format: room_id -> RoomInfo
+static constexpr const char *ROOMS_DB = "rooms";
+static constexpr const char *INVITES_DB = "rooms";
+//! Keeps already downloaded media for reuse.
+//! Format: matrix_url -> binary data.
+static constexpr const char *MEDIA_DB = "media";
+//! Information that must be kept between sync requests.
+static constexpr const char *SYNC_STATE_DB = "sync_state";
+//! Read receipts per room/event.
+static constexpr const char *READ_RECEIPTS_DB = "read_receipts";
+
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
Cache::Cache(const QString &userId, QObject *parent)
: QObject{parent}
, env_{nullptr}
- , stateDb_{0}
- , roomDb_{0}
+ , syncStateDb_{0}
+ , roomsDb_{0}
, invitesDb_{0}
- , imagesDb_{0}
+ , mediaDb_{0}
, readReceiptsDb_{0}
- , isMounted_{false}
- , userId_{userId}
+ , localUserId_{userId}
{}
void
@@ -54,11 +70,11 @@ Cache::setup()
auto statePath = QString("%1/%2/state")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(QString::fromUtf8(userId_.toUtf8().toHex()));
+ .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
cacheDirectory_ = QString("%1/%2")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
- .arg(QString::fromUtf8(userId_.toUtf8().toHex()));
+ .arg(QString::fromUtf8(localUserId_.toUtf8().toHex()));
bool isInitial = !QFile::exists(statePath);
@@ -97,30 +113,26 @@ Cache::setup()
}
auto txn = lmdb::txn::begin(env_);
- 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);
- readReceiptsDb_ = lmdb::dbi::open(txn, "read_receipts", MDB_CREATE);
-
+ syncStateDb_ = lmdb::dbi::open(txn, SYNC_STATE_DB, MDB_CREATE);
+ roomsDb_ = lmdb::dbi::open(txn, ROOMS_DB, MDB_CREATE);
+ invitesDb_ = lmdb::dbi::open(txn, INVITES_DB, MDB_CREATE);
+ mediaDb_ = lmdb::dbi::open(txn, MEDIA_DB, MDB_CREATE);
+ readReceiptsDb_ = lmdb::dbi::open(txn, READ_RECEIPTS_DB, MDB_CREATE);
txn.commit();
- isMounted_ = true;
+ qRegisterMetaType<RoomInfo>();
}
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_,
+ mediaDb_,
lmdb::val(key.data(), key.size()),
lmdb::val(image.data(), image.size()));
@@ -140,7 +152,7 @@ Cache::image(const QString &url) const
lmdb::val image;
- bool res = lmdb::dbi_get(txn, imagesDb_, lmdb::val(key.data(), key.size()), image);
+ bool res = lmdb::dbi_get(txn, mediaDb_, lmdb::val(key.data(), key.size()), image);
txn.commit();
@@ -156,163 +168,37 @@ Cache::image(const QString &url) const
}
void
-Cache::setState(const QString &nextBatchToken,
- const std::map<QString, QSharedPointer<RoomState>> &states)
+Cache::removeInvite(const std::string &room_id)
{
- if (!isMounted_)
- return;
-
- try {
- auto txn = lmdb::txn::begin(env_);
-
- setNextBatchToken(txn, nextBatchToken);
-
- for (auto const &state : states)
- insertRoomState(txn, state.first, state.second);
-
- txn.commit();
- } catch (const lmdb::error &e) {
- qCritical() << "The cache couldn't be updated: " << e.what();
-
- unmount();
- deleteData();
- }
-}
-
-void
-Cache::insertRoomState(lmdb::txn &txn,
- const QString &roomid,
- const QSharedPointer<RoomState> &state)
-{
- auto stateEvents = state->serialize();
- auto id = roomid.toUtf8();
-
- lmdb::dbi_put(txn, roomDb_, lmdb::val(id.data(), id.size()), lmdb::val(stateEvents));
-
- for (const auto &membership : state->memberships) {
- lmdb::dbi membersDb =
- lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE);
-
- // The user_id this membership event relates to, is used
- // as the index on the membership database.
- auto key = membership.second.state_key;
-
- // Serialize membership event.
- nlohmann::json data = membership.second;
- std::string memberEvent = data.dump();
-
- switch (membership.second.content.membership) {
- // We add or update (e.g invite -> join) a new user to the membership
- // list.
- case mtx::events::state::Membership::Invite:
- case mtx::events::state::Membership::Join: {
- lmdb::dbi_put(txn, membersDb, lmdb::val(key), lmdb::val(memberEvent));
- break;
- }
- // We remove the user from the membership list.
- case mtx::events::state::Membership::Leave:
- case mtx::events::state::Membership::Ban: {
- lmdb::dbi_del(txn, membersDb, lmdb::val(key), lmdb::val(memberEvent));
- break;
- }
- case mtx::events::state::Membership::Knock: {
- qWarning()
- << "Skipping knock membership" << roomid << QString::fromStdString(key);
- break;
- }
- }
- }
+ auto txn = lmdb::txn::begin(env_);
+ lmdb::dbi_del(txn, invitesDb_, lmdb::val(room_id), nullptr);
+ txn.commit();
}
void
-Cache::removeRoom(const QString &roomid)
+Cache::removeRoom(lmdb::txn &txn, const std::string &roomid)
{
- if (!isMounted_)
- return;
-
- auto txn = lmdb::txn::begin(env_, nullptr, 0);
-
- lmdb::dbi_del(txn, roomDb_, lmdb::val(roomid.toUtf8(), roomid.toUtf8().size()), nullptr);
-
- txn.commit();
+ lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr);
}
void
-Cache::removeInvite(const QString &room_id)
+Cache::removeRoom(const std::string &roomid)
{
- if (!isMounted_)
- return;
-
auto txn = lmdb::txn::begin(env_, nullptr, 0);
-
- lmdb::dbi_del(
- txn, invitesDb_, lmdb::val(room_id.toUtf8(), room_id.toUtf8().size()), nullptr);
-
+ lmdb::dbi_del(txn, roomsDb_, lmdb::val(roomid), nullptr);
txn.commit();
}
void
-Cache::states()
+Cache::setNextBatchToken(lmdb::txn &txn, const std::string &token)
{
- std::map<QString, RoomState> states;
-
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
- auto cursor = lmdb::cursor::open(txn, roomDb_);
-
- std::string room;
- std::string stateData;
-
- // Retrieve all the room names.
- while (cursor.get(room, stateData, MDB_NEXT)) {
- auto roomid = QString::fromUtf8(room.data(), room.size());
- auto json = nlohmann::json::parse(stateData);
-
- RoomState state;
- state.parse(json);
-
- auto memberDb = lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE);
- std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> members;
-
- auto memberCursor = lmdb::cursor::open(txn, memberDb);
-
- std::string memberId;
- std::string memberContent;
-
- while (memberCursor.get(memberId, memberContent, MDB_NEXT)) {
- auto userid = QString::fromStdString(memberId);
-
- try {
- auto data = nlohmann::json::parse(memberContent);
- mtx::events::StateEvent<mtx::events::state::Member> member = data;
- members.emplace(memberId, member);
- } catch (std::exception &e) {
- qWarning() << "Fault while parsing member event" << e.what()
- << QString::fromStdString(memberContent);
- continue;
- }
- }
-
- qDebug() << members.size() << "members for" << roomid;
-
- state.memberships = members;
- states.emplace(roomid, std::move(state));
- }
-
- qDebug() << "Retrieved" << states.size() << "rooms";
-
- cursor.close();
-
- txn.commit();
-
- emit statesLoaded(states);
+ lmdb::dbi_put(txn, syncStateDb_, NEXT_BATCH_KEY, lmdb::val(token.data(), token.size()));
}
void
Cache::setNextBatchToken(lmdb::txn &txn, const QString &token)
{
- auto value = token.toUtf8();
-
- lmdb::dbi_put(txn, stateDb_, NEXT_BATCH_KEY, lmdb::val(value.data(), value.size()));
+ setNextBatchToken(txn, token.toStdString());
}
bool
@@ -321,7 +207,7 @@ Cache::isInitialized() const
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
lmdb::val token;
- bool res = lmdb::dbi_get(txn, stateDb_, NEXT_BATCH_KEY, token);
+ bool res = lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token);
txn.commit();
@@ -334,7 +220,7 @@ Cache::nextBatchToken() const
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
lmdb::val token;
- lmdb::dbi_get(txn, stateDb_, NEXT_BATCH_KEY, token);
+ lmdb::dbi_get(txn, syncStateDb_, NEXT_BATCH_KEY, token);
txn.commit();
@@ -356,7 +242,7 @@ Cache::isFormatValid()
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
lmdb::val current_version;
- bool res = lmdb::dbi_get(txn, stateDb_, CACHE_FORMAT_VERSION_KEY, current_version);
+ bool res = lmdb::dbi_get(txn, syncStateDb_, CACHE_FORMAT_VERSION_KEY, current_version);
txn.commit();
@@ -381,69 +267,13 @@ Cache::setCurrentFormat()
lmdb::dbi_put(
txn,
- stateDb_,
+ syncStateDb_,
CACHE_FORMAT_VERSION_KEY,
lmdb::val(CURRENT_CACHE_FORMAT_VERSION.data(), CURRENT_CACHE_FORMAT_VERSION.size()));
txn.commit();
}
-std::map<std::string, mtx::responses::InvitedRoom>
-Cache::invites()
-{
- std::map<std::string, mtx::responses::InvitedRoom> invites;
-
- auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
- auto cursor = lmdb::cursor::open(txn, invitesDb_);
-
- std::string room_id;
- std::string payload;
-
- mtx::responses::InvitedRoom state;
-
- while (cursor.get(room_id, payload, MDB_NEXT)) {
- state = nlohmann::json::parse(payload);
- invites.emplace(room_id, state);
- }
-
- if (invites.size() > 0)
- qDebug() << "Retrieved" << invites.size() << "invites";
-
- cursor.close();
-
- txn.commit();
-
- return invites;
-}
-
-void
-Cache::setInvites(const std::map<std::string, mtx::responses::InvitedRoom> &invites)
-{
- if (!isMounted_)
- return;
-
- try {
- auto txn = lmdb::txn::begin(env_);
-
- for (auto it = invites.cbegin(); it != invites.cend(); ++it) {
- nlohmann::json j;
-
- for (const auto &e : it->second.invite_state) {
- mpark::visit(
- [&j](auto msg) { j["invite_state"]["events"].push_back(msg); },
- e);
- }
-
- lmdb::dbi_put(txn, invitesDb_, lmdb::val(it->first), lmdb::val(j.dump()));
- }
-
- txn.commit();
- } catch (const lmdb::error &e) {
- qCritical() << "setInvitedRooms: " << e.what();
- unmount();
- }
-}
-
CachedReceipts
Cache::readReceipts(const QString &event_id, const QString &room_id)
{
@@ -533,3 +363,634 @@ Cache::updateReadReceipt(const std::string &room_id, const Receipts &receipts)
}
}
}
+
+void
+Cache::saveState(const mtx::responses::Sync &res)
+{
+ auto txn = lmdb::txn::begin(env_);
+
+ setNextBatchToken(txn, res.next_batch);
+
+ // Save joined rooms
+ for (const auto &room : res.rooms.join) {
+ auto statesdb = getStatesDb(txn, room.first);
+ auto membersdb = getMembersDb(txn, room.first);
+
+ saveStateEvents(txn, statesdb, membersdb, room.first, room.second.state.events);
+ saveStateEvents(txn, statesdb, membersdb, room.first, room.second.timeline.events);
+
+ RoomInfo updatedInfo;
+ updatedInfo.name = getRoomName(txn, statesdb, membersdb).toStdString();
+ updatedInfo.topic = getRoomTopic(txn, statesdb).toStdString();
+ updatedInfo.avatar_url =
+ getRoomAvatarUrl(txn, statesdb, membersdb, QString::fromStdString(room.first))
+ .toStdString();
+
+ lmdb::dbi_put(
+ txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump()));
+ }
+
+ saveInvites(txn, res.rooms.invite);
+
+ std::map<std::string, bool> invites;
+ for (const auto &invite : res.rooms.invite)
+ invites.emplace(std::move(invite.first), true);
+
+ // removeStaleInvites(txn, invites);
+
+ removeLeftRooms(txn, res.rooms.leave);
+
+ txn.commit();
+}
+
+void
+Cache::removeStaleInvites(lmdb::txn &txn, const std::map<std::string, bool> &curr)
+{
+ auto invitesCursor = lmdb::cursor::open(txn, invitesDb_);
+
+ std::string room_id, room_data;
+ while (invitesCursor.get(room_id, room_data, MDB_NEXT)) {
+ if (curr.find(room_id) == curr.end())
+ lmdb::cursor_del(invitesCursor);
+ }
+
+ invitesCursor.close();
+}
+
+void
+Cache::saveInvites(lmdb::txn &txn, const std::map<std::string, mtx::responses::InvitedRoom> &rooms)
+{
+ for (const auto &room : rooms) {
+ auto statesdb = getInviteStatesDb(txn, room.first);
+ auto membersdb = getInviteMembersDb(txn, room.first);
+
+ saveInvite(txn, statesdb, membersdb, room.second);
+
+ RoomInfo updatedInfo;
+ updatedInfo.name = getInviteRoomName(txn, statesdb, membersdb).toStdString();
+ updatedInfo.topic = getInviteRoomTopic(txn, statesdb).toStdString();
+ updatedInfo.avatar_url =
+ getInviteRoomAvatarUrl(txn, statesdb, membersdb).toStdString();
+ updatedInfo.is_invite = true;
+
+ lmdb::dbi_put(
+ txn, invitesDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump()));
+ }
+}
+
+void
+Cache::saveInvite(lmdb::txn &txn,
+ lmdb::dbi &statesdb,
+ lmdb::dbi &membersdb,
+ const mtx::responses::InvitedRoom &room)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ for (const auto &e : room.invite_state) {
+ if (mpark::holds_alternative<StrippedEvent<Member>>(e)) {
+ auto msg = mpark::get<StrippedEvent<Member>>(e);
+
+ auto display_name = msg.content.display_name.empty()
+ ? msg.state_key
+ : msg.content.display_name;
+
+ MemberInfo tmp{display_name, msg.content.avatar_url};
+
+ lmdb::dbi_put(
+ txn, membersdb, lmdb::val(msg.state_key), lmdb::val(json(tmp).dump()));
+ } else {
+ mpark::visit(
+ [&txn, &statesdb](auto msg) {
+ bool res = lmdb::dbi_put(txn,
+ statesdb,
+ lmdb::val(to_string(msg.type)),
+ lmdb::val(json(msg).dump()));
+
+ if (!res)
+ std::cout << "couldn't save data" << json(msg).dump()
+ << '\n';
+ },
+ e);
+ }
+ }
+}
+
+std::vector<std::string>
+Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
+{
+ std::vector<std::string> rooms;
+ for (const auto &room : res.rooms.join) {
+ bool hasUpdates = false;
+ for (const auto &s : room.second.state.events) {
+ if (containsStateUpdates(s)) {
+ hasUpdates = true;
+ break;
+ }
+ }
+
+ for (const auto &s : room.second.timeline.events) {
+ if (containsStateUpdates(s)) {
+ hasUpdates = true;
+ break;
+ }
+ }
+
+ if (hasUpdates)
+ rooms.emplace_back(room.first);
+ }
+
+ for (const auto &room : res.rooms.invite) {
+ for (const auto &s : room.second.invite_state) {
+ if (containsStateUpdates(s)) {
+ rooms.emplace_back(room.first);
+ break;
+ }
+ }
+ }
+
+ return rooms;
+}
+
+std::map<QString, RoomInfo>
+Cache::getRoomInfo(const std::vector<std::string> &rooms)
+{
+ std::map<QString, RoomInfo> room_info;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+
+ for (const auto &room : rooms) {
+ lmdb::val data;
+
+ // Check if the room is joined.
+ if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room), data)) {
+ try {
+ room_info.emplace(
+ QString::fromStdString(room),
+ json::parse(std::string(data.data(), data.size())));
+ } catch (const json::exception &e) {
+ qWarning()
+ << "failed to parse room info:" << QString::fromStdString(room)
+ << QString::fromStdString(std::string(data.data(), data.size()));
+ }
+ } else {
+ // Check if the room is an invite.
+ if (lmdb::dbi_get(txn, invitesDb_, lmdb::val(room), data)) {
+ try {
+ room_info.emplace(
+ QString::fromStdString(room),
+ json::parse(std::string(data.data(), data.size())));
+ } catch (const json::exception &e) {
+ qWarning() << "failed to parse room info for invite:"
+ << QString::fromStdString(room)
+ << QString::fromStdString(
+ std::string(data.data(), data.size()));
+ }
+ }
+ }
+ }
+
+ txn.commit();
+
+ return room_info;
+}
+
+QMap<QString, RoomInfo>
+Cache::roomInfo()
+{
+ QMap<QString, RoomInfo> result;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+ auto invitesCursor = lmdb::cursor::open(txn, invitesDb_);
+
+ std::string room_id;
+ std::string room_data;
+
+ // Gather info about the joined rooms.
+ while (roomsCursor.get(room_id, room_data, MDB_NEXT)) {
+ RoomInfo tmp = json::parse(room_data);
+ result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp));
+ }
+
+ // Gather info about the invites.
+ while (invitesCursor.get(room_id, room_data, MDB_NEXT)) {
+ RoomInfo tmp = json::parse(room_data);
+ result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp));
+ }
+
+ invitesCursor.close();
+ roomsCursor.close();
+
+ txn.commit();
+
+ return result;
+}
+
+QString
+Cache::getRoomAvatarUrl(lmdb::txn &txn,
+ lmdb::dbi &statesdb,
+ lmdb::dbi &membersdb,
+ const QString &room_id)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event);
+
+ if (res) {
+ try {
+ StateEvent<Avatar> msg =
+ json::parse(std::string(event.data(), event.size()));
+
+ return QString::fromStdString(msg.content.url);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ // We don't use an avatar for group chats.
+ if (membersdb.size(txn) > 2)
+ return QString();
+
+ auto cursor = lmdb::cursor::open(txn, membersdb);
+ std::string user_id;
+ std::string member_data;
+
+ // Resolve avatar for 1-1 chats.
+ while (cursor.get(user_id, member_data, MDB_NEXT)) {
+ if (user_id == localUserId_.toStdString())
+ continue;
+
+ try {
+ MemberInfo m = json::parse(member_data);
+
+ cursor.close();
+ return QString::fromStdString(m.avatar_url);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ cursor.close();
+
+ // Default case when there is only one member.
+ return avatarUrl(room_id, localUserId_);
+}
+
+QString
+Cache::getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event);
+
+ if (res) {
+ try {
+ StateEvent<Name> msg = json::parse(std::string(event.data(), event.size()));
+
+ if (!msg.content.name.empty())
+ return QString::fromStdString(msg.content.name);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomCanonicalAlias)), event);
+
+ if (res) {
+ try {
+ StateEvent<CanonicalAlias> msg =
+ json::parse(std::string(event.data(), event.size()));
+
+ if (!msg.content.alias.empty())
+ return QString::fromStdString(msg.content.alias);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ auto cursor = lmdb::cursor::open(txn, membersdb);
+ const int total = membersdb.size(txn);
+
+ std::size_t ii = 0;
+ std::string user_id;
+ std::string member_data;
+ std::map<std::string, MemberInfo> members;
+
+ while (cursor.get(user_id, member_data, MDB_NEXT) && ii < 3) {
+ try {
+ members.emplace(user_id, json::parse(member_data));
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+
+ ii++;
+ }
+
+ cursor.close();
+
+ if (total == 1 && !members.empty())
+ return QString::fromStdString(members.begin()->second.name);
+
+ auto first_member = [&members, this]() {
+ for (const auto &m : members) {
+ if (m.first != localUserId_.toStdString())
+ return QString::fromStdString(m.second.name);
+ }
+
+ return localUserId_;
+ }();
+
+ if (total == 2)
+ return first_member;
+ else if (total > 2)
+ return QString("%1 and %2 others").arg(first_member).arg(total);
+
+ return "Empty Room";
+}
+
+QString
+Cache::getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event);
+
+ if (res) {
+ try {
+ StateEvent<Topic> msg =
+ json::parse(std::string(event.data(), event.size()));
+
+ if (!msg.content.topic.empty())
+ return QString::fromStdString(msg.content.topic);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ return QString();
+}
+
+QString
+Cache::getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomName)), event);
+
+ if (res) {
+ try {
+ StrippedEvent<state::Name> msg =
+ json::parse(std::string(event.data(), event.size()));
+ return QString::fromStdString(msg.content.name);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ auto cursor = lmdb::cursor::open(txn, membersdb);
+ std::string user_id, member_data;
+
+ while (cursor.get(user_id, member_data, MDB_NEXT)) {
+ if (user_id == localUserId_.toStdString())
+ continue;
+
+ try {
+ MemberInfo tmp = json::parse(member_data);
+ cursor.close();
+
+ return QString::fromStdString(tmp.name);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ cursor.close();
+
+ return QString("Empty Room");
+}
+
+QString
+Cache::getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res = lmdb::dbi_get(
+ txn, statesdb, lmdb::val(to_string(mtx::events::EventType::RoomAvatar)), event);
+
+ if (res) {
+ try {
+ StrippedEvent<state::Avatar> msg =
+ json::parse(std::string(event.data(), event.size()));
+ return QString::fromStdString(msg.content.url);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ auto cursor = lmdb::cursor::open(txn, membersdb);
+ std::string user_id, member_data;
+
+ while (cursor.get(user_id, member_data, MDB_NEXT)) {
+ if (user_id == localUserId_.toStdString())
+ continue;
+
+ try {
+ MemberInfo tmp = json::parse(member_data);
+ cursor.close();
+
+ return QString::fromStdString(tmp.avatar_url);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ cursor.close();
+
+ return QString();
+}
+
+QString
+Cache::getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &db)
+{
+ using namespace mtx::events;
+ using namespace mtx::events::state;
+
+ lmdb::val event;
+ bool res =
+ lmdb::dbi_get(txn, db, lmdb::val(to_string(mtx::events::EventType::RoomTopic)), event);
+
+ if (res) {
+ try {
+ StrippedEvent<Topic> msg =
+ json::parse(std::string(event.data(), event.size()));
+ return QString::fromStdString(msg.content.topic);
+ } catch (const json::exception &e) {
+ qWarning() << QString::fromStdString(e.what());
+ }
+ }
+
+ return QString();
+}
+
+std::vector<std::string>
+Cache::joinedRooms()
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto roomsCursor = lmdb::cursor::open(txn, roomsDb_);
+
+ std::string id, data;
+ std::vector<std::string> room_ids;
+
+ // Gather the room ids for the joined rooms.
+ while (roomsCursor.get(id, data, MDB_NEXT))
+ room_ids.emplace_back(id);
+
+ roomsCursor.close();
+ txn.commit();
+
+ return room_ids;
+}
+
+void
+Cache::populateMembers()
+{
+ auto rooms = joinedRooms();
+ qDebug() << "loading" << rooms.size() << "rooms";
+
+ auto txn = lmdb::txn::begin(env_);
+
+ for (const auto &room : rooms) {
+ const auto roomid = QString::fromStdString(room);
+
+ auto membersdb = getMembersDb(txn, room);
+ auto cursor = lmdb::cursor::open(txn, membersdb);
+
+ std::string user_id, info;
+ while (cursor.get(user_id, info, MDB_NEXT)) {
+ MemberInfo m = json::parse(info);
+
+ const auto userid = QString::fromStdString(user_id);
+
+ insertDisplayName(roomid, userid, QString::fromStdString(m.name));
+ insertAvatarUrl(roomid, userid, QString::fromStdString(m.avatar_url));
+ }
+
+ cursor.close();
+ }
+
+ txn.commit();
+}
+
+QVector<SearchResult>
+Cache::getAutocompleteMatches(const std::string &room_id,
+ const std::string &query,
+ std::uint8_t max_items)
+{
+ std::multimap<int, std::pair<std::string, std::string>> items;
+
+ auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+ auto cursor = lmdb::cursor::open(txn, getMembersDb(txn, room_id));
+
+ std::string user_id, user_data;
+ while (cursor.get(user_id, user_data, MDB_NEXT)) {
+ const auto display_name = displayName(room_id, user_id);
+ const int score = utils::levenshtein_distance(query, display_name);
+
+ items.emplace(score, std::make_pair(user_id, display_name));
+ }
+
+ auto end = items.begin();
+
+ if (items.size() >= max_items)
+ std::advance(end, max_items);
+ else if (items.size() > 0)
+ std::advance(end, items.size());
+
+ QVector<SearchResult> results;
+ for (auto it = items.begin(); it != end; it++) {
+ const auto user = it->second;
+ results.push_back(SearchResult{QString::fromStdString(user.first),
+ QString::fromStdString(user.second)});
+ }
+
+ return results;
+}
+
+QHash<QString, QString> Cache::DisplayNames;
+QHash<QString, QString> Cache::AvatarUrls;
+
+QString
+Cache::displayName(const QString &room_id, const QString &user_id)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ if (DisplayNames.contains(fmt))
+ return DisplayNames[fmt];
+
+ return user_id;
+}
+
+std::string
+Cache::displayName(const std::string &room_id, const std::string &user_id)
+{
+ auto fmt = QString::fromStdString(room_id + " " + user_id);
+ if (DisplayNames.contains(fmt))
+ return DisplayNames[fmt].toStdString();
+
+ return user_id;
+}
+
+QString
+Cache::avatarUrl(const QString &room_id, const QString &user_id)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ if (AvatarUrls.contains(fmt))
+ return AvatarUrls[fmt];
+
+ return QString();
+}
+
+void
+Cache::insertDisplayName(const QString &room_id,
+ const QString &user_id,
+ const QString &display_name)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ DisplayNames.insert(fmt, display_name);
+}
+
+void
+Cache::removeDisplayName(const QString &room_id, const QString &user_id)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ DisplayNames.remove(fmt);
+}
+
+void
+Cache::insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ AvatarUrls.insert(fmt, avatar_url);
+}
+
+void
+Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
+{
+ auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
+ AvatarUrls.remove(fmt);
+}
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 8b8b4438..aa21a98e 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -28,7 +28,6 @@
#include "OverlayModal.h"
#include "QuickSwitcher.h"
#include "RoomList.h"
-#include "RoomSettings.h"
#include "RoomState.h"
#include "SideBarActions.h"
#include "Splitter.h"
@@ -158,20 +157,21 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
typingDisplay_->setUsers(users);
});
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
- connect(room_list_, &RoomList::roomChanged, text_input_, [this](const QString &room_id) {
- if (roomStates_.find(room_id) != roomStates_.end())
- text_input_->setRoomState(roomStates_[room_id]);
- else
- qWarning() << "no state found for room_id" << room_id;
- });
-
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
connect(
room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView);
- connect(room_list_, &RoomList::acceptInvite, client_.data(), &MatrixClient::joinRoom);
- connect(room_list_, &RoomList::declineInvite, client_.data(), &MatrixClient::leaveRoom);
+ connect(room_list_, &RoomList::acceptInvite, this, [this](const QString &room_id) {
+ view_manager_->addRoom(room_id);
+ client_->joinRoom(room_id);
+ room_list_->removeRoom(room_id, currentRoom() == room_id);
+ });
+
+ connect(room_list_, &RoomList::declineInvite, this, [this](const QString &room_id) {
+ client_->leaveRoom(room_id);
+ room_list_->removeRoom(room_id, currentRoom() == room_id);
+ });
connect(text_input_, &TextInputWidget::startedTyping, this, [this]() {
if (!userSettings_->isTypingNotificationsEnabled())
@@ -329,15 +329,22 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
connect(client_.data(), &MatrixClient::joinedRoom, this, [this](const QString &room_id) {
emit showNotification("You joined the room.");
- removeInvite(room_id);
+
+ // We remove any invites with the same room_id.
+ try {
+ cache_->removeInvite(room_id.toStdString());
+ } catch (const lmdb::error &e) {
+ emit showNotification(QString("Failed to remove invite: %1")
+ .arg(QString::fromStdString(e.what())));
+ }
});
+ connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
connect(client_.data(), &MatrixClient::invitedUser, this, [this](QString, QString user) {
emit showNotification(QString("Invited user %1").arg(user));
});
connect(client_.data(), &MatrixClient::roomCreated, this, [this](QString room_id) {
emit showNotification(QString("Room %1 created").arg(room_id));
});
- connect(client_.data(), &MatrixClient::leftRoom, this, &ChatPage::removeRoom);
connect(client_.data(), &MatrixClient::redactionFailed, this, [this](const QString &error) {
emit showNotification(QString("Message redaction failed: %1").arg(error));
});
@@ -394,7 +401,38 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client,
AvatarProvider::init(client);
+ connect(this, &ChatPage::continueSync, this, [this](const QString &next_batch) {
+ syncTimeoutTimer_->start(SYNC_RETRY_TIMEOUT);
+ client_->setNextBatchToken(next_batch);
+ client_->sync();
+ });
+
+ connect(this, &ChatPage::startConsesusTimer, this, [this]() {
+ consensusTimer_->start(CONSENSUS_TIMEOUT);
+ showContentTimer_->start(SHOW_CONTENT_TIMEOUT);
+ });
+ connect(this, &ChatPage::initializeRoomList, room_list_, &RoomList::initialize);
+ connect(this,
+ &ChatPage::initializeViews,
+ view_manager_,
+ [this](const mtx::responses::Rooms &rooms) { view_manager_->initialize(rooms); });
+ connect(
+ this,
+ &ChatPage::initializeEmptyViews,
+ this,
+ [this](const std::vector<std::string> &rooms) { view_manager_->initialize(rooms); });
+ connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
+ view_manager_->initialize(rooms);
+ removeLeftRooms(rooms.leave);
+ });
+ connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
+
instance_ = this;
+
+ qRegisterMetaType<std::map<QString, RoomInfo>>();
+ qRegisterMetaType<QMap<QString, RoomInfo>>();
+ qRegisterMetaType<mtx::responses::Rooms>();
+ qRegisterMetaType<std::vector<std::string>>();
}
void
@@ -412,8 +450,6 @@ ChatPage::resetUI()
{
roomAvatars_.clear();
room_list_->clear();
- roomSettings_.clear();
- roomStates_.clear();
top_bar_->reset();
user_info_widget_->reset();
view_manager_->clearAll();
@@ -451,6 +487,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
cache_ = QSharedPointer<Cache>(new Cache(userid));
room_list_->setCache(cache_);
+ text_input_->setCache(cache_);
try {
cache_->setup();
@@ -467,7 +504,6 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token)
}
} catch (const lmdb::error &e) {
qCritical() << "Cache failure" << e.what();
- cache_->unmount();
cache_->deleteData();
qInfo() << "Falling back to initial sync ...";
}
@@ -482,25 +518,28 @@ ChatPage::syncCompleted(const mtx::responses::Sync &response)
{
syncTimeoutTimer_->stop();
- updateJoinedRooms(response.rooms.join);
- removeLeftRooms(response.rooms.leave);
-
- const auto nextBatchToken = QString::fromStdString(response.next_batch);
-
- auto stateDiff = generateMembershipDifference(response.rooms.join, roomStates_);
- QtConcurrent::run(cache_.data(), &Cache::setState, nextBatchToken, stateDiff);
- QtConcurrent::run(cache_.data(), &Cache::setInvites, response.rooms.invite);
+ // Process ephemeral data per room.
+ for (const auto &room : response.rooms.join) {
+ auto room_id = QString::fromStdString(room.first);
- room_list_->sync(roomStates_, roomSettings_);
- room_list_->syncInvites(response.rooms.invite);
- trackInvites(response.rooms.invite);
-
- view_manager_->sync(response.rooms);
+ updateTypingUsers(room_id, room.second.ephemeral.typing);
+ updateRoomNotificationCount(room_id,
+ room.second.unread_notifications.notification_count);
+ }
- client_->setNextBatchToken(nextBatchToken);
- client_->sync();
+ QtConcurrent::run([this, res = std::move(response)]() {
+ try {
+ cache_->saveState(res);
+ emit syncRoomlist(cache_->roomUpdates(res));
+ } catch (const lmdb::error &e) {
+ std::cout << "save cache error:" << e.what() << '\n';
+ // TODO: retry sync.
+ return;
+ }
- syncTimeoutTimer_->start(SYNC_RETRY_TIMEOUT);
+ emit syncUI(std::move(res.rooms));
+ emit continueSync(cache_->nextBatchToken());
+ });
}
void
@@ -508,57 +547,22 @@ ChatPage::initialSyncCompleted(const mtx::responses::Sync &response)
{
initialSyncTimer_->stop();
- auto joined = response.rooms.join;
-
- for (auto it = joined.cbegin(); it != joined.cend(); ++it) {
- auto roomState = QSharedPointer<RoomState>(new RoomState);
-
- // Build the current state from the timeline and state events.
- roomState->updateFromEvents(it->second.state.events);
- roomState->updateFromEvents(it->second.timeline.events);
-
- // Remove redundant memberships.
- roomState->removeLeaveMemberships();
-
- // Resolve room name and avatar. e.g in case of one-to-one chats.
- roomState->resolveName();
- roomState->resolveAvatar();
+ qDebug() << "initial sync completed";
- const auto room_id = QString::fromStdString(it->first);
-
- roomStates_.emplace(room_id, roomState);
- roomSettings_.emplace(room_id,
- QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
-
- for (const auto &membership : roomState->memberships) {
- updateUserDisplayName(membership.second);
- updateUserAvatarUrl(membership.second);
+ QtConcurrent::run([this, res = std::move(response)]() {
+ try {
+ cache_->saveState(res);
+ emit initializeRoomList(cache_->roomInfo());
+ } catch (const lmdb::error &e) {
+ qWarning() << "cache error:" << QString::fromStdString(e.what());
+ emit retryInitialSync();
+ return;
}
- QApplication::processEvents();
- }
-
- QtConcurrent::run(cache_.data(),
- &Cache::setState,
- QString::fromStdString(response.next_batch),
- roomStates_);
- QtConcurrent::run(cache_.data(), &Cache::setInvites, response.rooms.invite);
-
- // Create timelines
- view_manager_->initialize(response.rooms);
-
- // Initialize room list.
- room_list_->setInitialRooms(roomSettings_, roomStates_);
- room_list_->syncInvites(response.rooms.invite);
- trackInvites(response.rooms.invite);
-
- client_->setNextBatchToken(QString::fromStdString(response.next_batch));
- client_->sync();
-
- // Add messages
- view_manager_->sync(response.rooms);
-
- emit contentLoaded();
+ emit initializeViews(std::move(res.rooms));
+ emit continueSync(cache_->nextBatchToken());
+ emit contentLoaded();
+ });
}
void
@@ -613,19 +617,26 @@ ChatPage::updateOwnCommunitiesInfo(const QList<QString> &own_communities)
void
ChatPage::changeTopRoomInfo(const QString &room_id)
{
- if (roomStates_.find(room_id) == roomStates_.end())
- return;
+ try {
+ auto room_info = cache_->getRoomInfo({room_id.toStdString()});
- auto state = roomStates_[room_id];
+ if (room_info.find(room_id) == room_info.end())
+ return;
- top_bar_->updateRoomName(state->getName());
- top_bar_->updateRoomTopic(state->getTopic());
- top_bar_->setRoomSettings(roomSettings_[room_id]);
+ const auto name = QString::fromStdString(room_info[room_id].name);
+ const auto avatar_url = QString::fromStdString(room_info[room_id].avatar_url);
- if (roomAvatars_.find(room_id) != roomAvatars_.end())
- top_bar_->updateRoomAvatar(roomAvatars_[room_id].toImage());
- else
- top_bar_->updateRoomAvatarFromName(state->getName());
+ top_bar_->updateRoomName(name);
+ top_bar_->updateRoomTopic(QString::fromStdString(room_info[room_id].topic));
+
+ if (roomAvatars_.find(room_id) != roomAvatars_.end())
+ top_bar_->updateRoomAvatar(roomAvatars_[room_id].toImage());
+ else
+ top_bar_->updateRoomAvatarFromName(name);
+ } catch (const lmdb::error &e) {
+ qWarning() << "failed to change top bar room info"
+ << QString::fromStdString(e.what());
+ }
current_room_ = room_id;
}
@@ -645,64 +656,26 @@ ChatPage::showUnreadMessageNotification(int count)
void
ChatPage::loadStateFromCache()
{
- qDebug() << "Restoring state from cache";
-
- qDebug() << "Restored nextBatchToken" << cache_->nextBatchToken();
- client_->setNextBatchToken(cache_->nextBatchToken());
+ qDebug() << "restoring state from cache";
- qRegisterMetaType<std::map<QString, RoomState>>();
+ QtConcurrent::run([this]() {
+ try {
+ cache_->populateMembers();
- QtConcurrent::run(cache_.data(), &Cache::states);
-
- connect(
- cache_.data(), &Cache::statesLoaded, this, [this](std::map<QString, RoomState> rooms) {
- qDebug() << "Cache data loaded";
-
- std::vector<QString> roomKeys;
-
- for (auto const &room : rooms) {
- auto roomState = QSharedPointer<RoomState>(new RoomState(room.second));
-
- // Clean up and prepare state for use.
- roomState->removeLeaveMemberships();
- roomState->resolveName();
- roomState->resolveAvatar();
-
- // Save the current room state.
- roomStates_.emplace(room.first, roomState);
-
- // Create or restore the settings for this room.
- roomSettings_.emplace(
- room.first, QSharedPointer<RoomSettings>(new RoomSettings(room.first)));
-
- // Resolve user avatars.
- for (auto const &membership : roomState->memberships) {
- updateUserDisplayName(membership.second);
- updateUserAvatarUrl(membership.second);
- }
-
- roomKeys.emplace_back(room.first);
- }
-
- // Initializing empty timelines.
- view_manager_->initialize(roomKeys);
-
- // Initialize room list from the restored state and settings.
- room_list_->setInitialRooms(roomSettings_, roomStates_);
-
- const auto invites = cache_->invites();
- room_list_->syncInvites(invites);
- trackInvites(invites);
-
- // Check periodically if the timelines have been loaded.
- consensusTimer_->start(CONSENSUS_TIMEOUT);
+ emit initializeRoomList(cache_->roomInfo());
+ emit initializeEmptyViews(cache_->joinedRooms());
+ } catch (const lmdb::error &e) {
+ std::cout << "load cache error:" << e.what() << '\n';
+ // TODO Clear cache and restart.
+ return;
+ }
- // Show the content if consensus can't be achieved.
- showContentTimer_->start(SHOW_CONTENT_TIMEOUT);
+ // Start receiving events.
+ emit continueSync(cache_->nextBatchToken());
- // Start receiving events.
- client_->sync();
- });
+ // Check periodically if the timelines have been loaded.
+ emit startConsesusTimer();
+ });
}
void
@@ -734,69 +707,30 @@ ChatPage::showQuickSwitcher()
std::map<QString, QString> rooms;
- for (auto const &state : roomStates_) {
- QString deambiguator =
- QString::fromStdString(state.second->canonical_alias.content.alias);
- if (deambiguator == "")
- deambiguator = state.first;
- rooms.emplace(state.second->getName() + " (" + deambiguator + ")", state.first);
- }
-
- quickSwitcher_->setRoomList(rooms);
- quickSwitcherModal_->show();
-}
-
-void
-ChatPage::addRoom(const QString &room_id)
-{
- if (roomStates_.find(room_id) == roomStates_.end()) {
- auto room_state = QSharedPointer<RoomState>(new RoomState);
-
- roomStates_.emplace(room_id, room_state);
- roomSettings_.emplace(room_id,
- QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
-
- room_list_->addRoom(roomSettings_[room_id], roomStates_[room_id], room_id);
- room_list_->highlightSelectedRoom(room_id);
-
- changeTopRoomInfo(room_id);
- }
-}
-
-void
-ChatPage::removeRoom(const QString &room_id)
-{
- roomStates_.erase(room_id);
- roomSettings_.erase(room_id);
-
try {
- cache_->removeRoom(room_id);
- cache_->removeInvite(room_id);
+ auto info = cache_->roomInfo();
+ for (auto it = info.begin(); it != info.end(); ++it)
+ rooms.emplace(QString::fromStdString(it.value().name), it.key());
+ quickSwitcher_->setRoomList(rooms);
+ quickSwitcherModal_->show();
} catch (const lmdb::error &e) {
- qCritical() << "The cache couldn't be updated: " << e.what();
- // TODO: Notify the user.
- cache_->unmount();
- cache_->deleteData();
+ const auto err = QString::fromStdString(e.what());
+ emit showNotification(QString("Failed to load room list: %1").arg(err));
}
-
- room_list_->removeRoom(room_id, room_id == current_room_);
- roomInvites_.erase(room_id);
}
void
-ChatPage::removeInvite(const QString &room_id)
+ChatPage::removeRoom(const QString &room_id)
{
try {
- cache_->removeInvite(room_id);
+ cache_->removeRoom(room_id);
+ cache_->removeInvite(room_id.toStdString());
} catch (const lmdb::error &e) {
qCritical() << "The cache couldn't be updated: " << e.what();
// TODO: Notify the user.
- cache_->unmount();
- cache_->deleteData();
}
room_list_->removeRoom(room_id, room_id == current_room_);
- roomInvites_.erase(room_id);
}
void
@@ -821,7 +755,7 @@ ChatPage::updateTypingUsers(const QString &roomid, const std::vector<std::string
if (user == user_id)
continue;
- users.append(TimelineViewManager::displayName(user));
+ users.append(Cache::displayName(roomid, user));
}
users.sort();
@@ -834,135 +768,15 @@ ChatPage::updateTypingUsers(const QString &roomid, const std::vector<std::string
}
void
-ChatPage::updateUserAvatarUrl(const mtx::events::StateEvent<mtx::events::state::Member> &membership)
-{
- auto uid = QString::fromStdString(membership.state_key);
- auto url = QString::fromStdString(membership.content.avatar_url);
-
- if (!url.isEmpty())
- AvatarProvider::setAvatarUrl(uid, url);
-}
-
-void
-ChatPage::updateUserDisplayName(
- const mtx::events::StateEvent<mtx::events::state::Member> &membership)
-{
- auto displayName = QString::fromStdString(membership.content.display_name);
- auto stateKey = QString::fromStdString(membership.state_key);
-
- if (!displayName.isEmpty())
- TimelineViewManager::DISPLAY_NAMES.emplace(stateKey, displayName);
-}
-
-void
ChatPage::removeLeftRooms(const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
const auto room_id = QString::fromStdString(it->first);
-
- if (roomStates_.find(room_id) != roomStates_.end())
- removeRoom(room_id);
-
- if (roomInvites_.find(room_id) != roomInvites_.end())
- removeInvite(room_id);
+ room_list_->removeRoom(room_id, room_id == current_room_);
}
}
void
-ChatPage::updateJoinedRooms(const std::map<std::string, mtx::responses::JoinedRoom> &rooms)
-{
- for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
- const auto roomid = QString::fromStdString(it->first);
-
- if (roomInvites_.find(roomid) != roomInvites_.end())
- removeInvite(roomid);
-
- updateTypingUsers(roomid, it->second.ephemeral.typing);
- updateRoomNotificationCount(roomid,
- it->second.unread_notifications.notification_count);
-
- if (it->second.ephemeral.receipts.size() > 0)
- QtConcurrent::run(cache_.data(),
- &Cache::updateReadReceipt,
- it->first,
- it->second.ephemeral.receipts);
-
- const auto newStateEvents = it->second.state;
- const auto newTimelineEvents = it->second.timeline;
-
- // Merge the new updates for rooms that we are tracking.
- if (roomStates_.find(roomid) != roomStates_.end()) {
- auto oldState = roomStates_[roomid];
- oldState->updateFromEvents(newStateEvents.events);
- oldState->updateFromEvents(newTimelineEvents.events);
- oldState->resolveName();
- oldState->resolveAvatar();
- } else {
- // Build the current state from the timeline and state events.
- auto roomState = QSharedPointer<RoomState>(new RoomState);
- roomState->updateFromEvents(newStateEvents.events);
- roomState->updateFromEvents(newTimelineEvents.events);
-
- // Resolve room name and avatar. e.g in case of one-to-one chats.
- roomState->resolveName();
- roomState->resolveAvatar();
-
- roomStates_.emplace(roomid, roomState);
-
- roomSettings_.emplace(
- roomid, QSharedPointer<RoomSettings>(new RoomSettings(roomid)));
-
- view_manager_->addRoom(it->second, roomid);
- }
-
- updateUserMetadata(newStateEvents.events);
- updateUserMetadata(newTimelineEvents.events);
-
- if (roomid == current_room_)
- changeTopRoomInfo(roomid);
-
- QApplication::processEvents();
- }
-}
-
-std::map<QString, QSharedPointer<RoomState>>
-ChatPage::generateMembershipDifference(
- const std::map<std::string, mtx::responses::JoinedRoom> &rooms,
- const std::map<QString, QSharedPointer<RoomState>> &states) const
-{
- std::map<QString, QSharedPointer<RoomState>> stateDiff;
-
- for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
- const auto room_id = QString::fromStdString(it->first);
-
- if (states.find(room_id) == states.end())
- continue;
-
- auto all_memberships = getMemberships(it->second.state.events);
- auto timelineMemberships = getMemberships(it->second.timeline.events);
-
- // We have to process first the state events and then the timeline.
- for (auto mm = timelineMemberships.cbegin(); mm != timelineMemberships.cend(); ++mm)
- all_memberships.emplace(mm->first, mm->second);
-
- auto local = QSharedPointer<RoomState>(new RoomState);
- local->aliases = states.at(room_id)->aliases;
- local->avatar = states.at(room_id)->avatar;
- local->canonical_alias = states.at(room_id)->canonical_alias;
- local->history_visibility = states.at(room_id)->history_visibility;
- local->join_rules = states.at(room_id)->join_rules;
- local->name = states.at(room_id)->name;
- local->power_levels = states.at(room_id)->power_levels;
- local->topic = states.at(room_id)->topic;
- local->memberships = all_memberships;
-
- stateDiff.emplace(room_id, local);
- }
-
- return stateDiff;
-}
-
-void
ChatPage::showReadReceipts(const QString &event_id)
{
if (receiptsDialog_.isNull()) {
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index e7d04ebb..17a34d96 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -28,6 +28,7 @@
#include <QProcessEnvironment>
#include <QSettings>
#include <QUrlQuery>
+#include <QtConcurrent>
#include <mtx/errors.hpp>
#include "Deserializable.h"
@@ -438,16 +439,15 @@ MatrixClient::initialSync() noexcept
return;
}
- auto data = reply->readAll();
-
- try {
- mtx::responses::Sync response = nlohmann::json::parse(data);
- emit initialSyncCompleted(response);
- } catch (std::exception &e) {
- qWarning() << "Initial sync error:" << e.what();
- emit initialSyncFailed();
- return;
- }
+ qRegisterMetaType<mtx::responses::Sync>();
+ QtConcurrent::run([data = reply->readAll(), this]() {
+ try {
+ emit initialSyncCompleted(nlohmann::json::parse(std::move(data)));
+ } catch (std::exception &e) {
+ qWarning() << "Initial sync error:" << e.what();
+ emit initialSyncFailed();
+ }
+ });
});
}
@@ -730,10 +730,8 @@ MatrixClient::fetchUserAvatar(const QUrl &avatarUrl)
{
QList<QString> url_parts = avatarUrl.toString().split("mxc://");
- if (url_parts.size() != 2) {
- qDebug() << "Invalid format for user avatar:" << avatarUrl.toString();
+ if (url_parts.size() != 2)
return QSharedPointer<DownloadMediaProxy>();
- }
QUrlQuery query;
query.addQueryItem("width", "128");
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc
index 0c1a89db..981908e2 100644
--- a/src/RoomInfoListItem.cc
+++ b/src/RoomInfoListItem.cc
@@ -22,12 +22,12 @@
#include <variant.hpp>
+#include "Cache.h"
#include "Config.h"
#include "Menu.h"
#include "Ripple.h"
#include "RippleOverlay.h"
#include "RoomInfoListItem.h"
-#include "RoomSettings.h"
#include "Theme.h"
#include "Utils.h"
@@ -73,15 +73,20 @@ RoomInfoListItem::init(QWidget *parent)
headingFont_ = font_;
headingFont_.setPixelSize(conf::roomlist::fonts::heading);
headingFont_.setWeight(60);
+
+ menu_ = new Menu(this);
+ leaveRoom_ = new QAction(tr("Leave room"), this);
+ connect(leaveRoom_, &QAction::triggered, this, [this]() { emit leaveRoom(roomId_); });
+ menu_->addAction(leaveRoom_);
}
-RoomInfoListItem::RoomInfoListItem(QString room_id,
- mtx::responses::InvitedRoom room,
- QWidget *parent)
+RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent)
: QWidget(parent)
- , roomType_{RoomType::Invited}
- , invitedRoom_{std::move(room)}
- , roomId_{std::move(room_id)}
+ , roomType_{info.is_invite ? RoomType::Invited : RoomType::Joined}
+ , roomId_(std::move(room_id))
+ , roomName_{QString::fromStdString(std::move(info.name))}
+ , isPressed_(false)
+ , unreadMsgCount_(0)
{
init(parent);
@@ -91,47 +96,8 @@ RoomInfoListItem::RoomInfoListItem(QString room_id,
//
// State events in invited rooms don't contain timestamp info,
// so we can't use them for sorting.
- auto now = QDateTime::currentDateTime();
- lastMsgInfo_ = {"-", "-", "-", "-", now.addYears(10)};
-
- roomAvatar_ = QString::fromStdString(invitedRoom_.avatar());
- roomName_ = QString::fromStdString(invitedRoom_.name());
-}
-
-RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
- QSharedPointer<RoomState> state,
- QString room_id,
- QWidget *parent)
- : QWidget(parent)
- , state_(state)
- , roomId_(room_id)
- , roomSettings_{settings}
- , isPressed_(false)
- , unreadMsgCount_(0)
-{
- init(parent);
-
- menu_ = new Menu(this);
-
- toggleNotifications_ = new QAction(notificationText(), this);
- connect(toggleNotifications_, &QAction::triggered, this, [this]() {
- roomSettings_->toggleNotifications();
- });
-
- leaveRoom_ = new QAction(tr("Leave room"), this);
- connect(leaveRoom_, &QAction::triggered, this, [this]() { emit leaveRoom(roomId_); });
-
- menu_->addAction(toggleNotifications_);
- menu_->addAction(leaveRoom_);
-}
-
-QString
-RoomInfoListItem::notificationText()
-{
- if (roomSettings_.isNull() || roomSettings_->isNotificationsEnabled())
- return QString(tr("Disable notifications"));
-
- return tr("Enable notifications");
+ if (roomType_ == RoomType::Invited)
+ lastMsgInfo_ = {"-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
}
void
@@ -352,7 +318,6 @@ RoomInfoListItem::contextMenuEvent(QContextMenuEvent *event)
if (roomType_ == RoomType::Invited)
return;
- toggleNotifications_->setText(notificationText());
menu_->popup(event->globalPos());
}
diff --git a/src/RoomList.cc b/src/RoomList.cc
index 866d28ae..7d17585c 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -26,7 +26,6 @@
#include "OverlayModal.h"
#include "RoomInfoListItem.h"
#include "RoomList.h"
-#include "RoomSettings.h"
#include "RoomState.h"
#include "UserSettingsPage.h"
@@ -74,17 +73,11 @@ RoomList::RoomList(QSharedPointer<MatrixClient> client,
}
void
-RoomList::clear()
+RoomList::addRoom(const QString &room_id, const RoomInfo &info)
{
- rooms_.clear();
-}
+ auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
+ room_item->setRoomName(QString::fromStdString(std::move(info.name)));
-void
-RoomList::addRoom(const QSharedPointer<RoomSettings> &settings,
- const QSharedPointer<RoomState> &state,
- const QString &room_id)
-{
- auto room_item = new RoomInfoListItem(settings, state, room_id, scrollArea_);
connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
connect(room_item, &RoomInfoListItem::leaveRoom, this, [](const QString &room_id) {
MainWindow::instance()->openLeaveRoomDialog(room_id);
@@ -92,8 +85,8 @@ RoomList::addRoom(const QSharedPointer<RoomSettings> &settings,
rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item));
- if (!state->getAvatar().toString().isEmpty())
- updateAvatar(room_id, state->getAvatar().toString());
+ if (!info.avatar_url.empty())
+ updateAvatar(room_id, QString::fromStdString(info.avatar_url));
int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item);
@@ -164,20 +157,19 @@ RoomList::calculateUnreadMessageCount()
}
void
-RoomList::setInitialRooms(const std::map<QString, QSharedPointer<RoomSettings>> &settings,
- const std::map<QString, QSharedPointer<RoomState>> &states)
+RoomList::initialize(const QMap<QString, RoomInfo> &info)
{
+ qDebug() << "initialize room list";
+
rooms_.clear();
- if (settings.size() != states.size()) {
- qWarning() << "Initializing room list";
- qWarning() << "Different number of room states and room settings";
- return;
+ for (auto it = info.begin(); it != info.end(); it++) {
+ if (it.value().is_invite)
+ addInvitedRoom(it.key(), it.value());
+ else
+ addRoom(it.key(), it.value());
}
- for (auto const &state : states)
- addRoom(settings.at(state.first), state.second, state.first);
-
if (rooms_.empty())
return;
@@ -190,21 +182,11 @@ RoomList::setInitialRooms(const std::map<QString, QSharedPointer<RoomSettings>>
}
void
-RoomList::sync(const std::map<QString, QSharedPointer<RoomState>> &states,
- const std::map<QString, QSharedPointer<RoomSettings>> &settings)
+RoomList::sync(const std::map<QString, RoomInfo> &info)
{
- for (auto const &state : states) {
- if (!roomExists(state.first))
- addRoom(settings.at(state.first), state.second, state.first);
-
- auto room = rooms_[state.first];
- auto new_avatar = state.second->getAvatar();
-
- updateAvatar(state.first, new_avatar.toString());
-
- room->setState(state.second);
- }
+ for (const auto &room : info)
+ updateRoom(room.first, room.second);
}
void
@@ -368,14 +350,24 @@ RoomList::paintEvent(QPaintEvent *)
}
void
-RoomList::syncInvites(const std::map<std::string, mtx::responses::InvitedRoom> &rooms)
+RoomList::updateRoom(const QString &room_id, const RoomInfo &info)
{
- for (auto it = rooms.cbegin(); it != rooms.cend(); ++it) {
- const auto room_id = QString::fromStdString(it->first);
+ qDebug() << "updateRoom" << QString::fromStdString(info.name) << room_id;
- if (!roomExists(room_id))
- addInvitedRoom(room_id, it->second);
+ if (!roomExists(room_id)) {
+ if (info.is_invite)
+ addInvitedRoom(room_id, info);
+ else
+ addRoom(room_id, info);
+
+ return;
}
+
+ auto room = rooms_[room_id];
+ updateAvatar(room_id, QString::fromStdString(info.avatar_url));
+ room->setRoomName(QString::fromStdString(info.name));
+ room->setRoomType(info.is_invite);
+ room->update();
}
void
@@ -386,15 +378,16 @@ RoomList::setRoomFilter(std::vector<QString> room_ids)
}
void
-RoomList::addInvitedRoom(const QString &room_id, const mtx::responses::InvitedRoom &room)
+RoomList::addInvitedRoom(const QString &room_id, const RoomInfo &info)
{
- auto room_item = new RoomInfoListItem(room_id, room, scrollArea_);
+ auto room_item = new RoomInfoListItem(room_id, info, scrollArea_);
+
connect(room_item, &RoomInfoListItem::acceptInvite, this, &RoomList::acceptInvite);
connect(room_item, &RoomInfoListItem::declineInvite, this, &RoomList::declineInvite);
rooms_.emplace(room_id, QSharedPointer<RoomInfoListItem>(room_item));
- updateAvatar(room_id, QString::fromStdString(room.avatar()));
+ updateAvatar(room_id, QString::fromStdString(info.avatar_url));
int pos = contentsLayout_->count() - 1;
contentsLayout_->insertWidget(pos, room_item);
diff --git a/src/SuggestionsPopup.cpp b/src/SuggestionsPopup.cpp
index 51229806..e7011fcb 100644
--- a/src/SuggestionsPopup.cpp
+++ b/src/SuggestionsPopup.cpp
@@ -1,10 +1,11 @@
#include "Avatar.h"
#include "AvatarProvider.h"
+#include "Cache.h"
+#include "ChatPage.h"
#include "Config.h"
#include "DropShadow.h"
#include "SuggestionsPopup.hpp"
#include "Utils.h"
-#include "timeline/TimelineViewManager.h"
#include <QDebug>
#include <QPaintEvent>
@@ -30,7 +31,7 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
QFont font;
font.setPixelSize(conf::popup::font);
- auto displayName = TimelineViewManager::displayName(user_id);
+ auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), user_id);
avatar_->setSize(conf::popup::avatar);
avatar_->setLetter(utils::firstChar(displayName));
@@ -45,8 +46,10 @@ PopupItem::PopupItem(QWidget *parent, const QString &user_id)
topLayout_->addWidget(avatar_);
topLayout_->addWidget(userName_, 1);
- AvatarProvider::resolve(
- user_id, this, [this](const QImage &img) { avatar_->setImage(img); });
+ AvatarProvider::resolve(ChatPage::instance()->currentRoom(),
+ user_id,
+ this,
+ [this](const QImage &img) { avatar_->setImage(img); });
}
void
@@ -65,7 +68,7 @@ void
PopupItem::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() != Qt::RightButton)
- emit clicked(TimelineViewManager::displayName(user_id_));
+ emit clicked(Cache::displayName(ChatPage::instance()->currentRoom(), user_id_));
QWidget::mousePressEvent(event);
}
@@ -164,7 +167,7 @@ SuggestionsPopup::selectHoveredSuggestion()
return;
const auto &widget = qobject_cast<PopupItem *>(item->widget());
- emit itemSelected(TimelineViewManager::displayName(widget->user()));
+ emit itemSelected(Cache::displayName(ChatPage::instance()->currentRoom(), widget->user()));
resetSelection();
}
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index 4c6c8704..1535f563 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -31,8 +31,9 @@
#include <variant.hpp>
+#include "Cache.h"
+#include "ChatPage.h"
#include "Config.h"
-#include "RoomState.h"
#include "TextInputWidget.h"
#include "Utils.h"
@@ -40,7 +41,6 @@ static constexpr size_t INPUT_HISTORY_SIZE = 127;
static constexpr int MAX_TEXTINPUT_HEIGHT = 120;
static constexpr int InputHeight = 26;
static constexpr int ButtonHeight = 24;
-static constexpr int MaxPopupItems = 5;
FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit{parent}
@@ -454,49 +454,16 @@ TextInputWidget::TextInputWidget(QWidget *parent)
input_->setFixedHeight(textInputHeight);
});
connect(input_, &FilteredTextEdit::showSuggestions, this, [this](const QString &q) {
- if (q.isEmpty() || currState_.isNull())
+ if (q.isEmpty() || cache_.isNull())
return;
QtConcurrent::run([this, q = q.toLower().toStdString()]() {
- std::multimap<int, std::pair<std::string, std::string>> items;
-
- auto get_name = [](auto membership) {
- auto name = membership.second.content.display_name;
- auto key = membership.first;
-
- // Remove the leading '@' character.
- if (name.empty()) {
- key.erase(0, 1);
- name = key;
- }
-
- return std::make_pair(key, name);
- };
-
- for (const auto &m : currState_->memberships) {
- const auto user = get_name(m);
- const int score = utils::levenshtein_distance(q, user.second);
-
- items.emplace(score, user);
+ try {
+ emit input_->resultsRetrieved(cache_->getAutocompleteMatches(
+ ChatPage::instance()->currentRoom().toStdString(), q));
+ } catch (const lmdb::error &e) {
+ std::cout << e.what() << '\n';
}
-
- QVector<SearchResult> results;
- auto end = items.begin();
-
- if (items.size() >= MaxPopupItems)
- std::advance(end, MaxPopupItems);
- else if (items.size() > 0)
- std::advance(end, items.size());
-
- for (auto it = items.begin(); it != end; it++) {
- const auto user = it->second;
-
- results.push_back(
- SearchResult{QString::fromStdString(user.first),
- QString::fromStdString(user.second)});
- }
-
- emit input_->resultsRetrieved(results);
});
});
diff --git a/src/Utils.cc b/src/Utils.cc
index 169be75e..1c053d38 100644
--- a/src/Utils.cc
+++ b/src/Utils.cc
@@ -1,5 +1,5 @@
+#include "Cache.h"
#include "Utils.h"
-#include "timeline/TimelineViewManager.h"
#include <variant.hpp>
@@ -22,7 +22,9 @@ utils::descriptiveTime(const QDateTime &then)
}
DescInfo
-utils::getMessageDescription(const TimelineEvent &event, const QString &localUser)
+utils::getMessageDescription(const TimelineEvent &event,
+ const QString &localUser,
+ const QString &room_id)
{
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
@@ -36,7 +38,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Audio>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@@ -48,7 +50,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
auto msg = mpark::get<Emote>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
const auto body = QString::fromStdString(msg.content.body).trimmed();
@@ -61,7 +63,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<File>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@@ -73,7 +75,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Image>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
@@ -85,7 +87,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Notice>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{
@@ -94,7 +96,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Text>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
const auto body = QString::fromStdString(msg.content.body).trimmed();
@@ -107,7 +109,7 @@ utils::getMessageDescription(const TimelineEvent &event, const QString &localUse
const auto msg = mpark::get<Video>(event);
QString sender = QString::fromStdString(msg.sender);
- const auto username = TimelineViewManager::displayName(sender);
+ const auto username = Cache::displayName(room_id, sender);
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
return DescInfo{sender == localUser ? "You" : username,
diff --git a/src/dialogs/ReadReceipts.cc b/src/dialogs/ReadReceipts.cc
index 63ce68e6..a2e1faf2 100644
--- a/src/dialogs/ReadReceipts.cc
+++ b/src/dialogs/ReadReceipts.cc
@@ -6,17 +6,21 @@
#include <QTimer>
#include <QVBoxLayout>
+#include "ChatPage.h"
#include "Config.h"
#include "Utils.h"
#include "Avatar.h"
#include "AvatarProvider.h"
+#include "Cache.h"
#include "dialogs/ReadReceipts.h"
-#include "timeline/TimelineViewManager.h"
using namespace dialogs;
-ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t timestamp)
+ReceiptItem::ReceiptItem(QWidget *parent,
+ const QString &user_id,
+ uint64_t timestamp,
+ const QString &room_id)
: QWidget(parent)
{
topLayout_ = new QHBoxLayout(this);
@@ -29,7 +33,7 @@ ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t times
QFont font;
font.setPixelSize(conf::receipts::font);
- auto displayName = TimelineViewManager::displayName(user_id);
+ auto displayName = Cache::displayName(room_id, user_id);
avatar_ = new Avatar(this);
avatar_->setSize(40);
@@ -51,8 +55,10 @@ ReceiptItem::ReceiptItem(QWidget *parent, const QString &user_id, uint64_t times
topLayout_->addWidget(avatar_);
topLayout_->addLayout(textLayout_, 1);
- AvatarProvider::resolve(
- user_id, this, [this](const QImage &img) { avatar_->setImage(img); });
+ AvatarProvider::resolve(ChatPage::instance()->currentRoom(),
+ user_id,
+ this,
+ [this](const QImage &img) { avatar_->setImage(img); });
}
QString
@@ -104,8 +110,10 @@ ReadReceipts::addUsers(const std::multimap<uint64_t, std::string, std::greater<u
userList_->clear();
for (const auto &receipt : receipts) {
- auto user =
- new ReceiptItem(this, QString::fromStdString(receipt.second), receipt.first);
+ auto user = new ReceiptItem(this,
+ QString::fromStdString(receipt.second),
+ receipt.first,
+ ChatPage::instance()->currentRoom());
auto item = new QListWidgetItem(userList_);
item->setSizeHint(user->minimumSizeHint());
diff --git a/src/timeline/TimelineItem.cc b/src/timeline/TimelineItem.cc
index 0296c6cd..cd1dac67 100644
--- a/src/timeline/TimelineItem.cc
+++ b/src/timeline/TimelineItem.cc
@@ -99,12 +99,14 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
const QString &userid,
QString body,
bool withSender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
init();
- auto displayName = TimelineViewManager::displayName(userid);
+ auto displayName = Cache::displayName(room_id_, userid);
auto timestamp = QDateTime::currentDateTime();
if (ty == mtx::events::MessageType::Emote) {
@@ -127,7 +129,7 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
- userid, this, [this](const QImage &img) { setUserAvatar(img); });
+ room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@@ -143,8 +145,10 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
TimelineItem::TimelineItem(ImageItem *image,
const QString &userid,
bool withSender,
+ const QString &room_id,
QWidget *parent)
: QWidget{parent}
+ , room_id_{room_id}
{
init();
@@ -153,8 +157,13 @@ TimelineItem::TimelineItem(ImageItem *image,
addSaveImageAction(image);
}
-TimelineItem::TimelineItem(FileItem *file, const QString &userid, bool withSender, QWidget *parent)
+TimelineItem::TimelineItem(FileItem *file,
+ const QString &userid,
+ bool withSender,
+ const QString &room_id,
+ QWidget *parent)
: QWidget{parent}
+ , room_id_{room_id}
{
init();
@@ -164,8 +173,10 @@ TimelineItem::TimelineItem(FileItem *file, const QString &userid, bool withSende
TimelineItem::TimelineItem(AudioItem *audio,
const QString &userid,
bool withSender,
+ const QString &room_id,
QWidget *parent)
: QWidget{parent}
+ , room_id_{room_id}
{
init();
@@ -175,8 +186,10 @@ TimelineItem::TimelineItem(AudioItem *audio,
TimelineItem::TimelineItem(VideoItem *video,
const QString &userid,
bool withSender,
+ const QString &room_id,
QWidget *parent)
: QWidget{parent}
+ , room_id_{room_id}
{
init();
@@ -186,8 +199,10 @@ TimelineItem::TimelineItem(VideoItem *video,
TimelineItem::TimelineItem(ImageItem *image,
const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
image, event, " sent an image", with_sender);
@@ -198,8 +213,10 @@ TimelineItem::TimelineItem(ImageItem *image,
TimelineItem::TimelineItem(FileItem *file,
const mtx::events::RoomEvent<mtx::events::msg::File> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
file, event, " sent a file", with_sender);
@@ -208,8 +225,10 @@ TimelineItem::TimelineItem(FileItem *file,
TimelineItem::TimelineItem(AudioItem *audio,
const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
audio, event, " sent an audio clip", with_sender);
@@ -218,8 +237,10 @@ TimelineItem::TimelineItem(AudioItem *audio,
TimelineItem::TimelineItem(VideoItem *video,
const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
video, event, " sent a video clip", with_sender);
@@ -230,8 +251,10 @@ TimelineItem::TimelineItem(VideoItem *video,
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
init();
@@ -240,7 +263,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
const auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
- descriptionMsg_ = {TimelineViewManager::displayName(sender),
+ descriptionMsg_ = {Cache::displayName(room_id_, sender),
sender,
" sent a notification",
utils::descriptiveTime(timestamp),
@@ -253,7 +276,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
body = "<i>" + body + "</i>";
if (with_sender) {
- auto displayName = TimelineViewManager::displayName(sender);
+ auto displayName = Cache::displayName(room_id_, sender);
generateBody(displayName, body);
setupAvatarLayout(displayName);
@@ -261,7 +284,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
- sender, this, [this](const QImage &img) { setUserAvatar(img); });
+ room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@@ -279,8 +302,10 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
init();
@@ -289,7 +314,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
auto body = QString::fromStdString(event.content.body).trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
- auto displayName = TimelineViewManager::displayName(sender);
+ auto displayName = Cache::displayName(room_id_, sender);
auto emoteMsg = QString("* %1 %2").arg(displayName).arg(body);
descriptionMsg_ = {"", sender, emoteMsg, utils::descriptiveTime(timestamp), timestamp};
@@ -306,7 +331,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
- sender, this, [this](const QImage &img) { setUserAvatar(img); });
+ room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(emoteMsg);
setupSimpleLayout();
@@ -324,8 +349,10 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
*/
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &event,
bool with_sender,
+ const QString &room_id,
QWidget *parent)
: QWidget(parent)
+ , room_id_{room_id}
{
init();
@@ -334,7 +361,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
auto body = QString::fromStdString(event.content.body).trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
- auto displayName = TimelineViewManager::displayName(sender);
+ auto displayName = Cache::displayName(room_id_, sender);
QSettings settings;
descriptionMsg_ = {sender == settings.value("auth/user_id") ? "You" : displayName,
@@ -356,7 +383,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
- sender, this, [this](const QImage &img) { setUserAvatar(img); });
+ room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
@@ -532,7 +559,7 @@ TimelineItem::addAvatar()
// TODO: should be replaced with the proper event struct.
auto userid = descriptionMsg_.userid;
- auto displayName = TimelineViewManager::displayName(userid);
+ auto displayName = Cache::displayName(room_id_, userid);
QFontMetrics fm(usernameFont_);
userName_ = new QLabel(this);
@@ -566,5 +593,6 @@ TimelineItem::addAvatar()
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
- AvatarProvider::resolve(userid, this, [this](const QImage &img) { setUserAvatar(img); });
+ AvatarProvider::resolve(
+ room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
}
diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc
index d18c1cff..f7194265 100644
--- a/src/timeline/TimelineView.cc
+++ b/src/timeline/TimelineView.cc
@@ -477,8 +477,7 @@ TimelineView::addUserMessage(mtx::events::MessageType ty, const QString &body)
auto with_sender = lastSender_ != local_user_;
TimelineItem *view_item =
- new TimelineItem(ty, local_user_, body, with_sender, scroll_widget_);
- view_item->setRoomId(room_id_);
+ new TimelineItem(ty, local_user_, body, with_sender, room_id_, scroll_widget_);
addTimelineItem(view_item);
@@ -538,7 +537,7 @@ TimelineView::notifyForLastEvent()
void
TimelineView::notifyForLastEvent(const TimelineEvent &event)
{
- auto descInfo = utils::getMessageDescription(event, local_user_);
+ auto descInfo = utils::getMessageDescription(event, local_user_, room_id_);
if (!descInfo.timestamp.isEmpty())
emit updateLastTimelineMessage(room_id_, descInfo);
diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc
index 55f25dfc..28f44770 100644
--- a/src/timeline/TimelineViewManager.cc
+++ b/src/timeline/TimelineViewManager.cc
@@ -172,18 +172,23 @@ TimelineViewManager::initialize(const mtx::responses::Rooms &rooms)
for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
addRoom(it->second, QString::fromStdString(it->first));
}
+
+ sync(rooms);
}
void
-TimelineViewManager::initialize(const std::vector<QString> &rooms)
+TimelineViewManager::initialize(const std::vector<std::string> &rooms)
{
for (const auto &roomid : rooms)
- addRoom(roomid);
+ addRoom(QString::fromStdString(roomid));
}
void
TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id)
{
+ if (timelineViewExists(room_id))
+ return;
+
// Create a history view with the room events.
TimelineView *view = new TimelineView(room.timeline, client_, room_id);
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
@@ -200,6 +205,9 @@ TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QStri
void
TimelineViewManager::addRoom(const QString &room_id)
{
+ if (timelineViewExists(room_id))
+ return;
+
// Create a history view without any events.
TimelineView *view = new TimelineView(client_, room_id);
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
@@ -247,8 +255,6 @@ TimelineViewManager::setHistoryView(const QString &room_id)
view->scrollDown();
}
-std::map<QString, QString> TimelineViewManager::DISPLAY_NAMES;
-
QString
TimelineViewManager::chooseRandomColor()
{
@@ -307,15 +313,6 @@ TimelineViewManager::chooseRandomColor()
return color.name();
}
-QString
-TimelineViewManager::displayName(const QString &userid)
-{
- if (DISPLAY_NAMES.find(userid) != DISPLAY_NAMES.end())
- return DISPLAY_NAMES.at(userid);
-
- return userid;
-}
-
bool
TimelineViewManager::hasLoaded() const
{
|