From 0d700d99339ff4c921a7d242427fdd1f3c1d4c0b Mon Sep 17 00:00:00 2001 From: kamathmanu Date: Wed, 28 Jul 2021 22:29:57 -0400 Subject: [PATCH] Implemented Room Directory model to store and provide QML view with public room data from mtxclient --- CMakeLists.txt | 2 + src/RoomDirectoryModel.cpp | 171 +++++++++++++++++++++++++++ src/RoomDirectoryModel.h | 85 +++++++++++++ src/timeline/TimelineViewManager.cpp | 8 ++ 4 files changed, 266 insertions(+) create mode 100644 src/RoomDirectoryModel.cpp create mode 100644 src/RoomDirectoryModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b26602c..587059fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,6 +361,7 @@ set(SRC_FILES src/UserSettingsPage.cpp src/UsersModel.cpp src/RoomsModel.cpp + src/RoomDirectoryModel.cpp src/Utils.cpp src/WebRTCSession.cpp src/WelcomePage.cpp @@ -567,6 +568,7 @@ qt5_wrap_cpp(MOC_HEADERS src/UserSettingsPage.h src/UsersModel.h src/RoomsModel.h + src/RoomDirectoryModel.h src/WebRTCSession.h src/WelcomePage.h ) diff --git a/src/RoomDirectoryModel.cpp b/src/RoomDirectoryModel.cpp new file mode 100644 index 00000000..397871eb --- /dev/null +++ b/src/RoomDirectoryModel.cpp @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RoomDirectoryModel.h" +#include "ChatPage.h" + +#include + +RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) + : QAbstractListModel(parent) + , server_(s) + , canFetchMore_(true) +{ + connect(this, &RoomDirectoryModel::fetchedRoomsBatch, this, &RoomDirectoryModel::displayRooms, Qt::QueuedConnection); +} + +QHash +RoomDirectoryModel::roleNames() const +{ + return { + {Roles::Name, "name"}, + {Roles::Id, "roomid"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::Topic, "topic"}, + {Roles::MemberCount, "numMembers"}, + {Roles::Previewable, "canPreview"},}; +} + +void +RoomDirectoryModel::resetDisplayedData() +{ + beginResetModel(); + + prevBatch_ = ""; + nextBatch_ = ""; + canFetchMore_ = true; + + beginRemoveRows(QModelIndex(), 0 , static_cast (publicRoomsData_.size())); + publicRoomsData_.clear(); + endRemoveRows(); + + endResetModel(); +} + +void +RoomDirectoryModel::setMatrixServer(const QString &s) +{ + server_ = s.toStdString(); + + nhlog::ui()->debug("Received matrix server: {}", server_); + + resetDisplayedData(); +} + +void +RoomDirectoryModel::setSearchTerm(const QString &f) +{ + userSearchString_ = f.toStdString(); + + nhlog::ui()->debug("Received user query: {}", userSearchString_); + + resetDisplayedData(); +} + +std::vector +RoomDirectoryModel::getViasForRoom(const std::vector &aliases) +{ + std::vector vias; + + vias.reserve(aliases.size()); + + std::transform(aliases.begin(), aliases.end(), + std::back_inserter(vias), [](const auto &alias) { + const auto roomAliasDelimiter = ":"; + return alias.substr(alias.find(roomAliasDelimiter) + 1); + }); + + return vias; +} + +void +RoomDirectoryModel::joinRoom(const int &index) +{ + if (index >= 0 && static_cast (index) < publicRoomsData_.size()) + { + const auto &chunk = publicRoomsData_[index]; + nhlog::ui()->debug("'Joining room {}", chunk.room_id); + ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); + } +} + +QVariant +RoomDirectoryModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) + { + const auto &room_chunk = publicRoomsData_[index.row()]; + switch (role) + { + case Roles::Name: + return QString::fromStdString(room_chunk.name); + case Roles::Id: + return QString::fromStdString(room_chunk.room_id); + case Roles::AvatarUrl: + return QString::fromStdString(room_chunk.avatar_url); + case Roles::Topic: + return QString::fromStdString(room_chunk.topic); + case Roles::MemberCount: + return QVariant::fromValue(room_chunk.num_joined_members); + case Roles::Previewable: + return QVariant::fromValue(room_chunk.world_readable); + } + } + return {}; +} + +void +RoomDirectoryModel::fetchMore(const QModelIndex &) +{ + nhlog::net()->debug("Fetching more rooms from mtxclient..."); + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + + mtx::requests::PublicRooms req; + req.limit = limit_; + req.since = prevBatch_; + req.filter.generic_search_term = userSearchString_; + // req.third_party_instance_id = third_party_instance_id; + auto requested_server = server_; + + http::client()->post_public_rooms(req, [requested_server, this, req] + (const mtx::responses::PublicRooms &res, + mtx::http::RequestErr err) + { + if (err) { + nhlog::net()->error + ("Failed to retrieve rooms from mtxclient - {} - {} - {}", + mtx::errors::to_string(err->matrix_error.errcode), + err->matrix_error.error, + err->parse_error); + } else if ( req.filter.generic_search_term == this->userSearchString_ + && req.since == this->prevBatch_ + && requested_server == this->server_) { + nhlog::net()->debug("signalling chunk to GUI thread"); + emit fetchedRoomsBatch(res.chunk, res.prev_batch, res.next_batch); + } + }, requested_server); +} + +void +RoomDirectoryModel::displayRooms(std::vector fetched_rooms, + const std::string &prev_batch, const std::string &next_batch) +{ + nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, nextBatch_); + nhlog::net()->debug("NP batch: {} | NN batch: {}", prev_batch, next_batch); + + if (fetched_rooms.empty()) { + nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); + return; + } + + beginInsertRows(QModelIndex(), static_cast (publicRoomsData_.size()), static_cast (publicRoomsData_.size() + fetched_rooms.size()) - 1); + this->publicRoomsData_.insert(this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); + endInsertRows(); + + if (next_batch.empty()) { + canFetchMore_ = false; + } + + prevBatch_ = std::exchange(nextBatch_, next_batch); +} \ No newline at end of file diff --git a/src/RoomDirectoryModel.h b/src/RoomDirectoryModel.h new file mode 100644 index 00000000..125813da --- /dev/null +++ b/src/RoomDirectoryModel.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "MatrixClient.h" +#include +#include + +#include "Logging.h" + +namespace mtx::http { +using RequestErr = const std::optional &; +} +namespace mtx::responses { +struct PublicRooms; +} + +class RoomDirectoryModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit RoomDirectoryModel + (QObject *parent = nullptr, const std::string &s = ""); + + enum Roles { + Name = Qt::UserRole, + Id, + AvatarUrl, + Topic, + MemberCount, + Previewable + }; + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role) const override; + + inline int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void) parent; + return static_cast (publicRoomsData_.size()); + } + + inline bool canFetchMore(const QModelIndex &) const override + { + nhlog::net()->debug("determining if can fetch more"); + return canFetchMore_; + } + void fetchMore(const QModelIndex &) override; + + Q_INVOKABLE void joinRoom(const int &index = -1); + +signals: + void fetchedRoomsBatch(std::vector rooms, + const std::string &prev_batch, const std::string &next_batch); + void serverChanged(); + void searchTermEntered(); + +public slots: + void displayRooms(std::vector rooms, + const std::string &prev, const std::string &next); + void setMatrixServer(const QString &s = ""); + void setSearchTerm(const QString &f); + +private: + static constexpr size_t limit_ = 50; + + std::string server_; + std::string userSearchString_; + std::string prevBatch_; + std::string nextBatch_; + bool canFetchMore_; + std::vector publicRoomsData_; + + std::vector getViasForRoom(const std::vector &room); + void resetDisplayedData(); +}; \ No newline at end of file diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index a6922be7..2e6c45ca 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -26,6 +26,8 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" +#include "ReadReceiptsModel.h" +#include "RoomDirectoryModel.h" #include "RoomsModel.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" @@ -39,6 +41,7 @@ Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(std::vector) +Q_DECLARE_METATYPE(std::vector) namespace msgs = mtx::events::msg; @@ -150,6 +153,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType>(); + qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", 1, @@ -273,6 +278,9 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "EmojiCategory", "Error: Only enums"); + qmlRegisterType( + "im.nheko.RoomDirectoryModel", 1, 0, "RoomDirectoryModel"); + #ifdef USE_QUICK_VIEW view = new QQuickView(parent); container = QWidget::createWindowContainer(view, parent);