File indexing completed on 2024-12-08 04:30:52
0001 /** 0002 * SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "conversationsdbusinterface.h" 0008 #include "interfaces/conversationmessage.h" 0009 #include "interfaces/dbusinterfaces.h" 0010 0011 #include "requestconversationworker.h" 0012 0013 #include <QDBusConnection> 0014 0015 #include <core/device.h> 0016 #include <core/kdeconnectplugin.h> 0017 0018 #include "kdeconnect_conversations_debug.h" 0019 0020 QMap<QString, ConversationsDbusInterface *> ConversationsDbusInterface::liveConversationInterfaces; 0021 0022 ConversationsDbusInterface::ConversationsDbusInterface(KdeConnectPlugin *plugin) 0023 : QDBusAbstractAdaptor(const_cast<Device *>(plugin->device())) 0024 , m_device(plugin->device()->id()) 0025 , m_lastId(0) 0026 , m_smsInterface(m_device) 0027 { 0028 ConversationMessage::registerDbusType(); 0029 0030 // Check for an existing interface for the same device 0031 // If there is already an interface for this device, we can safely delete is since we have just replaced it 0032 const auto &oldInterfaceItr = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); 0033 if (oldInterfaceItr != ConversationsDbusInterface::liveConversationInterfaces.end()) { 0034 ConversationsDbusInterface *oldInterface = oldInterfaceItr.value(); 0035 oldInterface->deleteLater(); 0036 ConversationsDbusInterface::liveConversationInterfaces.erase(oldInterfaceItr); 0037 } 0038 0039 ConversationsDbusInterface::liveConversationInterfaces[m_device] = this; 0040 } 0041 0042 ConversationsDbusInterface::~ConversationsDbusInterface() 0043 { 0044 // Wake all threads which were waiting for a reply from this interface 0045 // This might result in some noise on dbus, but it's better than leaking a bunch of resources! 0046 waitingForMessagesLock.lock(); 0047 conversationsWaitingForMessages.clear(); 0048 waitingForMessages.wakeAll(); 0049 waitingForMessagesLock.unlock(); 0050 0051 // Erase this interface from the list of known interfaces 0052 const auto myIterator = ConversationsDbusInterface::liveConversationInterfaces.find(m_device); 0053 ConversationsDbusInterface::liveConversationInterfaces.erase(myIterator); 0054 } 0055 0056 QVariantList ConversationsDbusInterface::activeConversations() 0057 { 0058 QList<QVariant> toReturn; 0059 toReturn.reserve(m_conversations.size()); 0060 0061 for (auto it = m_conversations.cbegin(); it != m_conversations.cend(); ++it) { 0062 const auto &conversation = it.value().values(); 0063 if (conversation.isEmpty()) { 0064 // This should really never happen because we create a conversation at the same time 0065 // as adding a message, but better safe than sorry 0066 qCWarning(KDECONNECT_CONVERSATIONS) << "Conversation with ID" << it.key() << "is unexpectedly empty"; 0067 break; 0068 } 0069 const QVariant &message = QVariant::fromValue<ConversationMessage>(*conversation.crbegin()); 0070 toReturn.append(message); 0071 } 0072 0073 return toReturn; 0074 } 0075 0076 void ConversationsDbusInterface::requestConversation(const qint64 &conversationID, int start, int end) 0077 { 0078 if (start < 0 || end < 0) { 0079 qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" 0080 << "Start and end must be >= 0"; 0081 return; 0082 } 0083 0084 if (end - start < 0) { 0085 qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation" 0086 << "Start must be before end"; 0087 return; 0088 } 0089 0090 RequestConversationWorker *worker = new RequestConversationWorker(conversationID, start, end, this); 0091 connect(worker, &RequestConversationWorker::conversationMessageRead, this, &ConversationsDbusInterface::conversationUpdated, Qt::QueuedConnection); 0092 worker->work(); 0093 } 0094 0095 void ConversationsDbusInterface::requestAttachmentFile(const qint64 &partID, const QString &uniqueIdentifier) 0096 { 0097 m_smsInterface.getAttachment(partID, uniqueIdentifier); 0098 } 0099 0100 void ConversationsDbusInterface::addMessages(const QList<ConversationMessage> &messages) 0101 { 0102 QSet<qint64> updatedConversationIDs; 0103 0104 for (const auto &message : messages) { 0105 const qint32 &threadId = message.threadID(); 0106 0107 // We might discover that there are no new messages in this conversation, thus calling it 0108 // "updated" might turn out to be a bit misleading 0109 // However, we need to report it as updated regardless, for the case where we have already 0110 // cached every message of the conversation but we have received a request for more, otherwise 0111 // we will never respond to that request 0112 updatedConversationIDs.insert(message.threadID()); 0113 0114 if (m_known_messages[threadId].contains(message.uID())) { 0115 // This message has already been processed. Don't do anything. 0116 continue; 0117 } 0118 0119 // Store the Message in the list corresponding to its thread 0120 bool newConversation = !m_conversations.contains(threadId); 0121 const auto &threadPosition = m_conversations[threadId].insert(message.date(), message); 0122 m_known_messages[threadId].insert(message.uID()); 0123 0124 // If this message was inserted at the end of the list, it is the latest message in the conversation 0125 bool latestMessage = threadPosition == m_conversations[threadId].end() - 1; 0126 0127 // Tell the world about what just happened 0128 if (newConversation) { 0129 Q_EMIT conversationCreated(QDBusVariant(QVariant::fromValue(message))); 0130 } else if (latestMessage) { 0131 Q_EMIT conversationUpdated(QDBusVariant(QVariant::fromValue(message))); 0132 } 0133 } 0134 0135 // It feels bad to go through the set of updated conversations again, 0136 // but also there are not many times that updatedConversationIDs will be more than one 0137 for (qint64 conversationID : updatedConversationIDs) { 0138 quint64 numMessages = m_known_messages[conversationID].size(); 0139 Q_EMIT conversationLoaded(conversationID, numMessages); 0140 } 0141 0142 waitingForMessagesLock.lock(); 0143 // Remove the waiting flag for all conversations which we just processed 0144 conversationsWaitingForMessages.subtract(updatedConversationIDs); 0145 waitingForMessages.wakeAll(); 0146 waitingForMessagesLock.unlock(); 0147 } 0148 0149 void ConversationsDbusInterface::removeMessage(const QString &internalId) 0150 { 0151 // TODO: Delete the specified message from our internal structures 0152 Q_UNUSED(internalId); 0153 } 0154 0155 QList<ConversationMessage> ConversationsDbusInterface::getConversation(const qint64 &conversationID) const 0156 { 0157 return m_conversations.value(conversationID).values(); 0158 } 0159 0160 void ConversationsDbusInterface::updateConversation(const qint64 &conversationID) 0161 { 0162 waitingForMessagesLock.lock(); 0163 if (conversationsWaitingForMessages.contains(conversationID)) { 0164 // This conversation is already being waited on, don't allow more than one thread to wait at a time 0165 qCDebug(KDECONNECT_CONVERSATIONS) << "Not allowing two threads to wait for conversationID" << conversationID; 0166 waitingForMessagesLock.unlock(); 0167 return; 0168 } 0169 qCDebug(KDECONNECT_CONVERSATIONS) << "Requesting conversation with ID" << conversationID << "from remote"; 0170 conversationsWaitingForMessages.insert(conversationID); 0171 0172 // Request a window of messages 0173 qint64 rangeStartTimestamp; 0174 qint64 numberToRequest; 0175 if (m_conversations.contains(conversationID) && m_conversations[conversationID].count() > 0) { 0176 rangeStartTimestamp = m_conversations[conversationID].first().date(); // Request starting from the oldest-available message 0177 numberToRequest = m_conversations[conversationID].count(); // Request an increasing number of messages by requesting more equal to the amount we have 0178 } else { 0179 rangeStartTimestamp = -1; // Value < 0 indicates to return the newest messages 0180 numberToRequest = MIN_NUMBER_TO_REQUEST; // Start off with a small batch 0181 } 0182 if (numberToRequest < MIN_NUMBER_TO_REQUEST) { 0183 numberToRequest = MIN_NUMBER_TO_REQUEST; 0184 } 0185 m_smsInterface.requestConversation(conversationID, rangeStartTimestamp, numberToRequest); 0186 0187 while (conversationsWaitingForMessages.contains(conversationID)) { 0188 waitingForMessages.wait(&waitingForMessagesLock); 0189 } 0190 waitingForMessagesLock.unlock(); 0191 } 0192 0193 void ConversationsDbusInterface::replyToConversation(const qint64 &conversationID, const QString &message, const QVariantList &attachmentUrls) 0194 { 0195 const auto messagesList = m_conversations[conversationID]; 0196 if (messagesList.isEmpty()) { 0197 qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!"; 0198 return; 0199 } 0200 0201 const QList<ConversationAddress> &addressList = messagesList.first().addresses(); 0202 QVariantList addresses; 0203 0204 for (const auto &address : addressList) { 0205 addresses << QVariant::fromValue(address); 0206 } 0207 0208 m_smsInterface.sendSms(addresses, message, attachmentUrls, messagesList.first().subID()); 0209 } 0210 0211 void ConversationsDbusInterface::sendWithoutConversation(const QVariantList &addresses, const QString &message, const QVariantList &attachmentUrls) 0212 { 0213 m_smsInterface.sendSms(addresses, message, attachmentUrls); 0214 } 0215 0216 void ConversationsDbusInterface::requestAllConversationThreads() 0217 { 0218 // Prepare the list of conversations by requesting the first in every thread 0219 m_smsInterface.requestAllConversations(); 0220 } 0221 0222 QString ConversationsDbusInterface::newId() 0223 { 0224 return QString::number(++m_lastId); 0225 } 0226 0227 void ConversationsDbusInterface::attachmentDownloaded(const QString &filePath, const QString &fileName) 0228 { 0229 Q_EMIT attachmentReceived(filePath, fileName); 0230 } 0231 0232 #include "moc_conversationsdbusinterface.cpp"