File indexing completed on 2024-04-28 08:52:01

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-FileCopyrightText: 2021 Alexey Rusakov <TODO>
0003 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 
0005 #include "roommanager.h"
0006 
0007 #include "chatbarcache.h"
0008 #include "enums/delegatetype.h"
0009 #include "models/timelinemodel.h"
0010 #include "neochatconfig.h"
0011 #include "neochatconnection.h"
0012 #include "neochatroom.h"
0013 
0014 #include <KLocalizedString>
0015 #include <QDesktopServices>
0016 #include <QQuickTextDocument>
0017 #include <QStandardPaths>
0018 
0019 #include <Quotient/csapi/joining.h>
0020 #include <Quotient/csapi/knocking.h>
0021 #include <Quotient/qt_connection_util.h>
0022 #include <Quotient/user.h>
0023 
0024 #ifndef Q_OS_ANDROID
0025 #include <KIO/OpenUrlJob>
0026 #endif
0027 
0028 RoomManager::RoomManager(QObject *parent)
0029     : QObject(parent)
0030     , m_currentRoom(nullptr)
0031     , m_lastCurrentRoom(nullptr)
0032     , m_config(KSharedConfig::openStateConfig())
0033     , m_timelineModel(new TimelineModel(this))
0034     , m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
0035     , m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
0036 {
0037     m_lastRoomConfig = m_config->group(QStringLiteral("LastOpenRoom"));
0038 
0039     connect(this, &RoomManager::currentRoomChanged, this, [this]() {
0040         m_timelineModel->setRoom(m_currentRoom);
0041     });
0042 }
0043 
0044 RoomManager::~RoomManager()
0045 {
0046 }
0047 
0048 RoomManager &RoomManager::instance()
0049 {
0050     static RoomManager _instance;
0051     return _instance;
0052 }
0053 
0054 NeoChatRoom *RoomManager::currentRoom() const
0055 {
0056     return m_currentRoom;
0057 }
0058 
0059 TimelineModel *RoomManager::timelineModel() const
0060 {
0061     return m_timelineModel;
0062 }
0063 
0064 MessageFilterModel *RoomManager::messageFilterModel() const
0065 {
0066     return m_messageFilterModel;
0067 }
0068 
0069 MediaMessageFilterModel *RoomManager::mediaMessageFilterModel() const
0070 {
0071     return m_mediaMessageFilterModel;
0072 }
0073 
0074 UriResolveResult RoomManager::resolveResource(const Uri &uri)
0075 {
0076     return UriResolverBase::visitResource(m_connection, uri);
0077 }
0078 
0079 void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
0080 {
0081     Uri uri{idOrUri};
0082     if (!uri.isValid()) {
0083         Q_EMIT warning(i18n("Malformed or empty Matrix id"), i18n("%1 is not a correct Matrix identifier", idOrUri));
0084         return;
0085     }
0086 
0087     if (uri.type() != Uri::NonMatrix) {
0088         if (!m_connection) {
0089             return;
0090         }
0091         if (!action.isEmpty()) {
0092             uri.setAction(action);
0093         }
0094         // TODO we should allow the user to select a connection.
0095     }
0096 
0097     const auto result = visitResource(m_connection, uri);
0098     if (result == Quotient::CouldNotResolve) {
0099         Q_EMIT warning(i18n("Room not found"), i18n("There's no room %1 in the room list. Check the spelling and the account.", idOrUri));
0100     } else { // Invalid cases should have been eliminated earlier
0101         Q_ASSERT(result == Quotient::UriResolved);
0102 
0103         if (uri.type() == Uri::RoomAlias || uri.type() == Uri::RoomId) {
0104             connectSingleShot(m_connection, &Connection::newRoom, this, [this, uri](Room *room) {
0105                 enterRoom(dynamic_cast<NeoChatRoom *>(room));
0106             });
0107         }
0108     }
0109 }
0110 
0111 void RoomManager::maximizeMedia(int index)
0112 {
0113     if (index < -1 || index > m_mediaMessageFilterModel->rowCount()) {
0114         return;
0115     }
0116     Q_EMIT showMaximizedMedia(index);
0117 }
0118 
0119 void RoomManager::requestFullScreenClose()
0120 {
0121     Q_EMIT closeFullScreen();
0122 }
0123 
0124 void RoomManager::viewEventSource(const QString &eventId)
0125 {
0126     Q_EMIT showEventSource(eventId);
0127 }
0128 
0129 void RoomManager::viewEventMenu(const QString &eventId,
0130                                 const QVariantMap &author,
0131                                 DelegateType::Type delegateType,
0132                                 const QString &plainText,
0133                                 const QString &htmlText,
0134                                 const QString &selectedText,
0135                                 const QString &mimeType,
0136                                 const FileTransferInfo &progressInfo)
0137 {
0138     if (delegateType == DelegateType::Image || delegateType == DelegateType::Video || delegateType == DelegateType::Audio
0139         || delegateType == DelegateType::File) {
0140         Q_EMIT showFileMenu(eventId, author, delegateType, plainText, mimeType, progressInfo);
0141         return;
0142     }
0143 
0144     Q_EMIT showMessageMenu(eventId, author, delegateType, plainText, htmlText, selectedText);
0145 }
0146 
0147 bool RoomManager::hasOpenRoom() const
0148 {
0149     return m_currentRoom != nullptr;
0150 }
0151 
0152 void RoomManager::setUrlArgument(const QString &arg)
0153 {
0154     m_arg = arg;
0155 }
0156 
0157 void RoomManager::loadInitialRoom()
0158 {
0159     Q_ASSERT(m_connection);
0160 
0161     if (!m_arg.isEmpty()) {
0162         resolveResource(m_arg);
0163     }
0164 
0165     if (m_currentRoom) {
0166         // we opened a room with the arg parsing already
0167         return;
0168     }
0169 
0170     openRoomForActiveConnection();
0171 
0172     connect(this, &RoomManager::connectionChanged, this, &RoomManager::openRoomForActiveConnection);
0173 }
0174 
0175 void RoomManager::openRoomForActiveConnection()
0176 {
0177     if (!m_connection) {
0178         return;
0179     }
0180     // Read from last open room
0181     QString roomId = m_lastRoomConfig.readEntry(m_connection->userId(), QString());
0182 
0183     // TODO remove legacy check at some point.
0184     if (roomId.isEmpty()) {
0185         roomId = NeoChatConfig::self()->openRoom();
0186     }
0187 
0188     if (!roomId.isEmpty()) {
0189         // Here we can cast because the controller has been configured to
0190         // return NeoChatRoom instead of simple Quotient::Room
0191         const auto room = qobject_cast<NeoChatRoom *>(m_connection->room(roomId));
0192 
0193         if (room) {
0194             if (room->isSpace()) {
0195                 enterSpaceHome(room);
0196             } else {
0197                 enterRoom(room);
0198             }
0199         }
0200     }
0201 }
0202 
0203 void RoomManager::enterRoom(NeoChatRoom *room)
0204 {
0205     if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
0206         m_currentRoom->editCache()->setEditId({});
0207     }
0208     if (m_currentRoom && m_chatDocumentHandler) {
0209         // We're doing these things here because it is critical that they are switched at the same time
0210         if (m_chatDocumentHandler->document()) {
0211             m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
0212             m_chatDocumentHandler->setRoom(room);
0213             m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText());
0214             room->mainCache()->setText(room->mainCache()->savedText());
0215         } else {
0216             m_chatDocumentHandler->setRoom(room);
0217         }
0218     }
0219     m_lastCurrentRoom = std::exchange(m_currentRoom, room);
0220     Q_EMIT currentRoomChanged();
0221 
0222     if (!m_lastCurrentRoom) {
0223         Q_EMIT pushRoom(room, QString());
0224     } else {
0225         Q_EMIT replaceRoom(m_currentRoom, QString());
0226     }
0227 
0228     if (room && room->timelineSize() == 0) {
0229         room->getPreviousContent(20);
0230     }
0231 
0232     // Save last open room
0233     m_lastRoomConfig.writeEntry(m_connection->userId(), room->id());
0234 }
0235 
0236 void RoomManager::openWindow(NeoChatRoom *room)
0237 {
0238     // forward the call to QML
0239     Q_EMIT openRoomInNewWindow(room);
0240 }
0241 
0242 void RoomManager::enterSpaceHome(NeoChatRoom *spaceRoom)
0243 {
0244     if (!spaceRoom->isSpace()) {
0245         return;
0246     }
0247     // If replacing a normal room message timeline make sure any edit is cancelled.
0248     if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
0249         m_currentRoom->editCache()->setEditId({});
0250     }
0251     // Save the chatbar text for the current room if any before switching
0252     if (m_currentRoom && m_chatDocumentHandler) {
0253         if (m_chatDocumentHandler->document()) {
0254             m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
0255         }
0256     }
0257     m_lastCurrentRoom = std::exchange(m_currentRoom, spaceRoom);
0258     Q_EMIT currentRoomChanged();
0259 
0260     if (!m_lastCurrentRoom) {
0261         Q_EMIT pushSpaceHome(spaceRoom);
0262     } else {
0263         Q_EMIT replaceSpaceHome(m_currentRoom);
0264     }
0265 
0266     // Save last open room
0267     m_lastRoomConfig.writeEntry(m_connection->userId(), spaceRoom->id());
0268 }
0269 
0270 UriResolveResult RoomManager::visitUser(User *user, const QString &action)
0271 {
0272     if (action == "mention"_ls || action.isEmpty()) {
0273         // send it has QVariantMap because the properties in the
0274         user->load();
0275         Q_EMIT showUserDetail(user);
0276     } else if (action == "_interactive"_ls) {
0277         user->requestDirectChat();
0278     } else if (action == "chat"_ls) {
0279         user->load();
0280         Q_EMIT askDirectChatConfirmation(user);
0281     } else {
0282         return Quotient::IncorrectAction;
0283     }
0284 
0285     return Quotient::UriResolved;
0286 }
0287 
0288 void RoomManager::visitRoom(Room *room, const QString &eventId)
0289 {
0290     auto neoChatRoom = qobject_cast<NeoChatRoom *>(room);
0291     Q_ASSERT(neoChatRoom != nullptr);
0292 
0293     if (m_currentRoom) {
0294         if (m_currentRoom->id() == room->id()) {
0295             Q_EMIT goToEvent(eventId);
0296         } else {
0297             m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
0298             Q_EMIT currentRoomChanged();
0299             Q_EMIT replaceRoom(neoChatRoom, eventId);
0300         }
0301     } else {
0302         m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
0303         Q_EMIT currentRoomChanged();
0304         Q_EMIT pushRoom(neoChatRoom, eventId);
0305     }
0306 }
0307 
0308 void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QStringList &viaServers)
0309 {
0310     auto job = account->joinRoom(roomAliasOrId, viaServers);
0311     connectSingleShot(job, &Quotient::BaseJob::finished, this, [this, account](Quotient::BaseJob *finish) {
0312         if (finish->status() == Quotient::BaseJob::Success) {
0313             connectSingleShot(account, &Quotient::Connection::newRoom, this, [this](Quotient::Room *room) {
0314                 enterRoom(dynamic_cast<NeoChatRoom *>(room));
0315             });
0316         } else {
0317             Q_EMIT warning(i18n("Failed to join room"), finish->errorString());
0318         }
0319     });
0320 }
0321 
0322 void RoomManager::knockRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers)
0323 {
0324     auto *const job = account->callApi<KnockRoomJob>(roomAliasOrId, viaServers, reason);
0325     // Upon completion, ensure a room object is created in case it hasn't come
0326     // with a sync yet. If the room object is not there, provideRoom() will
0327     // create it in Join state. finished() is used here instead of success()
0328     // to overtake clients that may add their own slots to finished().
0329     connectSingleShot(job, &BaseJob::finished, this, [this, job, account] {
0330         if (job->status() == Quotient::BaseJob::Success) {
0331             connectSingleShot(account, &Quotient::Connection::newRoom, this, [this](Quotient::Room *room) {
0332                 Q_EMIT currentRoom()->showMessage(NeoChatRoom::Info, i18n("You requested to join '%1'", room->name()));
0333             });
0334         } else {
0335             Q_EMIT warning(i18n("Failed to request joining room"), job->errorString());
0336         }
0337     });
0338 }
0339 
0340 bool RoomManager::visitNonMatrix(const QUrl &url)
0341 {
0342 #ifdef Q_OS_ANDROID
0343     if (!QDesktopServices::openUrl(url)) {
0344         Q_EMIT warning(i18n("No application for the link"), i18n("Your operating system could not find an application for the link."));
0345     }
0346 #else
0347     auto *job = new KIO::OpenUrlJob(url);
0348     connect(job, &KJob::finished, this, [this](KJob *job) {
0349         if (job->error()) {
0350             Q_EMIT warning(i18n("Could not open URL"), job->errorString());
0351         }
0352     });
0353     job->start();
0354 #endif
0355     return true;
0356 }
0357 
0358 void RoomManager::reset()
0359 {
0360     m_arg = QString();
0361     m_currentRoom = nullptr;
0362     m_lastCurrentRoom = nullptr;
0363     Q_EMIT currentRoomChanged();
0364 }
0365 
0366 void RoomManager::leaveRoom(NeoChatRoom *room)
0367 {
0368     if (!room) {
0369         return;
0370     }
0371     if (m_lastCurrentRoom && room->id() == m_lastCurrentRoom->id()) {
0372         m_lastCurrentRoom = nullptr;
0373     }
0374     if (m_currentRoom && m_currentRoom->id() == room->id()) {
0375         m_currentRoom = m_lastCurrentRoom;
0376         m_lastCurrentRoom = nullptr;
0377         Q_EMIT currentRoomChanged();
0378     }
0379 
0380     room->forget();
0381 }
0382 
0383 ChatDocumentHandler *RoomManager::chatDocumentHandler() const
0384 {
0385     return m_chatDocumentHandler;
0386 }
0387 
0388 void RoomManager::setChatDocumentHandler(ChatDocumentHandler *handler)
0389 {
0390     m_chatDocumentHandler = handler;
0391     m_chatDocumentHandler->setRoom(m_currentRoom);
0392     Q_EMIT chatDocumentHandlerChanged();
0393 }
0394 
0395 NeoChatConnection *RoomManager::connection() const
0396 {
0397     return m_connection;
0398 }
0399 
0400 void RoomManager::setConnection(NeoChatConnection *connection)
0401 {
0402     if (m_connection == connection) {
0403         return;
0404     }
0405     m_connection = connection;
0406     Q_EMIT connectionChanged();
0407 }
0408 
0409 #include "moc_roommanager.cpp"