From 5ef3250994d21954ae3736f42611268bbe03ad0e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 20 Nov 2021 22:48:04 +0100 Subject: [PATCH 1/5] Add a filter for direct chats fixes #317 --- resources/icons/ui/people.svg | 1 + resources/res.qrc | 1 + src/ChatPage.cpp | 12 +-- src/ChatPage.h | 4 +- src/timeline/CommunitiesModel.cpp | 42 ++++++-- src/timeline/CommunitiesModel.h | 4 +- src/timeline/RoomlistModel.cpp | 152 +++++++++++++++++++++++---- src/timeline/RoomlistModel.h | 10 +- src/timeline/TimelineViewManager.cpp | 6 +- src/timeline/TimelineViewManager.h | 2 +- 10 files changed, 193 insertions(+), 41 deletions(-) create mode 100644 resources/icons/ui/people.svg diff --git a/resources/icons/ui/people.svg b/resources/icons/ui/people.svg new file mode 100644 index 00000000..7c284050 --- /dev/null +++ b/resources/icons/ui/people.svg @@ -0,0 +1 @@ + diff --git a/resources/res.qrc b/resources/res.qrc index 838aeadb..83edc941 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -17,6 +17,7 @@ icons/ui/microphone-mute.svg icons/ui/microphone-unmute.svg icons/ui/pause-symbol.svg + icons/ui/people.svg icons/ui/play-sign.svg icons/ui/power-off.svg icons/ui/refresh.svg diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 77a8edcf..4b37864b 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) this, &ChatPage::initializeViews, view_manager_, - [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); }, + [this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); }, Qt::QueuedConnection); connect(this, &ChatPage::initializeEmptyViews, @@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) &TimelineViewManager::initializeRoomlist); connect( this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); - connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { - view_manager_->sync(rooms); + connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) { + view_manager_->sync(sync); static unsigned int prevNotificationCount = 0; unsigned int notificationCount = 0; - for (const auto &room : rooms.join) { + for (const auto &room : sync.rooms.join) { notificationCount += room.second.unread_notifications.notification_count; } @@ -583,7 +583,7 @@ ChatPage::startInitialSync() olm::handle_to_device_messages(res.to_device.events); - emit initializeViews(std::move(res.rooms)); + emit initializeViews(std::move(res)); emit initializeMentions(cache::getTimelineMentions()); cache::calculateRoomReadStatus(); @@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); - emit syncUI(res.rooms); + emit syncUI(std::move(res)); // if we process a lot of syncs (1 every 200ms), this means we clean the // db every 100s diff --git a/src/ChatPage.h b/src/ChatPage.h index 8f3dc53e..c572f94b 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -126,10 +126,10 @@ signals: void newRoom(const QString &room_id); void changeToRoom(const QString &room_id); - void initializeViews(const mtx::responses::Rooms &rooms); + void initializeViews(const mtx::responses::Sync &rooms); void initializeEmptyViews(); void initializeMentions(const QMap ¬ifs); - void syncUI(const mtx::responses::Rooms &rooms); + void syncUI(const mtx::responses::Sync &sync); void dropToLoginPageCb(const QString &msg); void notifyMessage(const QString &roomid, diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp index 47ff521d..90f1532b 100644 --- a/src/timeline/CommunitiesModel.cpp +++ b/src/timeline/CommunitiesModel.cpp @@ -44,8 +44,23 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return ""; } - } else if (index.row() - 1 < spaceOrder_.size()) { - auto id = spaceOrder_.at(index.row() - 1); + } else if (index.row() == 1) { + switch (role) { + case CommunitiesModel::Roles::AvatarUrl: + return QString(":/icons/icons/ui/people.svg"); + case CommunitiesModel::Roles::DisplayName: + return tr("Direct Chats"); + case CommunitiesModel::Roles::Tooltip: + return tr("Show direct chats."); + case CommunitiesModel::Roles::ChildrenHidden: + return false; + case CommunitiesModel::Roles::Hidden: + return hiddentTagIds_.contains("dm"); + case CommunitiesModel::Roles::Id: + return "dm"; + } + } else if (index.row() - 2 < spaceOrder_.size()) { + auto id = spaceOrder_.at(index.row() - 2); switch (role) { case CommunitiesModel::Roles::AvatarUrl: return QString::fromStdString(spaces_.at(id).avatar_url); @@ -59,8 +74,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const case CommunitiesModel::Roles::Id: return "space:" + id; } - } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) { - auto tag = tags_.at(index.row() - 1 - spaceOrder_.size()); + } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) { + auto tag = tags_.at(index.row() - 2 - spaceOrder_.size()); if (tag == "m.favourite") { switch (role) { case CommunitiesModel::Roles::AvatarUrl: @@ -156,11 +171,11 @@ CommunitiesModel::clear() } void -CommunitiesModel::sync(const mtx::responses::Rooms &rooms) +CommunitiesModel::sync(const mtx::responses::Sync &sync_) { bool tagsUpdated = false; - for (const auto &[roomid, room] : rooms.join) { + for (const auto &[roomid, room] : sync_.rooms.join) { (void)roomid; for (const auto &e : room.account_data.events) if (std::holds_alternative< @@ -182,11 +197,18 @@ CommunitiesModel::sync(const mtx::responses::Rooms &rooms) tagsUpdated = true; } } - for (const auto &[roomid, room] : rooms.leave) { + for (const auto &[roomid, room] : sync_.rooms.leave) { (void)room; if (spaceOrder_.contains(QString::fromStdString(roomid))) tagsUpdated = true; } + for (const auto &e : sync_.account_data.events) { + if (std::holds_alternative< + mtx::events::AccountDataEvent>(e)) { + tagsUpdated = true; + break; + } + } if (tagsUpdated) initializeSidebar(); @@ -213,6 +235,10 @@ CommunitiesModel::setCurrentTagId(QString tagId) return; } } + } else if (tagId == "dm") { + this->currentTagId_ = tagId; + emit currentTagIdChanged(currentTagId_); + return; } this->currentTagId_ = ""; @@ -239,6 +265,8 @@ CommunitiesModel::toggleTagId(QString tagId) auto idx = spaceOrder_.indexOf(tagId.mid(6)); if (idx != -1) emit dataChanged(index(idx + 1), index(idx + 1), {Hidden}); + } else if (tagId == "dm") { + emit dataChanged(index(1), index(1), {Hidden}); } emit hiddenTagsChanged(); diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h index 0440d17f..114e3f94 100644 --- a/src/timeline/CommunitiesModel.h +++ b/src/timeline/CommunitiesModel.h @@ -37,13 +37,13 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { (void)parent; - return 1 + tags_.size() + spaceOrder_.size(); + return 2 + tags_.size() + spaceOrder_.size(); } QVariant data(const QModelIndex &index, int role) const override; public slots: void initializeSidebar(); - void sync(const mtx::responses::Rooms &rooms); + void sync(const mtx::responses::Sync &sync_); void clear(); QString currentTagId() const { return currentTagId_; } void setCurrentTagId(QString tagId); diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index cc9ff800..53fd9498 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -95,6 +95,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const return list; } else if (role == Roles::RoomId) { return roomid; + } else if (role == Roles::IsDirect) { + return directChatToUser.count(roomid) > 0; + } else if (role == Roles::DirectChatOtherUserId) { + return directChatToUser.count(roomid) ? directChatToUser.at(roomid).front() : ""; } if (models.contains(roomid)) { @@ -129,10 +133,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const list.push_back(QString::fromStdString(t)); return list; } - case Roles::IsDirect: - return room->isDirect(); - case Roles::DirectChatOtherUserId: - return room->directChatOtherUserId(); default: return {}; } @@ -162,12 +162,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const return false; case Roles::Tags: return QStringList(); - case Roles::IsDirect: - // The list of users from the room doesn't contain the invited - // users, so we won't factor the invite into the count - return room.member_count == 1; - case Roles::DirectChatOtherUserId: - return cache::getMembersFromInvite(roomid.toStdString(), 0, 1).front().user_id; default: return {}; } @@ -199,10 +193,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const return true; case Roles::Tags: return QStringList(); - case Roles::IsDirect: - return false; - case Roles::DirectChatOtherUserId: - return QString{}; // should never be reached default: return {}; } @@ -443,10 +433,69 @@ RoomlistModel::fetchPreview(QString roomid_) const }); } -void -RoomlistModel::sync(const mtx::responses::Rooms &rooms) +std::set +RoomlistModel::updateDMs(mtx::events::AccountDataEvent event) { - for (const auto &[room_id, room] : rooms.join) { + std::set roomsToUpdate; + std::map> directChatToUserTemp; + + for (const auto &[user, rooms] : event.content.user_to_rooms) { + QString u = QString::fromStdString(user); + + for (const auto &r : rooms) { + directChatToUserTemp[QString::fromStdString(r)].push_back(u); + } + } + + for (auto l = directChatToUser.begin(), r = directChatToUserTemp.begin(); + l != directChatToUser.end() && r != directChatToUserTemp.end();) { + if (l == directChatToUser.end()) { + while (r != directChatToUserTemp.end()) { + roomsToUpdate.insert(r->first); + ++r; + } + } else if (r == directChatToUserTemp.end()) { + while (l != directChatToUser.end()) { + roomsToUpdate.insert(l->first); + ++l; + } + } else if (l->first == r->first) { + if (l->second != r->second) + roomsToUpdate.insert(l->first); + + ++l; + ++r; + } else if (l->first < r->first) { + roomsToUpdate.insert(l->first); + ++l; + } else if (l->first > r->first) { + roomsToUpdate.insert(r->first); + ++r; + } else { + throw std::logic_error("Infinite loop when updating DMs!"); + } + } + + this->directChatToUser = directChatToUserTemp; + + return roomsToUpdate; +} + +void +RoomlistModel::sync(const mtx::responses::Sync &sync_) +{ + for (const auto &e : sync_.account_data.events) { + if (auto event = + std::get_if>(&e)) { + auto updatedDMs = updateDMs(*event); + for (const auto &r : updatedDMs) { + if (auto idx = roomidToIndex(r); idx != -1) + emit dataChanged(index(idx), index(idx), {IsDirect, DirectChatOtherUserId}); + } + } + } + + for (const auto &[room_id, room] : sync_.rooms.join) { auto qroomid = QString::fromStdString(room_id); // addRoom will only add the room, if it doesn't exist @@ -477,7 +526,7 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms) } } - for (const auto &[room_id, room] : rooms.leave) { + for (const auto &[room_id, room] : sync_.rooms.leave) { (void)room; auto qroomid = QString::fromStdString(room_id); @@ -497,7 +546,7 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms) } } - for (const auto &[room_id, room] : rooms.invite) { + for (const auto &[room_id, room] : sync_.rooms.invite) { (void)room; auto qroomid = QString::fromStdString(room_id); @@ -527,6 +576,15 @@ RoomlistModel::initializeRooms() invites.clear(); currentRoom_ = nullptr; + auto e = cache::client()->getAccountData(mtx::events::EventType::Direct); + if (e) { + if (auto event = + std::get_if>( + &e.value())) { + updateDMs(*event); + } + } + invites = cache::client()->invites(); for (const auto &id : invites.keys()) roomids.push_back(id); @@ -756,11 +814,14 @@ FilteredRoomlistModel::updateHiddenTagsAndSpaces() { hiddenTags.clear(); hiddenSpaces.clear(); + hideDMs = false; for (const auto &t : UserSettings::instance()->hiddenTags()) { if (t.startsWith("tag:")) hiddenTags.push_back(t.mid(4)); else if (t.startsWith("space:")) hiddenSpaces.push_back(t.mid(6)); + else if (t == "dm") + hideDMs = true; } invalidateFilter(); @@ -801,7 +862,48 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons return false; } + if (hideDMs) { + return !sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect) + .toBool(); + } + return true; + } else if (filterType == FilterBy::DirectChats) { + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) + .toBool()) { + return false; + } + + if (sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace) + .toBool()) { + return false; + } + + if (!hiddenTags.empty()) { + auto tags = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags) + .toStringList(); + + for (const auto &t : tags) + if (hiddenTags.contains(t)) + return false; + } + + if (!hiddenSpaces.empty()) { + auto parents = sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces) + .toStringList(); + for (const auto &t : parents) + if (hiddenSpaces.contains(t)) + return false; + } + + return sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect) + .toBool(); } else if (filterType == FilterBy::Tag) { if (sourceModel() ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) @@ -837,6 +939,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons return false; } + if (hideDMs) { + return !sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect) + .toBool(); + } + return true; } else if (filterType == FilterBy::Space) { if (filterStr == sourceModel() @@ -874,6 +982,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons return false; } + if (hideDMs) { + return !sourceModel() + ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect) + .toBool(); + } + return true; } else { return true; diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index 458e0fe7..5d0bcd53 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -87,7 +87,7 @@ public: public slots: void initializeRooms(); - void sync(const mtx::responses::Rooms &rooms); + void sync(const mtx::responses::Sync &sync_); void clear(); int roomidToIndex(QString roomid) { @@ -123,6 +123,7 @@ signals: private: void addRoom(const QString &room_id, bool suppressInsertNotification = false); void fetchPreview(QString roomid) const; + std::set updateDMs(mtx::events::AccountDataEvent e); TimelineViewManager *manager = nullptr; std::vector roomids; @@ -134,6 +135,8 @@ private: QSharedPointer currentRoom_; std::optional currentRoomPreview_; + std::map> directChatToUser; + friend class FilteredRoomlistModel; }; @@ -180,6 +183,9 @@ public slots: } else if (tagId.startsWith("space:")) { filterType = FilterBy::Space; filterStr = tagId.mid(6); + } else if (tagId.startsWith("dm")) { + filterType = FilterBy::DirectChats; + filterStr.clear(); } else { filterType = FilterBy::Nothing; filterStr.clear(); @@ -202,9 +208,11 @@ private: { Tag, Space, + DirectChats, Nothing, }; QString filterStr = ""; FilterBy filterType = FilterBy::Nothing; QStringList hiddenTags, hiddenSpaces; + bool hideDMs = false; }; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index c5fe6b4b..07fb0417 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -359,10 +359,10 @@ TimelineViewManager::setVideoCallItem() } void -TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res) +TimelineViewManager::sync(const mtx::responses::Sync &sync_) { - this->rooms_->sync(rooms_res); - this->communities_->sync(rooms_res); + this->rooms_->sync(sync_); + this->communities_->sync(sync_); if (isInitialSync_) { this->isInitialSync_ = false; diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 3e3952a8..a4b49829 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -48,7 +48,7 @@ public: TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); QWidget *getWidget() const { return container; } - void sync(const mtx::responses::Rooms &rooms); + void sync(const mtx::responses::Sync &sync_); MxcImageProvider *imageProvider() { return imgProvider; } CallManager *callManager() { return callManager_; } From 4dc5b647c6faea3d73fc7f6a4b61b392087015dc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 21 Nov 2021 05:04:48 +0100 Subject: [PATCH 2/5] Pretty error printing --- CMakeLists.txt | 4 +- io.github.NhekoReborn.Nheko.yaml | 6 +- src/ChatPage.cpp | 8 +-- src/MatrixClient.h | 116 +++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d84df5f..2352b799 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -413,7 +413,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG v0.6.0 + GIT_TAG ffc1d3e13a507fa501966b2d7e9d4eda881f6bf4 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") @@ -710,7 +710,7 @@ if(USE_BUNDLED_COEURL) FetchContent_Declare( coeurl GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git - GIT_TAG v0.1.0 + GIT_TAG abafd60d7e9f5cce76c9abad3b2b3dc1382e5349 ) FetchContent_MakeAvailable(coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 70c3af83..22e52ae8 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -175,8 +175,7 @@ modules: - -Ddefault_library=static name: coeurl sources: - - commit: a08f619adaa1ccd34eb6315d6578eddae0d1cc9b - tag: v0.1.0 + - commit: abafd60d7e9f5cce76c9abad3b2b3dc1382e5349 type: git url: https://nheko.im/nheko-reborn/coeurl.git - config-opts: @@ -187,8 +186,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: eecc4e93f2137c658014f17cefd62ad569063769 - tag: v0.6.0 + - commit: ffc1d3e13a507fa501966b2d7e9d4eda881f6bf4 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 4b37864b..bd85dc75 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -662,8 +662,6 @@ ChatPage::trySync() if (err) { const auto error = QString::fromStdString(err->matrix_error.error); const auto msg = tr("Please try to login again: %1").arg(error); - const auto err_code = mtx::errors::to_string(err->matrix_error.errcode); - const int status_code = static_cast(err->status_code); if ((http::is_logged_in() && (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || @@ -673,11 +671,7 @@ ChatPage::trySync() return; } - nhlog::net()->error("sync error: {} {} {} {}", - err->parse_error, - status_code, - err->error_code, - err_code); + nhlog::net()->error("sync error: {}", *err); emit tryDelayedSyncCb(); return; } diff --git a/src/MatrixClient.h b/src/MatrixClient.h index 605ba5e0..7d86537b 100644 --- a/src/MatrixClient.h +++ b/src/MatrixClient.h @@ -6,6 +6,10 @@ #include +#include + +#include "Logging.h" + namespace http { mtx::http::Client * client(); @@ -17,3 +21,115 @@ is_logged_in(); void init(); } + +template<> +struct fmt::formatter +{ + // Presentation format: 'f' - fixed, 'e' - exponential. + bool print_network_error = false; + bool print_http_error = false; + bool print_parser_error = false; + bool print_matrix_error = false; + + // Parses format specifications of the form ['f' | 'e']. + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) + { + // [ctx.begin(), ctx.end()) is a character range that contains a part of + // the format string starting from the format specifications to be parsed, + // e.g. in + // + // fmt::format("{:f} - point of interest", point{1, 2}); + // + // the range will contain "f} - point of interest". The formatter should + // parse specifiers until '}' or the end of the range. In this example + // the formatter should parse the 'f' specifier and return an iterator + // pointing to '}'. + + // Parse the presentation format and store it in the formatter: + auto it = ctx.begin(), end = ctx.end(); + + while (it != end && *it != '}') { + auto tmp = *it++; + + switch (tmp) { + case 'n': + print_matrix_error = true; + break; + case 'h': + print_matrix_error = true; + break; + case 'p': + print_matrix_error = true; + break; + case 'm': + print_matrix_error = true; + break; + default: + throw format_error("invalid format specifier for mtx error"); + } + } + + // Check if reached the end of the range: + if (it != end && *it != '}') + throw format_error("invalid format"); + + // Return an iterator past the end of the parsed range: + return it; + } + + // Formats the point p using the parsed format specification (presentation) + // stored in this formatter. + template + auto format(const mtx::http::ClientError &e, FormatContext &ctx) -> decltype(ctx.out()) + { + // ctx.out() is an output iterator to write to. + bool prepend_comma = false; + format_to(ctx.out(), "("); + if (print_network_error || e.error_code) { + format_to(ctx.out(), "connection: {}", e.error_code_string()); + prepend_comma = true; + } + + if (print_http_error || + (e.status_code != 0 && (e.status_code < 200 || e.status_code >= 300))) { + if (prepend_comma) + format_to(ctx.out(), ", "); + format_to(ctx.out(), "http: {}", e.status_code); + prepend_comma = true; + } + + if (print_parser_error || !e.parse_error.empty()) { + if (prepend_comma) + format_to(ctx.out(), ", "); + format_to(ctx.out(), "parser: {}", e.parse_error); + prepend_comma = true; + } + + if (print_parser_error || + (e.matrix_error.errcode != mtx::errors::ErrorCode::M_UNRECOGNIZED && + !e.matrix_error.error.empty())) { + if (prepend_comma) + format_to(ctx.out(), ", "); + format_to(ctx.out(), + "matrix: {}:'{}'", + to_string(e.matrix_error.errcode), + e.matrix_error.error); + } + + return format_to(ctx.out(), ")"); + } +}; + +template<> +struct fmt::formatter> : formatter +{ + // parse is inherited from formatter. + template + auto format(std::optional c, FormatContext &ctx) + { + if (!c) + return format_to(ctx.out(), "(no error)"); + else + return formatter::format(*c, ctx); + } +}; From 3d92e8ae606024f5054795281ccd488abee1795a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 21 Nov 2021 05:23:38 +0100 Subject: [PATCH 3/5] Mark rooms as direct chats Either by accepting an invite or manually using /converttodm and revert with /converttoroom. --- src/ChatPage.cpp | 4 +-- src/Utils.cpp | 63 ++++++++++++++++++++++++++++++++++ src/Utils.h | 7 ++++ src/timeline/InputBar.cpp | 5 +++ src/timeline/RoomlistModel.cpp | 2 ++ 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index bd85dc75..c1c7eb7d 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -660,8 +660,8 @@ ChatPage::trySync() http::client()->sync( opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) { if (err) { - const auto error = QString::fromStdString(err->matrix_error.error); - const auto msg = tr("Please try to login again: %1").arg(error); + const auto error = QString::fromStdString(err->matrix_error.error); + const auto msg = tr("Please try to login again: %1").arg(error); if ((http::is_logged_in() && (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || diff --git a/src/Utils.cpp b/src/Utils.cpp index 59b02298..dda6f685 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -27,6 +27,7 @@ #include "Cache.h" #include "Config.h" #include "EventAccessors.h" +#include "Logging.h" #include "MatrixClient.h" #include "UserSettingsPage.h" @@ -813,3 +814,65 @@ utils::isReply(const mtx::events::collections::TimelineEvents &e) { return mtx::accessors::relations(e).reply_to().has_value(); } + +void +utils::removeDirectFromRoom(QString roomid) +{ + http::client()->get_account_data( + [roomid](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) { + if (e && e->status_code == 404) + ev = {}; + else if (e) { + nhlog::net()->error("Failed to retrieve m.direct: {}", *e); + return; + } + + auto r = roomid.toStdString(); + + for (auto it = ev.user_to_rooms.begin(); it != ev.user_to_rooms.end();) { + for (auto rit = it->second.begin(); rit != it->second.end();) { + if (r == *rit) + rit = it->second.erase(rit); + else + ++rit; + } + + if (it->second.empty()) + it = ev.user_to_rooms.erase(it); + else + ++it; + } + + http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) { + if (e) + nhlog::net()->error("Failed to update m.direct: {}", *e); + }); + }); +} +void +utils::markRoomAsDirect(QString roomid, std::vector members) +{ + http::client()->get_account_data( + [roomid, members](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) { + if (e && e->status_code == 404) + ev = {}; + else if (e) { + nhlog::net()->error("Failed to retrieve m.direct: {}", *e); + return; + } + + auto local = utils::localUser(); + auto r = roomid.toStdString(); + + for (const auto &m : members) { + if (m.user_id != local) { + ev.user_to_rooms[m.user_id.toStdString()].push_back(r); + } + } + + http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) { + if (e) + nhlog::net()->error("Failed to update m.direct: {}", *e); + }); + }); +} diff --git a/src/Utils.h b/src/Utils.h index da82ec7c..701ec8fc 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -304,4 +305,10 @@ readImage(const QByteArray &data); bool isReply(const mtx::events::collections::TimelineEvents &e); + +void +removeDirectFromRoom(QString roomid); + +void +markRoomAsDirect(QString roomid, std::vector members); } diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 44df3411..bd4f59d8 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -645,6 +645,11 @@ InputBar::command(QString command, QString args) return; } nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); + } else if (command == "converttodm") { + utils::markRoomAsDirect(this->room->roomId(), + cache::getMembers(this->room->roomId().toStdString(), 0, -1)); + } else if (command == "converttoroom") { + utils::removeDirectFromRoom(this->room->roomId()); } } diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 53fd9498..7d727659 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -627,6 +627,8 @@ RoomlistModel::acceptInvite(QString roomid) if (invites.contains(roomid)) { // Don't remove invite yet, so that we can switch to it ChatPage::instance()->joinRoom(roomid); + utils::markRoomAsDirect(roomid, + cache::client()->getMembersFromInvite(roomid.toStdString(), 0, -1)); } } void From e7d4aec6ecf65ad970c341e30949218e4472944a Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 21 Nov 2021 07:06:37 +0100 Subject: [PATCH 4/5] Only mark as direct, if invite was direct --- src/Cache.cpp | 8 ++++++-- src/CacheStructs.h | 2 ++ src/timeline/RoomlistModel.cpp | 15 ++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index c22cd0d6..4f96f430 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn, auto display_name = msg->content.display_name.empty() ? msg->state_key : msg->content.display_name; - MemberInfo tmp{display_name, msg->content.avatar_url}; + MemberInfo tmp{display_name, msg->content.avatar_url, msg->content.is_direct}; membersdb.put(txn, msg->state_key, json(tmp).dump()); } else { @@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, try { MemberInfo tmp = json::parse(user_data); members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), - QString::fromStdString(tmp.name)}); + QString::fromStdString(tmp.name), + tmp.is_direct}); } catch (const json::exception &e) { nhlog::db()->warn("{}", e.what()); } @@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info) { j["name"] = info.name; j["avatar_url"] = info.avatar_url; + if (info.is_direct) + j["is_direct"] = info.is_direct; } void @@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info) { info.name = j.at("name"); info.avatar_url = j.at("avatar_url"); + info.is_direct = j.value("is_direct", false); } void diff --git a/src/CacheStructs.h b/src/CacheStructs.h index e28f5b2d..01a050da 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -26,6 +26,7 @@ struct RoomMember { QString user_id; QString display_name; + bool is_direct = false; }; //! Used to uniquely identify a list of read receipts. @@ -98,6 +99,7 @@ struct MemberInfo { std::string name; std::string avatar_url; + bool is_direct = false; }; void diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 7d727659..79324628 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -586,8 +586,9 @@ RoomlistModel::initializeRooms() } invites = cache::client()->invites(); - for (const auto &id : invites.keys()) + for (const auto &id : invites.keys()) { roomids.push_back(id); + } for (const auto &id : cache::client()->roomIds()) addRoom(id, true); @@ -626,9 +627,17 @@ RoomlistModel::acceptInvite(QString roomid) { if (invites.contains(roomid)) { // Don't remove invite yet, so that we can switch to it + auto members = cache::getMembersFromInvite(roomid.toStdString(), 0, -1); + auto local = utils::localUser(); + for (const auto &m : members) { + if (m.user_id == local && m.is_direct) { + nhlog::db()->info("marking {} as direct", roomid.toStdString()); + utils::markRoomAsDirect(roomid, members); + break; + } + } + ChatPage::instance()->joinRoom(roomid); - utils::markRoomAsDirect(roomid, - cache::client()->getMembersFromInvite(roomid.toStdString(), 0, -1)); } } void From 71d7ff3a90f17be892678c122d4620fabfc5b05f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 21 Nov 2021 21:19:56 +0100 Subject: [PATCH 5/5] Try to tame Windows headers See also: https://stackoverflow.com/questions/1394910/how-to-tame-the-windows-headers-useful-defines --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2352b799..4276d2f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -644,7 +644,7 @@ endif() if(WIN32) add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS}) - target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601) + target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601 NOMINMAX WIN32_LEAN_AND_MEAN STRICT) else() add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})