Files
neochat/src/models/publicroomlistmodel.cpp
Tobias Fella d14d576d99 Implement MSC 4228 Search Redirection
See https://github.com/matrix-org/matrix-spec-proposals/pull/4228 for details.
Since this is tricky to test without server-side support, I have added a basic implementation
to the mock server in appiumtests/login-server.py

1. Start appiumtests/login-server.py
2. Start neochat with "--test --ignore-ssl-errors" options
3. Open "Explore Rooms"
4. Search for the exact string "forbidden"
5. See new error message provided by server
2024-11-23 15:50:12 +01:00

333 lines
7.4 KiB
C++

// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "publicroomlistmodel.h"
#include "neochatconnection.h"
#include "publicroomlist_logging.h"
using namespace Quotient;
class NeoChatQueryPublicRoomsJob : public QueryPublicRoomsJob
{
public:
explicit NeoChatQueryPublicRoomsJob(const QString &server = {},
std::optional<int> limit = std::nullopt,
const QString &since = {},
const std::optional<Filter> &filter = std::nullopt,
std::optional<bool> includeAllNetworks = std::nullopt,
const QString &thirdPartyInstanceId = {})
: QueryPublicRoomsJob(server, limit, since, filter, includeAllNetworks, thirdPartyInstanceId)
{
// TODO Remove once we can use libQuotient's job directly
// This is to make libQuotient happy about results not having the "chunk" field
setExpectedKeys({});
}
};
PublicRoomListModel::PublicRoomListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
NeoChatConnection *PublicRoomListModel::connection() const
{
return m_connection;
}
void PublicRoomListModel::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
beginResetModel();
nextBatch = QString();
attempted = false;
rooms.clear();
m_server.clear();
if (m_connection) {
m_connection->disconnect(this);
}
endResetModel();
m_connection = connection;
if (job) {
job->abandon();
job = nullptr;
Q_EMIT searchingChanged();
}
if (m_connection) {
next();
}
Q_EMIT connectionChanged();
Q_EMIT serverChanged();
}
QString PublicRoomListModel::server() const
{
return m_server;
}
void PublicRoomListModel::setServer(const QString &value)
{
if (m_server == value) {
return;
}
m_server = value;
beginResetModel();
nextBatch = QString();
attempted = false;
rooms.clear();
endResetModel();
if (job) {
job->abandon();
job = nullptr;
Q_EMIT searchingChanged();
}
if (m_connection) {
next();
}
Q_EMIT serverChanged();
}
QString PublicRoomListModel::searchText() const
{
return m_searchText;
}
void PublicRoomListModel::setSearchText(const QString &value)
{
if (m_searchText == value) {
return;
}
m_searchText = value;
Q_EMIT searchTextChanged();
nextBatch = QString();
attempted = false;
if (job) {
job->abandon();
job = nullptr;
Q_EMIT searchingChanged();
}
}
bool PublicRoomListModel::showOnlySpaces() const
{
return m_showOnlySpaces;
}
void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
{
if (showOnlySpaces == m_showOnlySpaces) {
return;
}
m_showOnlySpaces = showOnlySpaces;
Q_EMIT showOnlySpacesChanged();
nextBatch = QString();
attempted = false;
if (job) {
job->abandon();
job = nullptr;
Q_EMIT searchingChanged();
}
}
void PublicRoomListModel::search(int limit)
{
if (limit < 1 || attempted) {
return;
}
if (job) {
qCDebug(PublicRoomList) << "Other job running, ignore";
return;
}
next(limit);
}
void PublicRoomListModel::next(int limit)
{
if (m_connection == nullptr || limit < 1) {
return;
}
m_redirectedText.clear();
Q_EMIT redirectedChanged();
if (job) {
qCDebug(PublicRoomList) << "Other job running, ignore";
return;
}
QStringList roomTypes;
if (m_showOnlySpaces) {
roomTypes += QLatin1String("m.space");
}
job = m_connection->callApi<NeoChatQueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] {
if (!attempted) {
beginResetModel();
rooms.clear();
endResetModel();
attempted = true;
}
if (job->status() == BaseJob::Success) {
nextBatch = job->nextBatch();
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
rooms.append(job->chunk());
this->endInsertRows();
} else if (job->error() == BaseJob::ContentAccessError) {
m_redirectedText = job->jsonData()[u"error"_s].toString();
Q_EMIT redirectedChanged();
}
this->job = nullptr;
Q_EMIT searchingChanged();
});
}
QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= rooms.count()) {
qCDebug(PublicRoomList) << "something's wrong: index.row() >= rooms.count()";
return {};
}
auto room = rooms.at(index.row());
if (role == DisplayNameRole) {
auto displayName = room.name;
if (!displayName.isEmpty()) {
return displayName;
}
displayName = room.canonicalAlias;
if (!displayName.isEmpty()) {
return displayName;
}
if (!displayName.isEmpty()) {
return displayName;
}
return room.roomId;
}
if (role == AvatarUrlRole) {
auto avatarUrl = room.avatarUrl;
if (avatarUrl.isEmpty() || !m_connection) {
return QUrl();
}
return m_connection->makeMediaUrl(avatarUrl);
}
if (role == TopicRole) {
return room.topic;
}
if (role == RoomIdRole) {
return room.roomId;
}
if (role == AliasRole) {
if (!room.canonicalAlias.isEmpty()) {
return room.canonicalAlias;
}
return {};
}
if (role == MemberCountRole) {
return room.numJoinedMembers;
}
if (role == AllowGuestsRole) {
return room.guestCanJoin;
}
if (role == WorldReadableRole) {
return room.worldReadable;
}
if (role == IsJoinedRole) {
if (!m_connection) {
return {};
}
return m_connection->room(room.roomId, JoinState::Join) != nullptr;
}
if (role == IsSpaceRole) {
return room.roomType == QLatin1String("m.space");
}
return {};
}
QHash<int, QByteArray> PublicRoomListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DisplayNameRole] = "displayName";
roles[AvatarUrlRole] = "avatarUrl";
roles[TopicRole] = "topic";
roles[RoomIdRole] = "roomId";
roles[MemberCountRole] = "memberCount";
roles[AllowGuestsRole] = "allowGuests";
roles[WorldReadableRole] = "worldReadable";
roles[IsJoinedRole] = "isJoined";
roles[IsSpaceRole] = "isSpace";
roles[AliasRole] = "alias";
return roles;
}
int PublicRoomListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return rooms.count();
}
bool PublicRoomListModel::canFetchMore(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return !nextBatch.isEmpty();
}
void PublicRoomListModel::fetchMore(const QModelIndex &parent)
{
Q_UNUSED(parent)
next();
}
bool PublicRoomListModel::searching() const
{
return job != nullptr;
}
QString PublicRoomListModel::redirectedText() const
{
return m_redirectedText;
}
#include "moc_publicroomlistmodel.cpp"