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"