From 7fbfe3af153e37227087771389cf4068ff9a1fa4 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Sat, 26 Aug 2017 13:49:16 +0300 Subject: [PATCH] Update the cache state at once to avoid being in an invalid state --- include/Cache.h | 32 +- include/ChatPage.h | 105 ++++--- include/RoomState.h | 65 ++-- src/Cache.cc | 269 ++++++++-------- src/ChatPage.cc | 725 +++++++++++++++++++------------------------- src/RoomState.cc | 479 +++++++++++++++++------------ 6 files changed, 844 insertions(+), 831 deletions(-) diff --git a/include/Cache.h b/include/Cache.h index a64f0514..46107062 100644 --- a/include/Cache.h +++ b/include/Cache.h @@ -24,36 +24,38 @@ class Cache { public: - Cache(const QString &userId); + Cache(const QString &userId); - void insertRoomState(const QString &roomid, const RoomState &state); - void setNextBatchToken(const QString &token); - bool isInitialized(); + void setState(const QString &nextBatchToken, const QMap &states); + bool isInitialized() const; - QString nextBatchToken(); - QMap states(); + QString nextBatchToken() const; + QMap states(); - inline void unmount(); - inline QString memberDbName(const QString &roomid); + inline void unmount(); + inline QString memberDbName(const QString &roomid); private: - lmdb::env env_; - lmdb::dbi stateDb_; - lmdb::dbi roomDb_; + void setNextBatchToken(lmdb::txn &txn, const QString &token); + void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state); - bool isMounted_; + lmdb::env env_; + lmdb::dbi stateDb_; + lmdb::dbi roomDb_; - QString userId_; + bool isMounted_; + + QString userId_; }; inline void Cache::unmount() { - isMounted_ = false; + isMounted_ = false; } inline QString Cache::memberDbName(const QString &roomid) { - return QString("m.%1").arg(roomid); + return QString("m.%1").arg(roomid); } diff --git a/include/ChatPage.h b/include/ChatPage.h index 13d6c8e9..0ea7ea38 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -35,80 +35,79 @@ class ChatPage : public QWidget { - Q_OBJECT + Q_OBJECT public: - ChatPage(QSharedPointer client, QWidget *parent = 0); - ~ChatPage(); + ChatPage(QSharedPointer client, QWidget *parent = 0); + ~ChatPage(); - // Initialize all the components of the UI. - void bootstrap(QString userid, QString homeserver, QString token); + // Initialize all the components of the UI. + void bootstrap(QString userid, QString homeserver, QString token); signals: - void contentLoaded(); - void close(); - void changeWindowTitle(const QString &msg); - void unreadMessages(int count); + void contentLoaded(); + void close(); + void changeWindowTitle(const QString &msg); + void unreadMessages(int count); private slots: - void showUnreadMessageNotification(int count); - void updateTopBarAvatar(const QString &roomid, const QPixmap &img); - void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name); - void setOwnAvatar(const QPixmap &img); - void initialSyncCompleted(const SyncResponse &response); - void syncCompleted(const SyncResponse &response); - void syncFailed(const QString &msg); - void changeTopRoomInfo(const QString &room_id); - void startSync(); - void logout(); + void showUnreadMessageNotification(int count); + void updateTopBarAvatar(const QString &roomid, const QPixmap &img); + void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name); + void setOwnAvatar(const QPixmap &img); + void initialSyncCompleted(const SyncResponse &response); + void syncCompleted(const SyncResponse &response); + void syncFailed(const QString &msg); + void changeTopRoomInfo(const QString &room_id); + void startSync(); + void logout(); protected: - void keyPressEvent(QKeyEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; private: - void updateDisplayNames(const RoomState &state); - void updateRoomState(RoomState &room_state, const QJsonArray &events); - void loadStateFromCache(); - void showQuickSwitcher(); + void updateDisplayNames(const RoomState &state); + void loadStateFromCache(); + void showQuickSwitcher(); - QHBoxLayout *topLayout_; - Splitter *splitter; + QHBoxLayout *topLayout_; + Splitter *splitter; - QWidget *sideBar_; - QVBoxLayout *sideBarLayout_; - QVBoxLayout *sideBarTopLayout_; - QVBoxLayout *sideBarMainLayout_; - QWidget *sideBarTopWidget_; - QVBoxLayout *sideBarTopWidgetLayout_; + QWidget *sideBar_; + QVBoxLayout *sideBarLayout_; + QVBoxLayout *sideBarTopLayout_; + QVBoxLayout *sideBarMainLayout_; + QWidget *sideBarTopWidget_; + QVBoxLayout *sideBarTopWidgetLayout_; - QWidget *content_; - QVBoxLayout *contentLayout_; - QHBoxLayout *topBarLayout_; - QVBoxLayout *mainContentLayout_; + QWidget *content_; + QVBoxLayout *contentLayout_; + QHBoxLayout *topBarLayout_; + QVBoxLayout *mainContentLayout_; - RoomList *room_list_; - TimelineViewManager *view_manager_; + RoomList *room_list_; + TimelineViewManager *view_manager_; - TopRoomBar *top_bar_; - TextInputWidget *text_input_; + TopRoomBar *top_bar_; + TextInputWidget *text_input_; - QTimer *sync_timer_; - int sync_interval_; + QTimer *sync_timer_; + int sync_interval_; - QString current_room_; - QMap room_avatars_; + QString current_room_; + QMap room_avatars_; - UserInfoWidget *user_info_widget_; + UserInfoWidget *user_info_widget_; - QMap state_manager_; - QMap> settingsManager_; + QMap state_manager_; + QMap> settingsManager_; - QuickSwitcher *quickSwitcher_ = nullptr; - OverlayModal *quickSwitcherModal_ = nullptr; + QuickSwitcher *quickSwitcher_ = nullptr; + OverlayModal *quickSwitcherModal_ = nullptr; - // Matrix Client API provider. - QSharedPointer client_; + // Matrix Client API provider. + QSharedPointer client_; - // LMDB wrapper. - QSharedPointer cache_; + // LMDB wrapper. + QSharedPointer cache_; }; diff --git a/include/RoomState.h b/include/RoomState.h index 536ac4a9..11d298d3 100644 --- a/include/RoomState.h +++ b/include/RoomState.h @@ -41,59 +41,60 @@ namespace events = matrix::events; class RoomState { public: - // Calculate room data that are not immediatly accessible. Like room name and avatar. - // - // e.g If the room is 1-on-1 name and avatar should be extracted from a user. - void resolveName(); - void resolveAvatar(); - void parse(const QJsonObject &object); + // Calculate room data that are not immediatly accessible. Like room name and avatar. + // + // e.g If the room is 1-on-1 name and avatar should be extracted from a user. + void resolveName(); + void resolveAvatar(); + void parse(const QJsonObject &object); - inline QUrl getAvatar() const; - inline QString getName() const; - inline QString getTopic() const; + inline QUrl getAvatar() const; + inline QString getName() const; + inline QString getTopic() const; - void removeLeaveMemberships(); - void update(const RoomState &state); + void removeLeaveMemberships(); + void update(const RoomState &state); + void updateFromEvents(const QJsonArray &events); - QJsonObject serialize() const; + QJsonObject serialize() const; - // The latest state events. - events::StateEvent aliases; - events::StateEvent avatar; - events::StateEvent canonical_alias; - events::StateEvent create; - events::StateEvent history_visibility; - events::StateEvent join_rules; - events::StateEvent name; - events::StateEvent power_levels; - events::StateEvent topic; + // The latest state events. + events::StateEvent aliases; + events::StateEvent avatar; + events::StateEvent canonical_alias; + events::StateEvent create; + events::StateEvent history_visibility; + events::StateEvent join_rules; + events::StateEvent name; + events::StateEvent power_levels; + events::StateEvent topic; - // Contains the m.room.member events for all the joined users. - QMap> memberships; + // Contains the m.room.member events for all the joined users. + QMap> memberships; private: - QUrl avatar_; - QString name_; + QUrl avatar_; + QString name_; - // It defines the user whose avatar is used for the room. If the room has an avatar - // event this should be empty. - QString userAvatar_; + // It defines the user whose avatar is used for the room. If the room has an avatar + // event this should be empty. + QString userAvatar_; }; inline QString RoomState::getTopic() const { - return topic.content().topic().simplified(); + return topic.content().topic().simplified(); } inline QString RoomState::getName() const { - return name_; + return name_; } inline QUrl RoomState::getAvatar() const { - return avatar_; + return avatar_; } diff --git a/src/Cache.cc b/src/Cache.cc index a9699276..01df492c 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -37,194 +37,207 @@ Cache::Cache(const QString &userId) , isMounted_{ false } , userId_{ userId } { - auto statePath = QString("%1/%2/state") - .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) - .arg(QString::fromUtf8(userId_.toUtf8().toHex())); + auto statePath = QString("%1/%2/state") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(QString::fromUtf8(userId_.toUtf8().toHex())); - bool isInitial = !QFile::exists(statePath); + bool isInitial = !QFile::exists(statePath); - env_ = lmdb::env::create(); - env_.set_mapsize(128UL * 1024UL * 1024UL); /* 128 MB */ - env_.set_max_dbs(1024UL); + env_ = lmdb::env::create(); + env_.set_mapsize(128UL * 1024UL * 1024UL); /* 128 MB */ + env_.set_max_dbs(1024UL); - if (isInitial) { - qDebug() << "[cache] First time initializing LMDB"; + if (isInitial) { + qDebug() << "[cache] First time initializing LMDB"; - if (!QDir().mkpath(statePath)) { - throw std::runtime_error( - ("Unable to create state directory:" + statePath).toStdString().c_str()); - } - } + if (!QDir().mkpath(statePath)) { + throw std::runtime_error( + ("Unable to create state directory:" + statePath).toStdString().c_str()); + } + } - try { - env_.open(statePath.toStdString().c_str()); - } catch (const lmdb::error &e) { - if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { - throw std::runtime_error("LMDB initialization failed" + std::string(e.what())); - } + try { + env_.open(statePath.toStdString().c_str()); + } catch (const lmdb::error &e) { + if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { + throw std::runtime_error("LMDB initialization failed" + + std::string(e.what())); + } - qWarning() << "Resetting cache due to LMDB version mismatch:" << e.what(); + qWarning() << "Resetting cache due to LMDB version mismatch:" << e.what(); - QDir stateDir(statePath); + QDir stateDir(statePath); - for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { - if (!stateDir.remove(file)) - throw std::runtime_error(("Unable to delete file " + file).toStdString().c_str()); - } + for (const auto &file : stateDir.entryList(QDir::NoDotAndDotDot)) { + if (!stateDir.remove(file)) + throw std::runtime_error( + ("Unable to delete file " + file).toStdString().c_str()); + } - env_.open(statePath.toStdString().c_str()); - } + env_.open(statePath.toStdString().c_str()); + } - auto txn = lmdb::txn::begin(env_); - stateDb_ = lmdb::dbi::open(txn, "state", MDB_CREATE); - roomDb_ = lmdb::dbi::open(txn, "rooms", MDB_CREATE); + auto txn = lmdb::txn::begin(env_); + stateDb_ = lmdb::dbi::open(txn, "state", MDB_CREATE); + roomDb_ = lmdb::dbi::open(txn, "rooms", MDB_CREATE); - txn.commit(); + txn.commit(); - isMounted_ = true; + isMounted_ = true; } void -Cache::insertRoomState(const QString &roomid, const RoomState &state) +Cache::setState(const QString &nextBatchToken, const QMap &states) { - if (!isMounted_) - return; + if (!isMounted_) + return; - auto txn = lmdb::txn::begin(env_); + auto txn = lmdb::txn::begin(env_); - auto stateEvents = QJsonDocument(state.serialize()).toBinaryData(); - auto id = roomid.toUtf8(); + setNextBatchToken(txn, nextBatchToken); - lmdb::dbi_put(txn, roomDb_, lmdb::val(id.data(), id.size()), lmdb::val(stateEvents.data(), stateEvents.size())); + for (auto it = states.constBegin(); it != states.constEnd(); it++) + insertRoomState(txn, it.key(), it.value()); - for (const auto &membership : state.memberships) { - lmdb::dbi membersDb = lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE); + txn.commit(); +} - // The user_id this membership event relates to, is used - // as the index on the membership database. - auto key = membership.stateKey().toUtf8(); - auto memberEvent = QJsonDocument(membership.serialize()).toBinaryData(); +void +Cache::insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state) +{ + auto stateEvents = QJsonDocument(state.serialize()).toBinaryData(); + auto id = roomid.toUtf8(); - switch (membership.content().membershipState()) { - // We add or update (e.g invite -> join) a new user to the membership list. - case events::Membership::Invite: - case events::Membership::Join: { - lmdb::dbi_put(txn, - membersDb, - lmdb::val(key.data(), key.size()), - lmdb::val(memberEvent.data(), memberEvent.size())); - break; - } - // We remove the user from the membership list. - case events::Membership::Leave: - case events::Membership::Ban: { - lmdb::dbi_del(txn, - membersDb, - lmdb::val(key.data(), key.size()), - lmdb::val(memberEvent.data(), memberEvent.size())); - break; - } - case events::Membership::Knock: { - qWarning() << "Skipping knock membership" << roomid << key; - break; - } - } - } + lmdb::dbi_put(txn, + roomDb_, + lmdb::val(id.data(), id.size()), + lmdb::val(stateEvents.data(), stateEvents.size())); - txn.commit(); + 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.stateKey().toUtf8(); + auto memberEvent = QJsonDocument(membership.serialize()).toBinaryData(); + + switch (membership.content().membershipState()) { + // We add or update (e.g invite -> join) a new user to the membership list. + case events::Membership::Invite: + case events::Membership::Join: { + lmdb::dbi_put(txn, + membersDb, + lmdb::val(key.data(), key.size()), + lmdb::val(memberEvent.data(), memberEvent.size())); + break; + } + // We remove the user from the membership list. + case events::Membership::Leave: + case events::Membership::Ban: { + lmdb::dbi_del(txn, + membersDb, + lmdb::val(key.data(), key.size()), + lmdb::val(memberEvent.data(), memberEvent.size())); + break; + } + case events::Membership::Knock: { + qWarning() << "Skipping knock membership" << roomid << key; + break; + } + } + } } QMap Cache::states() { - QMap states; + QMap states; - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - auto cursor = lmdb::cursor::open(txn, roomDb_); + auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); + auto cursor = lmdb::cursor::open(txn, roomDb_); - std::string room; - std::string stateData; + 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 = QJsonDocument::fromBinaryData(QByteArray(stateData.data(), stateData.size())); + // Retrieve all the room names. + while (cursor.get(room, stateData, MDB_NEXT)) { + auto roomid = QString::fromUtf8(room.data(), room.size()); + auto json = + QJsonDocument::fromBinaryData(QByteArray(stateData.data(), stateData.size())); - RoomState state; - state.parse(json.object()); + RoomState state; + state.parse(json.object()); - auto memberDb = lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE); - QMap> members; + auto memberDb = lmdb::dbi::open(txn, roomid.toStdString().c_str(), MDB_CREATE); + QMap> members; - auto memberCursor = lmdb::cursor::open(txn, memberDb); + auto memberCursor = lmdb::cursor::open(txn, memberDb); - std::string memberId; - std::string memberContent; + std::string memberId; + std::string memberContent; - while (memberCursor.get(memberId, memberContent, MDB_NEXT)) { - auto userid = QString::fromUtf8(memberId.data(), memberId.size()); - auto data = - QJsonDocument::fromBinaryData(QByteArray(memberContent.data(), memberContent.size())); + while (memberCursor.get(memberId, memberContent, MDB_NEXT)) { + auto userid = QString::fromUtf8(memberId.data(), memberId.size()); + auto data = QJsonDocument::fromBinaryData( + QByteArray(memberContent.data(), memberContent.size())); - try { - events::StateEvent member; - member.deserialize(data.object()); - members.insert(userid, member); - } catch (const DeserializationException &e) { - qWarning() << e.what(); - qWarning() << "Fault while parsing member event" << data.object(); - continue; - } - } + try { + events::StateEvent member; + member.deserialize(data.object()); + members.insert(userid, member); + } catch (const DeserializationException &e) { + qWarning() << e.what(); + qWarning() << "Fault while parsing member event" << data.object(); + continue; + } + } - qDebug() << members.size() << "members for" << roomid; + qDebug() << members.size() << "members for" << roomid; - state.memberships = members; - states.insert(roomid, state); - } + state.memberships = members; + states.insert(roomid, state); + } - qDebug() << "Retrieved" << states.size() << "rooms"; + qDebug() << "Retrieved" << states.size() << "rooms"; - cursor.close(); + cursor.close(); - txn.commit(); + txn.commit(); - return states; + return states; } void -Cache::setNextBatchToken(const QString &token) +Cache::setNextBatchToken(lmdb::txn &txn, const QString &token) { - auto txn = lmdb::txn::begin(env_); - auto value = token.toUtf8(); + auto value = token.toUtf8(); - lmdb::dbi_put(txn, stateDb_, NEXT_BATCH_KEY, lmdb::val(value.data(), value.size())); - - txn.commit(); + lmdb::dbi_put(txn, stateDb_, NEXT_BATCH_KEY, lmdb::val(value.data(), value.size())); } bool -Cache::isInitialized() +Cache::isInitialized() const { - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - lmdb::val token; + 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, stateDb_, NEXT_BATCH_KEY, token); - txn.commit(); + txn.commit(); - return res; + return res; } QString -Cache::nextBatchToken() +Cache::nextBatchToken() const { - auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY); - lmdb::val token; + 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, stateDb_, NEXT_BATCH_KEY, token); - txn.commit(); + txn.commit(); - return QString::fromUtf8(token.data(), token.size()); + return QString::fromUtf8(token.data(), token.size()); } diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 1ebbcf91..d199f98c 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -18,26 +18,15 @@ #include #include +#include "AvatarProvider.h" #include "ChatPage.h" +#include "MainWindow.h" #include "Splitter.h" #include "Sync.h" #include "Theme.h" #include "TimelineViewManager.h" #include "UserInfoWidget.h" -#include "AliasesEventContent.h" -#include "AvatarEventContent.h" -#include "AvatarProvider.h" -#include "CanonicalAliasEventContent.h" -#include "CreateEventContent.h" -#include "HistoryVisibilityEventContent.h" -#include "JoinRulesEventContent.h" -#include "MainWindow.h" -#include "MemberEventContent.h" -#include "NameEventContent.h" -#include "PowerLevelsEventContent.h" -#include "TopicEventContent.h" - #include "StateEvent.h" namespace events = matrix::events; @@ -47,569 +36,485 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) , sync_interval_(2000) , client_(client) { - setStyleSheet("background-color: #f8fbfe;"); + setStyleSheet("background-color: #f8fbfe;"); - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(0); + topLayout_ = new QHBoxLayout(this); + topLayout_->setSpacing(0); + topLayout_->setMargin(0); - auto splitter = new Splitter(this); - splitter->setHandleWidth(0); + auto splitter = new Splitter(this); + splitter->setHandleWidth(0); - topLayout_->addWidget(splitter); + topLayout_->addWidget(splitter); - // SideBar - sideBar_ = new QWidget(this); - sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, 0)); - sideBarLayout_ = new QVBoxLayout(sideBar_); - sideBarLayout_->setSpacing(0); - sideBarLayout_->setMargin(0); + // SideBar + sideBar_ = new QWidget(this); + sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, 0)); + sideBarLayout_ = new QVBoxLayout(sideBar_); + sideBarLayout_->setSpacing(0); + sideBarLayout_->setMargin(0); - sideBarTopLayout_ = new QVBoxLayout(); - sideBarTopLayout_->setSpacing(0); - sideBarTopLayout_->setMargin(0); - sideBarMainLayout_ = new QVBoxLayout(); - sideBarMainLayout_->setSpacing(0); - sideBarMainLayout_->setMargin(0); + sideBarTopLayout_ = new QVBoxLayout(); + sideBarTopLayout_->setSpacing(0); + sideBarTopLayout_->setMargin(0); + sideBarMainLayout_ = new QVBoxLayout(); + sideBarMainLayout_->setSpacing(0); + sideBarMainLayout_->setMargin(0); - sideBarLayout_->addLayout(sideBarTopLayout_); - sideBarLayout_->addLayout(sideBarMainLayout_); + sideBarLayout_->addLayout(sideBarTopLayout_); + sideBarLayout_->addLayout(sideBarMainLayout_); - sideBarTopWidget_ = new QWidget(sideBar_); - sideBarTopWidget_->setStyleSheet("background-color: #d6dde3; color: #ebebeb;"); + sideBarTopWidget_ = new QWidget(sideBar_); + sideBarTopWidget_->setStyleSheet("background-color: #d6dde3; color: #ebebeb;"); - sideBarTopLayout_->addWidget(sideBarTopWidget_); + sideBarTopLayout_->addWidget(sideBarTopWidget_); - sideBarTopWidgetLayout_ = new QVBoxLayout(sideBarTopWidget_); - sideBarTopWidgetLayout_->setSpacing(0); - sideBarTopWidgetLayout_->setMargin(0); + sideBarTopWidgetLayout_ = new QVBoxLayout(sideBarTopWidget_); + sideBarTopWidgetLayout_->setSpacing(0); + sideBarTopWidgetLayout_->setMargin(0); - // Content - content_ = new QWidget(this); - contentLayout_ = new QVBoxLayout(content_); - contentLayout_->setSpacing(0); - contentLayout_->setMargin(0); + // Content + content_ = new QWidget(this); + contentLayout_ = new QVBoxLayout(content_); + contentLayout_->setSpacing(0); + contentLayout_->setMargin(0); - topBarLayout_ = new QHBoxLayout(); - topBarLayout_->setSpacing(0); - mainContentLayout_ = new QVBoxLayout(); - mainContentLayout_->setSpacing(0); - mainContentLayout_->setMargin(0); + topBarLayout_ = new QHBoxLayout(); + topBarLayout_->setSpacing(0); + mainContentLayout_ = new QVBoxLayout(); + mainContentLayout_->setSpacing(0); + mainContentLayout_->setMargin(0); - contentLayout_->addLayout(topBarLayout_); - contentLayout_->addLayout(mainContentLayout_); + contentLayout_->addLayout(topBarLayout_); + contentLayout_->addLayout(mainContentLayout_); - // Splitter - splitter->addWidget(sideBar_); - splitter->addWidget(content_); + // Splitter + splitter->addWidget(sideBar_); + splitter->addWidget(content_); - room_list_ = new RoomList(client, sideBar_); - sideBarMainLayout_->addWidget(room_list_); + room_list_ = new RoomList(client, sideBar_); + sideBarMainLayout_->addWidget(room_list_); - top_bar_ = new TopRoomBar(this); - topBarLayout_->addWidget(top_bar_); + top_bar_ = new TopRoomBar(this); + topBarLayout_->addWidget(top_bar_); - view_manager_ = new TimelineViewManager(client, this); - mainContentLayout_->addWidget(view_manager_); + view_manager_ = new TimelineViewManager(client, this); + mainContentLayout_->addWidget(view_manager_); - text_input_ = new TextInputWidget(this); - contentLayout_->addWidget(text_input_); + text_input_ = new TextInputWidget(this); + contentLayout_->addWidget(text_input_); - user_info_widget_ = new UserInfoWidget(sideBarTopWidget_); - sideBarTopWidgetLayout_->addWidget(user_info_widget_); + user_info_widget_ = new UserInfoWidget(sideBarTopWidget_); + sideBarTopWidgetLayout_->addWidget(user_info_widget_); - sync_timer_ = new QTimer(this); - sync_timer_->setSingleShot(true); - connect(sync_timer_, SIGNAL(timeout()), this, SLOT(startSync())); + sync_timer_ = new QTimer(this); + sync_timer_->setSingleShot(true); + connect(sync_timer_, SIGNAL(timeout()), this, SLOT(startSync())); - connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout())); - connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout())); + connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout())); + connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout())); - 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::roomChanged, this, &ChatPage::changeTopRoomInfo); + connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit); + connect( + room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); - connect(view_manager_, &TimelineViewManager::unreadMessages, this, [=](const QString &roomid, int count) { - if (!settingsManager_.contains(roomid)) { - qWarning() << "RoomId does not have settings" << roomid; - room_list_->updateUnreadMessageCount(roomid, count); - return; - } + connect(view_manager_, + &TimelineViewManager::unreadMessages, + this, + [=](const QString &roomid, int count) { + if (!settingsManager_.contains(roomid)) { + qWarning() << "RoomId does not have settings" << roomid; + room_list_->updateUnreadMessageCount(roomid, count); + return; + } - if (settingsManager_[roomid]->isNotificationsEnabled()) - room_list_->updateUnreadMessageCount(roomid, count); - }); + if (settingsManager_[roomid]->isNotificationsEnabled()) + room_list_->updateUnreadMessageCount(roomid, count); + }); - connect(view_manager_, - &TimelineViewManager::updateRoomsLastMessage, - room_list_, - &RoomList::updateRoomDescription); + connect(view_manager_, + &TimelineViewManager::updateRoomsLastMessage, + room_list_, + &RoomList::updateRoomDescription); - connect(room_list_, - SIGNAL(totalUnreadMessageCountUpdated(int)), - this, - SLOT(showUnreadMessageNotification(int))); + connect(room_list_, + SIGNAL(totalUnreadMessageCountUpdated(int)), + this, + SLOT(showUnreadMessageNotification(int))); - connect(text_input_, - SIGNAL(sendTextMessage(const QString &)), - view_manager_, - SLOT(sendTextMessage(const QString &))); + connect(text_input_, + SIGNAL(sendTextMessage(const QString &)), + view_manager_, + SLOT(sendTextMessage(const QString &))); - connect(client_.data(), - SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), - this, - SLOT(updateTopBarAvatar(const QString &, const QPixmap &))); + connect(client_.data(), + SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), + this, + SLOT(updateTopBarAvatar(const QString &, const QPixmap &))); - connect(client_.data(), - SIGNAL(initialSyncCompleted(const SyncResponse &)), - this, - SLOT(initialSyncCompleted(const SyncResponse &))); - connect(client_.data(), - SIGNAL(syncCompleted(const SyncResponse &)), - this, - SLOT(syncCompleted(const SyncResponse &))); - connect(client_.data(), SIGNAL(syncFailed(const QString &)), this, SLOT(syncFailed(const QString &))); - connect(client_.data(), - SIGNAL(getOwnProfileResponse(const QUrl &, const QString &)), - this, - SLOT(updateOwnProfileInfo(const QUrl &, const QString &))); - connect(client_.data(), SIGNAL(ownAvatarRetrieved(const QPixmap &)), this, SLOT(setOwnAvatar(const QPixmap &))); + connect(client_.data(), + SIGNAL(initialSyncCompleted(const SyncResponse &)), + this, + SLOT(initialSyncCompleted(const SyncResponse &))); + connect(client_.data(), + SIGNAL(syncCompleted(const SyncResponse &)), + this, + SLOT(syncCompleted(const SyncResponse &))); + connect(client_.data(), + SIGNAL(syncFailed(const QString &)), + this, + SLOT(syncFailed(const QString &))); + connect(client_.data(), + SIGNAL(getOwnProfileResponse(const QUrl &, const QString &)), + this, + SLOT(updateOwnProfileInfo(const QUrl &, const QString &))); + connect(client_.data(), + SIGNAL(ownAvatarRetrieved(const QPixmap &)), + this, + SLOT(setOwnAvatar(const QPixmap &))); - AvatarProvider::init(client); + AvatarProvider::init(client); } void ChatPage::logout() { - sync_timer_->stop(); + sync_timer_->stop(); - // Delete all config parameters. - QSettings settings; - settings.beginGroup("auth"); - settings.remove(""); - settings.endGroup(); - settings.beginGroup("client"); - settings.remove(""); - settings.endGroup(); - settings.beginGroup("notifications"); - settings.remove(""); - settings.endGroup(); + // Delete all config parameters. + QSettings settings; + settings.beginGroup("auth"); + settings.remove(""); + settings.endGroup(); + settings.beginGroup("client"); + settings.remove(""); + settings.endGroup(); + settings.beginGroup("notifications"); + settings.remove(""); + settings.endGroup(); - // Clear the environment. - room_list_->clear(); - view_manager_->clearAll(); + // Clear the environment. + room_list_->clear(); + view_manager_->clearAll(); - top_bar_->reset(); - user_info_widget_->reset(); - client_->reset(); + top_bar_->reset(); + user_info_widget_->reset(); + client_->reset(); - state_manager_.clear(); - settingsManager_.clear(); - room_avatars_.clear(); + state_manager_.clear(); + settingsManager_.clear(); + room_avatars_.clear(); - AvatarProvider::clear(); + AvatarProvider::clear(); - emit close(); + emit close(); } void ChatPage::bootstrap(QString userid, QString homeserver, QString token) { - client_->setServer(homeserver); - client_->setAccessToken(token); - client_->getOwnProfile(); + client_->setServer(homeserver); + client_->setAccessToken(token); + client_->getOwnProfile(); - try { - cache_ = QSharedPointer(new Cache(userid)); - } catch (const std::exception &e) { - qCritical() << e.what(); - } + try { + cache_ = QSharedPointer(new Cache(userid)); + } catch (const std::exception &e) { + qCritical() << e.what(); + } - if (cache_->isInitialized()) - loadStateFromCache(); - else - client_->initialSync(); + if (cache_->isInitialized()) + loadStateFromCache(); + else + client_->initialSync(); } void ChatPage::startSync() { - client_->sync(); + client_->sync(); } void ChatPage::setOwnAvatar(const QPixmap &img) { - user_info_widget_->setAvatar(img.toImage()); + user_info_widget_->setAvatar(img.toImage()); } void ChatPage::syncFailed(const QString &msg) { - qWarning() << "Sync error:" << msg; - sync_timer_->start(sync_interval_ * 5); + qWarning() << "Sync error:" << msg; + sync_timer_->start(sync_interval_ * 5); } // TODO: Should be moved in another class that manages this global list. void ChatPage::updateDisplayNames(const RoomState &state) { - for (const auto member : state.memberships) { - auto displayName = member.content().displayName(); + for (const auto member : state.memberships) { + auto displayName = member.content().displayName(); - if (!displayName.isEmpty()) - TimelineViewManager::DISPLAY_NAMES.insert(member.stateKey(), displayName); - } + if (!displayName.isEmpty()) + TimelineViewManager::DISPLAY_NAMES.insert(member.stateKey(), displayName); + } } void ChatPage::syncCompleted(const SyncResponse &response) { - // TODO: Catch exception - cache_->setNextBatchToken(response.nextBatch()); - client_->setNextBatchToken(response.nextBatch()); + auto joined = response.rooms().join(); - auto joined = response.rooms().join(); + for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { + RoomState room_state; - for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { - RoomState room_state; + // Merge the new updates for rooms that we are tracking. + if (state_manager_.contains(it.key())) + room_state = state_manager_[it.key()]; - // Merge the new updates for rooms that we are tracking. - if (state_manager_.contains(it.key())) - room_state = state_manager_[it.key()]; + room_state.updateFromEvents(it.value().state().events()); + room_state.updateFromEvents(it.value().timeline().events()); - updateRoomState(room_state, it.value().state().events()); - updateRoomState(room_state, it.value().timeline().events()); - updateDisplayNames(room_state); + updateDisplayNames(room_state); - try { - cache_->insertRoomState(it.key(), room_state); - } catch (const lmdb::error &e) { - qCritical() << e.what(); - // Stop using the cache if an errors occurs. - // TODO: Should also be marked as invalid and be deleted. - cache_->unmount(); - } + if (state_manager_.contains(it.key())) { + // TODO: Use pointers instead of copying. + auto oldState = state_manager_[it.key()]; + oldState.update(room_state); + state_manager_.insert(it.key(), oldState); + } else { + qWarning() << "New rooms cannot be added after initial sync, yet."; + } - if (state_manager_.contains(it.key())) { - // TODO: Use pointers instead of copying. - auto oldState = state_manager_[it.key()]; - oldState.update(room_state); - state_manager_.insert(it.key(), oldState); - } else { - qWarning() << "New rooms cannot be added after initial sync, yet."; - } + if (it.key() == current_room_) + changeTopRoomInfo(it.key()); + } - if (it.key() == current_room_) - changeTopRoomInfo(it.key()); - } + try { + cache_->setState(response.nextBatch(), state_manager_); + } catch (const lmdb::error &e) { + qCritical() << "The cache couldn't be updated: " << e.what(); + // TODO: Notify the user. + cache_->unmount(); + } - room_list_->sync(state_manager_); - view_manager_->sync(response.rooms()); + client_->setNextBatchToken(response.nextBatch()); - sync_timer_->start(sync_interval_); + room_list_->sync(state_manager_); + view_manager_->sync(response.rooms()); + + sync_timer_->start(sync_interval_); } void ChatPage::initialSyncCompleted(const SyncResponse &response) { - if (!response.nextBatch().isEmpty()) - client_->setNextBatchToken(response.nextBatch()); + auto joined = response.rooms().join(); - auto joined = response.rooms().join(); + for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { + RoomState room_state; - // TODO: Catch exception - cache_->setNextBatchToken(response.nextBatch()); + // Build the current state from the timeline and state events. + room_state.updateFromEvents(it.value().state().events()); + room_state.updateFromEvents(it.value().timeline().events()); - for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { - RoomState room_state; + // Remove redundant memberships. + room_state.removeLeaveMemberships(); - // Build the current state from the timeline and state events. - updateRoomState(room_state, it.value().state().events()); - updateRoomState(room_state, it.value().timeline().events()); + // Resolve room name and avatar. e.g in case of one-to-one chats. + room_state.resolveName(); + room_state.resolveAvatar(); - // Remove redundant memberships. - room_state.removeLeaveMemberships(); + updateDisplayNames(room_state); - // Resolve room name and avatar. e.g in case of one-to-one chats. - room_state.resolveName(); - room_state.resolveAvatar(); + state_manager_.insert(it.key(), room_state); + settingsManager_.insert(it.key(), + QSharedPointer(new RoomSettings(it.key()))); - try { - cache_->insertRoomState(it.key(), room_state); - } catch (const lmdb::error &e) { - qCritical() << e.what(); - // Stop using the cache if an errors occurs. - // TODO: Should also be marked as invalid and be deleted. - cache_->unmount(); - } + for (const auto membership : room_state.memberships) { + auto uid = membership.sender(); + auto url = membership.content().avatarUrl(); - updateDisplayNames(room_state); + if (!url.toString().isEmpty()) + AvatarProvider::setAvatarUrl(uid, url); + } + } - state_manager_.insert(it.key(), room_state); - settingsManager_.insert(it.key(), QSharedPointer(new RoomSettings(it.key()))); + try { + cache_->setState(response.nextBatch(), state_manager_); + } catch (const lmdb::error &e) { + qCritical() << "The cache couldn't be initialized: " << e.what(); + cache_->unmount(); + } - for (const auto membership : room_state.memberships) { - auto uid = membership.sender(); - auto url = membership.content().avatarUrl(); + client_->setNextBatchToken(response.nextBatch()); - if (!url.toString().isEmpty()) - AvatarProvider::setAvatarUrl(uid, url); - } - } + // Populate timelines with messages. + view_manager_->initialize(response.rooms()); - // Populate timelines with messages. - view_manager_->initialize(response.rooms()); + // Initialize room list. + room_list_->setInitialRooms(settingsManager_, state_manager_); - // Initialize room list. - room_list_->setInitialRooms(settingsManager_, state_manager_); + sync_timer_->start(sync_interval_); - sync_timer_->start(sync_interval_); - - emit contentLoaded(); + emit contentLoaded(); } void ChatPage::updateTopBarAvatar(const QString &roomid, const QPixmap &img) { - room_avatars_.insert(roomid, img); + room_avatars_.insert(roomid, img); - if (current_room_ != roomid) - return; + if (current_room_ != roomid) + return; - top_bar_->updateRoomAvatar(img.toImage()); + top_bar_->updateRoomAvatar(img.toImage()); } void ChatPage::updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name) { - QSettings settings; - auto userid = settings.value("auth/user_id").toString(); + QSettings settings; + auto userid = settings.value("auth/user_id").toString(); - user_info_widget_->setUserId(userid); - user_info_widget_->setDisplayName(display_name); + user_info_widget_->setUserId(userid); + user_info_widget_->setDisplayName(display_name); - if (avatar_url.isValid()) - client_->fetchOwnAvatar(avatar_url); + if (avatar_url.isValid()) + client_->fetchOwnAvatar(avatar_url); } void ChatPage::changeTopRoomInfo(const QString &room_id) { - if (!state_manager_.contains(room_id)) - return; + if (!state_manager_.contains(room_id)) + return; - auto state = state_manager_[room_id]; + auto state = state_manager_[room_id]; - top_bar_->updateRoomName(state.getName()); - top_bar_->updateRoomTopic(state.getTopic()); - top_bar_->setRoomSettings(settingsManager_[room_id]); + top_bar_->updateRoomName(state.getName()); + top_bar_->updateRoomTopic(state.getTopic()); + top_bar_->setRoomSettings(settingsManager_[room_id]); - if (room_avatars_.contains(room_id)) - top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage()); - else - top_bar_->updateRoomAvatarFromName(state.getName()); + if (room_avatars_.contains(room_id)) + top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage()); + else + top_bar_->updateRoomAvatarFromName(state.getName()); - current_room_ = room_id; + current_room_ = room_id; } void ChatPage::showUnreadMessageNotification(int count) { - emit unreadMessages(count); + emit unreadMessages(count); - // TODO: Make the default title a const. - if (count == 0) - emit changeWindowTitle("nheko"); - else - emit changeWindowTitle(QString("nheko (%1)").arg(count)); -} - -void -ChatPage::updateRoomState(RoomState &room_state, const QJsonArray &events) -{ - events::EventType ty; - - for (const auto &event : events) { - try { - ty = events::extractEventType(event.toObject()); - } catch (const DeserializationException &e) { - qWarning() << e.what() << event; - continue; - } - - if (!events::isStateEvent(ty)) - continue; - - try { - switch (ty) { - case events::EventType::RoomAliases: { - events::StateEvent aliases; - aliases.deserialize(event); - room_state.aliases = aliases; - break; - } - case events::EventType::RoomAvatar: { - events::StateEvent avatar; - avatar.deserialize(event); - room_state.avatar = avatar; - break; - } - case events::EventType::RoomCanonicalAlias: { - events::StateEvent canonical_alias; - canonical_alias.deserialize(event); - room_state.canonical_alias = canonical_alias; - break; - } - case events::EventType::RoomCreate: { - events::StateEvent create; - create.deserialize(event); - room_state.create = create; - break; - } - case events::EventType::RoomHistoryVisibility: { - events::StateEvent history_visibility; - history_visibility.deserialize(event); - room_state.history_visibility = history_visibility; - break; - } - case events::EventType::RoomJoinRules: { - events::StateEvent join_rules; - join_rules.deserialize(event); - room_state.join_rules = join_rules; - break; - } - case events::EventType::RoomName: { - events::StateEvent name; - name.deserialize(event); - room_state.name = name; - break; - } - case events::EventType::RoomMember: { - events::StateEvent member; - member.deserialize(event); - - room_state.memberships.insert(member.stateKey(), member); - - break; - } - case events::EventType::RoomPowerLevels: { - events::StateEvent power_levels; - power_levels.deserialize(event); - room_state.power_levels = power_levels; - break; - } - case events::EventType::RoomTopic: { - events::StateEvent topic; - topic.deserialize(event); - room_state.topic = topic; - break; - } - default: { - continue; - } - } - } catch (const DeserializationException &e) { - qWarning() << e.what() << event; - continue; - } - } + // TODO: Make the default title a const. + if (count == 0) + emit changeWindowTitle("nheko"); + else + emit changeWindowTitle(QString("nheko (%1)").arg(count)); } void ChatPage::loadStateFromCache() { - qDebug() << "Restoring state from cache"; + qDebug() << "Restoring state from cache"; - try { - qDebug() << "Restored nextBatchToken" << cache_->nextBatchToken(); - client_->setNextBatchToken(cache_->nextBatchToken()); - } catch (const lmdb::error &e) { - qCritical() << "Failed to load next_batch_token from cache" << e.what(); - // TODO: Clean the environment - return; - } + try { + qDebug() << "Restored nextBatchToken" << cache_->nextBatchToken(); + client_->setNextBatchToken(cache_->nextBatchToken()); + } catch (const lmdb::error &e) { + qCritical() << "Failed to load next_batch_token from cache" << e.what(); + // TODO: Clean the environment + return; + } - // Fetch all the joined room's state. - auto rooms = cache_->states(); + // Fetch all the joined room's state. + auto rooms = cache_->states(); - for (auto it = rooms.constBegin(); it != rooms.constEnd(); it++) { - RoomState room_state = it.value(); + for (auto it = rooms.constBegin(); it != rooms.constEnd(); it++) { + RoomState room_state = it.value(); - // Clean up and prepare state for use. - room_state.removeLeaveMemberships(); - room_state.resolveName(); - room_state.resolveAvatar(); + // Clean up and prepare state for use. + room_state.removeLeaveMemberships(); + room_state.resolveName(); + room_state.resolveAvatar(); - // Update the global list with user's display names. - updateDisplayNames(room_state); + // Update the global list with user's display names. + updateDisplayNames(room_state); - // Save the current room state. - state_manager_.insert(it.key(), room_state); + // Save the current room state. + state_manager_.insert(it.key(), room_state); - // Create or restore the settings for this room. - settingsManager_.insert(it.key(), QSharedPointer(new RoomSettings(it.key()))); + // Create or restore the settings for this room. + settingsManager_.insert(it.key(), + QSharedPointer(new RoomSettings(it.key()))); - // Resolve user avatars. - for (const auto membership : room_state.memberships) { - auto uid = membership.sender(); - auto url = membership.content().avatarUrl(); + // Resolve user avatars. + for (const auto membership : room_state.memberships) { + auto uid = membership.sender(); + auto url = membership.content().avatarUrl(); - if (!url.toString().isEmpty()) - AvatarProvider::setAvatarUrl(uid, url); - } - } + if (!url.toString().isEmpty()) + AvatarProvider::setAvatarUrl(uid, url); + } + } - // Initializing empty timelines. - view_manager_->initialize(rooms.keys()); + // Initializing empty timelines. + view_manager_->initialize(rooms.keys()); - // Initialize room list from the restored state and settings. - room_list_->setInitialRooms(settingsManager_, state_manager_); + // Initialize room list from the restored state and settings. + room_list_->setInitialRooms(settingsManager_, state_manager_); - // Remove the spinner overlay. - emit contentLoaded(); + // Remove the spinner overlay. + emit contentLoaded(); - sync_timer_->start(sync_interval_); + sync_timer_->start(sync_interval_); } void ChatPage::keyPressEvent(QKeyEvent *event) { - if (event->key() == Qt::Key_K) { - if (event->modifiers() == Qt::ControlModifier) - showQuickSwitcher(); - } + if (event->key() == Qt::Key_K) { + if (event->modifiers() == Qt::ControlModifier) + showQuickSwitcher(); + } } void ChatPage::showQuickSwitcher() { - if (quickSwitcher_ == nullptr) { - quickSwitcher_ = new QuickSwitcher(this); + if (quickSwitcher_ == nullptr) { + quickSwitcher_ = new QuickSwitcher(this); - connect(quickSwitcher_, &QuickSwitcher::roomSelected, room_list_, &RoomList::highlightSelectedRoom); - connect(quickSwitcher_, &QuickSwitcher::closing, this, [=]() { - if (this->quickSwitcherModal_ != nullptr) - this->quickSwitcherModal_->fadeOut(); - }); - } + connect(quickSwitcher_, + &QuickSwitcher::roomSelected, + room_list_, + &RoomList::highlightSelectedRoom); + connect(quickSwitcher_, &QuickSwitcher::closing, this, [=]() { + if (this->quickSwitcherModal_ != nullptr) + this->quickSwitcherModal_->fadeOut(); + }); + } - if (quickSwitcherModal_ == nullptr) { - quickSwitcherModal_ = new OverlayModal(MainWindow::instance(), quickSwitcher_); - quickSwitcherModal_->setDuration(0); - quickSwitcherModal_->setColor(QColor(30, 30, 30, 170)); - } + if (quickSwitcherModal_ == nullptr) { + quickSwitcherModal_ = new OverlayModal(MainWindow::instance(), quickSwitcher_); + quickSwitcherModal_->setDuration(0); + quickSwitcherModal_->setColor(QColor(30, 30, 30, 170)); + } - QMap rooms; + QMap rooms; - for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) - rooms.insert(it.value().getName(), it.key()); + for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) + rooms.insert(it.value().getName(), it.key()); - quickSwitcher_->setRoomList(rooms); - quickSwitcherModal_->fadeIn(); + quickSwitcher_->setRoomList(rooms); + quickSwitcherModal_->fadeIn(); } ChatPage::~ChatPage() { - sync_timer_->stop(); + sync_timer_->stop(); } diff --git a/src/RoomState.cc b/src/RoomState.cc index b22177b2..de0ed6ea 100644 --- a/src/RoomState.cc +++ b/src/RoomState.cc @@ -26,272 +26,365 @@ namespace events = matrix::events; void RoomState::resolveName() { - name_ = "Empty Room"; - userAvatar_.clear(); + name_ = "Empty Room"; + userAvatar_.clear(); - if (!name.content().name().isEmpty()) { - name_ = name.content().name().simplified(); - return; - } + if (!name.content().name().isEmpty()) { + name_ = name.content().name().simplified(); + return; + } - if (!canonical_alias.content().alias().isEmpty()) { - name_ = canonical_alias.content().alias().simplified(); - return; - } + if (!canonical_alias.content().alias().isEmpty()) { + name_ = canonical_alias.content().alias().simplified(); + return; + } - // FIXME: Doesn't follow the spec guidelines. - if (aliases.content().aliases().size() != 0) { - name_ = aliases.content().aliases()[0].simplified(); - return; - } + // FIXME: Doesn't follow the spec guidelines. + if (aliases.content().aliases().size() != 0) { + name_ = aliases.content().aliases()[0].simplified(); + return; + } - QSettings settings; - auto user_id = settings.value("auth/user_id"); + QSettings settings; + auto user_id = settings.value("auth/user_id"); - // TODO: Display names should be sorted alphabetically. - for (const auto membership : memberships) { - if (membership.stateKey() == user_id) - continue; + // TODO: Display names should be sorted alphabetically. + for (const auto membership : memberships) { + if (membership.stateKey() == user_id) + continue; - if (membership.content().membershipState() == events::Membership::Join) { - userAvatar_ = membership.stateKey(); + if (membership.content().membershipState() == events::Membership::Join) { + userAvatar_ = membership.stateKey(); - if (membership.content().displayName().isEmpty()) - name_ = membership.stateKey(); - else - name_ = membership.content().displayName(); + if (membership.content().displayName().isEmpty()) + name_ = membership.stateKey(); + else + name_ = membership.content().displayName(); - break; - } - } + break; + } + } - // TODO: pluralization - if (memberships.size() > 2) - name_ = QString("%1 and %2 others").arg(name_).arg(memberships.size()); + // TODO: pluralization + if (memberships.size() > 2) + name_ = QString("%1 and %2 others").arg(name_).arg(memberships.size()); } void RoomState::resolveAvatar() { - if (userAvatar_.isEmpty()) { - avatar_ = avatar.content().url(); - return; - } + if (userAvatar_.isEmpty()) { + avatar_ = avatar.content().url(); + return; + } - if (memberships.contains(userAvatar_)) { - avatar_ = memberships[userAvatar_].content().avatarUrl(); - } else { - qWarning() << "Setting room avatar from unknown user id" << userAvatar_; - } + if (memberships.contains(userAvatar_)) { + avatar_ = memberships[userAvatar_].content().avatarUrl(); + } else { + qWarning() << "Setting room avatar from unknown user id" << userAvatar_; + } } // Should be used only after initial sync. void RoomState::removeLeaveMemberships() { - for (auto it = memberships.begin(); it != memberships.end();) { - if (it.value().content().membershipState() == events::Membership::Leave) { - it = memberships.erase(it); - } else { - ++it; - } - } + for (auto it = memberships.begin(); it != memberships.end();) { + if (it.value().content().membershipState() == events::Membership::Leave) { + it = memberships.erase(it); + } else { + ++it; + } + } } void RoomState::update(const RoomState &state) { - bool needsNameCalculation = false; - bool needsAvatarCalculation = false; + bool needsNameCalculation = false; + bool needsAvatarCalculation = false; - if (aliases.eventId() != state.aliases.eventId()) { - aliases = state.aliases; - } + if (aliases.eventId() != state.aliases.eventId()) { + aliases = state.aliases; + } - if (avatar.eventId() != state.avatar.eventId()) { - avatar = state.avatar; - needsAvatarCalculation = true; - } + if (avatar.eventId() != state.avatar.eventId()) { + avatar = state.avatar; + needsAvatarCalculation = true; + } - if (canonical_alias.eventId() != state.canonical_alias.eventId()) { - canonical_alias = state.canonical_alias; - needsNameCalculation = true; - } + if (canonical_alias.eventId() != state.canonical_alias.eventId()) { + canonical_alias = state.canonical_alias; + needsNameCalculation = true; + } - if (create.eventId() != state.create.eventId()) - create = state.create; - if (history_visibility.eventId() != state.history_visibility.eventId()) - history_visibility = state.history_visibility; - if (join_rules.eventId() != state.join_rules.eventId()) - join_rules = state.join_rules; + if (create.eventId() != state.create.eventId()) + create = state.create; + if (history_visibility.eventId() != state.history_visibility.eventId()) + history_visibility = state.history_visibility; + if (join_rules.eventId() != state.join_rules.eventId()) + join_rules = state.join_rules; - if (name.eventId() != state.name.eventId()) { - name = state.name; - needsNameCalculation = true; - } + if (name.eventId() != state.name.eventId()) { + name = state.name; + needsNameCalculation = true; + } - if (power_levels.eventId() != state.power_levels.eventId()) - power_levels = state.power_levels; - if (topic.eventId() != state.topic.eventId()) - topic = state.topic; + if (power_levels.eventId() != state.power_levels.eventId()) + power_levels = state.power_levels; + if (topic.eventId() != state.topic.eventId()) + topic = state.topic; - for (auto it = state.memberships.constBegin(); it != state.memberships.constEnd(); ++it) { - auto membershipState = it.value().content().membershipState(); + for (auto it = state.memberships.constBegin(); it != state.memberships.constEnd(); ++it) { + auto membershipState = it.value().content().membershipState(); - if (it.key() == userAvatar_) { - needsNameCalculation = true; - needsAvatarCalculation = true; - } + if (it.key() == userAvatar_) { + needsNameCalculation = true; + needsAvatarCalculation = true; + } - if (membershipState == events::Membership::Leave) - this->memberships.remove(it.key()); - else - this->memberships.insert(it.key(), it.value()); - } + if (membershipState == events::Membership::Leave) + this->memberships.remove(it.key()); + else + this->memberships.insert(it.key(), it.value()); + } - if (needsNameCalculation) - resolveName(); + if (needsNameCalculation) + resolveName(); - if (needsAvatarCalculation) - resolveAvatar(); + if (needsAvatarCalculation) + resolveAvatar(); } QJsonObject RoomState::serialize() const { - QJsonObject obj; + QJsonObject obj; - if (!aliases.eventId().isEmpty()) - obj["aliases"] = aliases.serialize(); + if (!aliases.eventId().isEmpty()) + obj["aliases"] = aliases.serialize(); - if (!avatar.eventId().isEmpty()) - obj["avatar"] = avatar.serialize(); + if (!avatar.eventId().isEmpty()) + obj["avatar"] = avatar.serialize(); - if (!canonical_alias.eventId().isEmpty()) - obj["canonical_alias"] = canonical_alias.serialize(); + if (!canonical_alias.eventId().isEmpty()) + obj["canonical_alias"] = canonical_alias.serialize(); - if (!create.eventId().isEmpty()) - obj["create"] = create.serialize(); + if (!create.eventId().isEmpty()) + obj["create"] = create.serialize(); - if (!history_visibility.eventId().isEmpty()) - obj["history_visibility"] = history_visibility.serialize(); + if (!history_visibility.eventId().isEmpty()) + obj["history_visibility"] = history_visibility.serialize(); - if (!join_rules.eventId().isEmpty()) - obj["join_rules"] = join_rules.serialize(); + if (!join_rules.eventId().isEmpty()) + obj["join_rules"] = join_rules.serialize(); - if (!name.eventId().isEmpty()) - obj["name"] = name.serialize(); + if (!name.eventId().isEmpty()) + obj["name"] = name.serialize(); - if (!power_levels.eventId().isEmpty()) - obj["power_levels"] = power_levels.serialize(); + if (!power_levels.eventId().isEmpty()) + obj["power_levels"] = power_levels.serialize(); - if (!topic.eventId().isEmpty()) - obj["topic"] = topic.serialize(); + if (!topic.eventId().isEmpty()) + obj["topic"] = topic.serialize(); - return obj; + return obj; } void RoomState::parse(const QJsonObject &object) { - // FIXME: Make this less versbose. + // FIXME: Make this less versbose. - if (object.contains("aliases")) { - events::StateEvent event; + if (object.contains("aliases")) { + events::StateEvent event; - try { - event.deserialize(object["aliases"]); - aliases = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - aliases" << e.what(); - } - } + try { + event.deserialize(object["aliases"]); + aliases = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - aliases" << e.what(); + } + } - if (object.contains("avatar")) { - events::StateEvent event; + if (object.contains("avatar")) { + events::StateEvent event; - try { - event.deserialize(object["avatar"]); - avatar = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - avatar" << e.what(); - } - } + try { + event.deserialize(object["avatar"]); + avatar = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - avatar" << e.what(); + } + } - if (object.contains("canonical_alias")) { - events::StateEvent event; + if (object.contains("canonical_alias")) { + events::StateEvent event; - try { - event.deserialize(object["canonical_alias"]); - canonical_alias = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - canonical_alias" << e.what(); - } - } + try { + event.deserialize(object["canonical_alias"]); + canonical_alias = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - canonical_alias" << e.what(); + } + } - if (object.contains("create")) { - events::StateEvent event; + if (object.contains("create")) { + events::StateEvent event; - try { - event.deserialize(object["create"]); - create = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - create" << e.what(); - } - } + try { + event.deserialize(object["create"]); + create = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - create" << e.what(); + } + } - if (object.contains("history_visibility")) { - events::StateEvent event; + if (object.contains("history_visibility")) { + events::StateEvent event; - try { - event.deserialize(object["history_visibility"]); - history_visibility = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - history_visibility" << e.what(); - } - } + try { + event.deserialize(object["history_visibility"]); + history_visibility = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - history_visibility" << e.what(); + } + } - if (object.contains("join_rules")) { - events::StateEvent event; + if (object.contains("join_rules")) { + events::StateEvent event; - try { - event.deserialize(object["join_rules"]); - join_rules = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - join_rules" << e.what(); - } - } + try { + event.deserialize(object["join_rules"]); + join_rules = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - join_rules" << e.what(); + } + } - if (object.contains("name")) { - events::StateEvent event; + if (object.contains("name")) { + events::StateEvent event; - try { - event.deserialize(object["name"]); - name = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - name" << e.what(); - } - } + try { + event.deserialize(object["name"]); + name = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - name" << e.what(); + } + } - if (object.contains("power_levels")) { - events::StateEvent event; + if (object.contains("power_levels")) { + events::StateEvent event; - try { - event.deserialize(object["power_levels"]); - power_levels = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - power_levels" << e.what(); - } - } + try { + event.deserialize(object["power_levels"]); + power_levels = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - power_levels" << e.what(); + } + } - if (object.contains("topic")) { - events::StateEvent event; + if (object.contains("topic")) { + events::StateEvent event; - try { - event.deserialize(object["topic"]); - topic = event; - } catch (const DeserializationException &e) { - qWarning() << "RoomState::parse - topic" << e.what(); - } - } + try { + event.deserialize(object["topic"]); + topic = event; + } catch (const DeserializationException &e) { + qWarning() << "RoomState::parse - topic" << e.what(); + } + } +} + +void +RoomState::updateFromEvents(const QJsonArray &events) +{ + events::EventType ty; + + for (const auto &event : events) { + try { + ty = events::extractEventType(event.toObject()); + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + + if (!events::isStateEvent(ty)) + continue; + + try { + switch (ty) { + case events::EventType::RoomAliases: { + events::StateEvent aliases; + aliases.deserialize(event); + this->aliases = aliases; + break; + } + case events::EventType::RoomAvatar: { + events::StateEvent avatar; + avatar.deserialize(event); + this->avatar = avatar; + break; + } + case events::EventType::RoomCanonicalAlias: { + events::StateEvent + canonical_alias; + canonical_alias.deserialize(event); + this->canonical_alias = canonical_alias; + break; + } + case events::EventType::RoomCreate: { + events::StateEvent create; + create.deserialize(event); + this->create = create; + break; + } + case events::EventType::RoomHistoryVisibility: { + events::StateEvent + history_visibility; + history_visibility.deserialize(event); + this->history_visibility = history_visibility; + break; + } + case events::EventType::RoomJoinRules: { + events::StateEvent join_rules; + join_rules.deserialize(event); + this->join_rules = join_rules; + break; + } + case events::EventType::RoomName: { + events::StateEvent name; + name.deserialize(event); + this->name = name; + break; + } + case events::EventType::RoomMember: { + events::StateEvent member; + member.deserialize(event); + + this->memberships.insert(member.stateKey(), member); + + break; + } + case events::EventType::RoomPowerLevels: { + events::StateEvent power_levels; + power_levels.deserialize(event); + this->power_levels = power_levels; + break; + } + case events::EventType::RoomTopic: { + events::StateEvent topic; + topic.deserialize(event); + this->topic = topic; + break; + } + default: { + continue; + } + } + } catch (const DeserializationException &e) { + qWarning() << e.what() << event; + continue; + } + } }