File indexing completed on 2024-09-29 10:12:48

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 "controller.h"
0008 #include "neochatconfig.h"
0009 #include "neochatroom.h"
0010 #include <KLocalizedString>
0011 #include <QDesktopServices>
0012 #include <QQuickTextDocument>
0013 #include <QStandardPaths>
0014 
0015 #include <Quotient/csapi/joining.h>
0016 #include <Quotient/csapi/knocking.h>
0017 #include <Quotient/qt_connection_util.h>
0018 #include <Quotient/user.h>
0019 
0020 #ifndef Q_OS_ANDROID
0021 #include <KIO/OpenUrlJob>
0022 #endif
0023 
0024 RoomManager::RoomManager(QObject *parent)
0025     : QObject(parent)
0026     , m_currentRoom(nullptr)
0027     , m_lastCurrentRoom(nullptr)
0028     , m_config(KConfig("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation))
0029 {
0030     m_lastRoomConfig = m_config.group("LastOpenRoom");
0031 }
0032 
0033 RoomManager::~RoomManager()
0034 {
0035 }
0036 
0037 RoomManager &RoomManager::instance()
0038 {
0039     static RoomManager _instance;
0040     return _instance;
0041 }
0042 
0043 NeoChatRoom *RoomManager::currentRoom() const
0044 {
0045     return m_currentRoom;
0046 }
0047 
0048 void RoomManager::openResource(const QString &idOrUri, const QString &action)
0049 {
0050     Uri uri{idOrUri};
0051     if (!uri.isValid()) {
0052         Q_EMIT warning(i18n("Malformed or empty Matrix id"), i18n("%1 is not a correct Matrix identifier", idOrUri));
0053         return;
0054     }
0055     auto account = Controller::instance().activeConnection();
0056 
0057     if (uri.type() != Uri::NonMatrix) {
0058         if (!account) {
0059             return;
0060         }
0061         if (!action.isEmpty()) {
0062             uri.setAction(action);
0063         }
0064         // TODO we should allow the user to select a connection.
0065     }
0066 
0067     const auto result = visitResource(account, uri);
0068     if (result == Quotient::CouldNotResolve) {
0069         Q_EMIT warning(i18n("Room not found"), i18n("There's no room %1 in the room list. Check the spelling and the account.", idOrUri));
0070     } else { // Invalid cases should have been eliminated earlier
0071         Q_ASSERT(result == Quotient::UriResolved);
0072     }
0073 }
0074 
0075 bool RoomManager::hasOpenRoom() const
0076 {
0077     return m_currentRoom != nullptr;
0078 }
0079 
0080 void RoomManager::setUrlArgument(const QString &arg)
0081 {
0082     m_arg = arg;
0083 }
0084 
0085 void RoomManager::loadInitialRoom()
0086 {
0087     Q_ASSERT(Controller::instance().activeConnection());
0088 
0089     if (!m_arg.isEmpty()) {
0090         openResource(m_arg);
0091     }
0092 
0093     if (m_currentRoom) {
0094         // we opened a room with the arg parsing already
0095         return;
0096     }
0097 
0098     openRoomForActiveConnection();
0099 
0100     connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &RoomManager::openRoomForActiveConnection);
0101 }
0102 
0103 void RoomManager::openRoomForActiveConnection()
0104 {
0105     if (!Controller::instance().activeConnection()) {
0106         return;
0107     }
0108     // Read from last open room
0109     QString roomId = m_lastRoomConfig.readEntry(Controller::instance().activeConnection()->userId(), QString());
0110 
0111     // TODO remove legacy check at some point.
0112     if (roomId.isEmpty()) {
0113         roomId = NeoChatConfig::self()->openRoom();
0114     }
0115 
0116     if (!roomId.isEmpty()) {
0117         // Here we can cast because the controller has been configured to
0118         // return NeoChatRoom instead of simple Quotient::Room
0119         const auto room = qobject_cast<NeoChatRoom *>(Controller::instance().activeConnection()->room(roomId));
0120 
0121         if (room) {
0122             enterRoom(room);
0123         }
0124     }
0125 }
0126 
0127 void RoomManager::enterRoom(NeoChatRoom *room)
0128 {
0129     if (m_currentRoom && !m_currentRoom->chatBoxEditId().isEmpty()) {
0130         m_currentRoom->setChatBoxEditId("");
0131     }
0132     if (m_currentRoom && m_chatDocumentHandler) {
0133         // We're doing these things here because it is critical that they are switched at the same time
0134         if (m_chatDocumentHandler->document()) {
0135             m_currentRoom->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
0136             m_chatDocumentHandler->setRoom(room);
0137             m_chatDocumentHandler->document()->textDocument()->setPlainText(room->savedText());
0138         } else {
0139             m_chatDocumentHandler->setRoom(room);
0140         }
0141     }
0142     m_lastCurrentRoom = std::exchange(m_currentRoom, room);
0143     Q_EMIT currentRoomChanged();
0144 
0145     if (!m_lastCurrentRoom) {
0146         Q_EMIT pushRoom(room, QString());
0147     } else {
0148         Q_EMIT replaceRoom(m_currentRoom, QString());
0149     }
0150 
0151     if (room && room->timelineSize() == 0) {
0152         room->getPreviousContent(20);
0153     }
0154 
0155     // Save last open room
0156     m_lastRoomConfig.writeEntry(Controller::instance().activeConnection()->userId(), room->id());
0157 }
0158 
0159 void RoomManager::openWindow(NeoChatRoom *room)
0160 {
0161     // forward the call to QML
0162     Q_EMIT openRoomInNewWindow(room);
0163 }
0164 
0165 UriResolveResult RoomManager::visitUser(User *user, const QString &action)
0166 {
0167     if (action == "mention" || action.isEmpty()) {
0168         // send it has QVariantMap because the properties in the
0169         user->load();
0170         Q_EMIT showUserDetail(user);
0171     } else if (action == "_interactive") {
0172         user->requestDirectChat();
0173     } else if (action == "chat") {
0174         user->load();
0175         Q_EMIT askDirectChatConfirmation(user);
0176     } else {
0177         return Quotient::IncorrectAction;
0178     }
0179 
0180     return Quotient::UriResolved;
0181 }
0182 
0183 void RoomManager::visitRoom(Room *room, const QString &eventId)
0184 {
0185     auto neoChatRoom = qobject_cast<NeoChatRoom *>(room);
0186     Q_ASSERT(neoChatRoom != nullptr);
0187 
0188     if (m_currentRoom) {
0189         if (m_currentRoom->id() == room->id()) {
0190             Q_EMIT goToEvent(eventId);
0191         } else {
0192             m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
0193             Q_EMIT currentRoomChanged();
0194             Q_EMIT replaceRoom(neoChatRoom, eventId);
0195         }
0196     } else {
0197         m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom);
0198         Q_EMIT currentRoomChanged();
0199         Q_EMIT pushRoom(neoChatRoom, eventId);
0200     }
0201 }
0202 
0203 void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QStringList &viaServers)
0204 {
0205     auto job = account->joinRoom(QUrl::toPercentEncoding(roomAliasOrId), viaServers);
0206     connectSingleShot(job, &Quotient::BaseJob::finished, this, [this, account](Quotient::BaseJob *finish) {
0207         if (finish->status() == Quotient::BaseJob::Success) {
0208             connectSingleShot(account, &Quotient::Connection::newRoom, this, [this](Quotient::Room *room) {
0209                 enterRoom(dynamic_cast<NeoChatRoom *>(room));
0210             });
0211         } else {
0212             Q_EMIT warning(i18n("Failed to join room"), finish->errorString());
0213         }
0214     });
0215 }
0216 
0217 void RoomManager::knockRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers)
0218 {
0219     auto *const job = account->callApi<KnockRoomJob>(roomAliasOrId, viaServers, reason);
0220     // Upon completion, ensure a room object is created in case it hasn't come
0221     // with a sync yet. If the room object is not there, provideRoom() will
0222     // create it in Join state. finished() is used here instead of success()
0223     // to overtake clients that may add their own slots to finished().
0224     connectSingleShot(job, &BaseJob::finished, this, [this, job, account] {
0225         if (job->status() == Quotient::BaseJob::Success) {
0226             connectSingleShot(account, &Quotient::Connection::newRoom, this, [this](Quotient::Room *room) {
0227                 Q_EMIT currentRoom()->showMessage(NeoChatRoom::Info, i18n("You requested to join '%1'", room->name()));
0228             });
0229         } else {
0230             Q_EMIT warning(i18n("Failed to request joining room"), job->errorString());
0231         }
0232     });
0233 }
0234 
0235 bool RoomManager::visitNonMatrix(const QUrl &url)
0236 {
0237 #ifdef Q_OS_ANDROID
0238     if (!QDesktopServices::openUrl(url)) {
0239         Q_EMIT warning(i18n("No application for the link"), i18n("Your operating system could not find an application for the link."));
0240     }
0241 #else
0242     auto *job = new KIO::OpenUrlJob(url);
0243     connect(job, &KJob::finished, this, [this](KJob *job) {
0244         if (job->error()) {
0245             Q_EMIT warning(i18n("Could not open URL"), job->errorString());
0246         }
0247     });
0248     job->start();
0249 #endif
0250     return true;
0251 }
0252 
0253 void RoomManager::reset()
0254 {
0255     m_arg = QString();
0256     m_currentRoom = nullptr;
0257     m_lastCurrentRoom = nullptr;
0258     Q_EMIT currentRoomChanged();
0259 }
0260 
0261 void RoomManager::leaveRoom(NeoChatRoom *room)
0262 {
0263     if (m_lastCurrentRoom && room->id() == m_lastCurrentRoom->id()) {
0264         m_lastCurrentRoom = nullptr;
0265     }
0266     if (m_currentRoom && m_currentRoom->id() == room->id()) {
0267         m_currentRoom = m_lastCurrentRoom;
0268         m_lastCurrentRoom = nullptr;
0269 
0270         Q_EMIT currentRoomChanged();
0271     }
0272 
0273     room->forget();
0274 }
0275 
0276 ChatDocumentHandler *RoomManager::chatDocumentHandler() const
0277 {
0278     return m_chatDocumentHandler;
0279 }
0280 
0281 void RoomManager::setChatDocumentHandler(ChatDocumentHandler *handler)
0282 {
0283     m_chatDocumentHandler = handler;
0284     m_chatDocumentHandler->setRoom(m_currentRoom);
0285     Q_EMIT chatDocumentHandlerChanged();
0286 }
0287 
0288 #include "moc_roommanager.cpp"