File indexing completed on 2024-10-06 12:54:07
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"