[OpenDocString] kdeconnect-kde (cpp)
conversationsdbusinterface.cpp
ConversationsDbusInterface::ConversationsDbusInterface(KdeConnectPlugin *plugin)
    : QDBusAbstractAdaptor(const_cast(plugin->device()))
    , m_device(plugin->device()->id())
    , m_lastId(0)
    , m_smsInterface(m_device)
{
    ConversationMessage::registerDbusType();

    // Check for an existing interface for the same device
    // If there is already an interface for this device, we can safely delete is since we have just replaced it
    const auto &oldInterfaceItr = ConversationsDbusInterface::liveConversationInterfaces.find(m_device);
    if (oldInterfaceItr != ConversationsDbusInterface::liveConversationInterfaces.end()) {
        ConversationsDbusInterface *oldInterface = oldInterfaceItr.value();
        oldInterface->deleteLater();
        ConversationsDbusInterface::liveConversationInterfaces.erase(oldInterfaceItr);
    }

    ConversationsDbusInterface::liveConversationInterfaces[m_device] = this;
}
This constructs a new instance of the ConversationsDbusInterface class. It takes the device object of the plugin and registers the conversation message type, and checks for an existing interface for the same device. Then it checks if the interface for the device already exists, if it exists, it deletes the old interface and erases the old one. If the interface is the first interface in the list, it is erased and added to the list.
ConversationsDbusInterface::~ConversationsDbusInterface()
{
    // Wake all threads which were waiting for a reply from this interface
    // This might result in some noise on dbus, but it's better than leaking a bunch of resources!
    waitingForMessagesLock.lock();
    conversationsWaitingForMessages.clear();
    waitingForMessages.wakeAll();
    waitingForMessagesLock.unlock();

    // Erase this interface from the list of known interfaces
    const auto myIterator = ConversationsDbusInterface::liveConversationInterfaces.find(m_device);
    ConversationsDbusInterface::liveConversationInterfaces.erase(myIterator);
}
This removes the interface from the list of known interfaces, and wakes all threads waiting for a reply.
QVariantList ConversationsDbusInterface::activeConversations()
{
    QList toReturn;
    toReturn.reserve(m_conversations.size());

    for (auto it = m_conversations.cbegin(); it != m_conversations.cend(); ++it) {
        const auto &conversation = it.value().values();
        if (conversation.isEmpty()) {
            // This should really never happen because we create a conversation at the same time
            // as adding a message, but better safe than sorry
            qCWarning(KDECONNECT_CONVERSATIONS) << "Conversation with ID" << it.key() << "is unexpectedly empty";
            break;
        }
        const QVariant &message = QVariant::fromValue(*conversation.crbegin());
        toReturn.append(message);
    }

    return toReturn;
}
This method returns a list of all active conversations.
void ConversationsDbusInterface::requestConversation(const qint64 &conversationID, int start, int end)
{
    if (start < 0 || end < 0) {
        qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation"
                                            << "Start and end must be >= 0";
        return;
    }

    if (end - start < 0) {
        qCWarning(KDECONNECT_CONVERSATIONS) << "requestConversation"
                                            << "Start must be before end";
        return;
    }

    RequestConversationWorker *worker = new RequestConversationWorker(conversationID, start, end, this);
    connect(worker, &RequestConversationWorker::conversationMessageRead, this, &ConversationsDbusInterface::conversationUpdated, Qt::QueuedConnection);
    worker->work();
}
This requests a conversation between the given id and a range of bytes. The start and end values are inclusive and are exclusive. If the start and end values are negative, the function logs a warning.
void ConversationsDbusInterface::requestAttachmentFile(const qint64 &partID, const QString &uniqueIdentifier)
{
    m_smsInterface.getAttachment(partID, uniqueIdentifier);
}
This requests that the attachment file be sent to the connected device by reading it from the internal buffer. By returning the attachment id and the unique identifier, the part id is required. By returning the attachment id, the unique identifier is required to determine the attachment's size. By returning the number of bytes the attachment was sent to the device, the unique identifier is required to determine the size of the attachment, and the number of bytes transferred. By returning the number of bytes transferred to the device, the attachment is available for the first time, the first successful attempt to read the attachment data, and receiving the attachment id, using the unique identifier, which can be used to determine the total amount of bytes transferred to the device, such as the total amount of space.
void ConversationsDbusInterface::addMessages(const QList &messages)
{
    QSet updatedConversationIDs;

    for (const auto &message : messages) {
        const qint32 &threadId = message.threadID();

        // We might discover that there are no new messages in this conversation, thus calling it
        // "updated" might turn out to be a bit misleading
        // However, we need to report it as updated regardless, for the case where we have already
        // cached every message of the conversation but we have received a request for more, otherwise
        // we will never respond to that request
        updatedConversationIDs.insert(message.threadID());

        if (m_known_messages[threadId].contains(message.uID())) {
            // This message has already been processed. Don't do anything.
            continue;
        }

        // Store the Message in the list corresponding to its thread
        bool newConversation = !m_conversations.contains(threadId);
        const auto &threadPosition = m_conversations[threadId].insert(message.date(), message);
        m_known_messages[threadId].insert(message.uID());

        // If this message was inserted at the end of the list, it is the latest message in the conversation
        bool latestMessage = threadPosition == m_conversations[threadId].end() - 1;

        // Tell the world about what just happened
        if (newConversation) {
            Q_EMIT conversationCreated(QDBusVariant(QVariant::fromValue(message)));
        } else if (latestMessage) {
            Q_EMIT conversationUpdated(QDBusVariant(QVariant::fromValue(message)));
        }
    }

    // It feels bad to go through the set of updated conversations again,
    // but also there are not many times that updatedConversationIDs will be more than one
    for (qint64 conversationID : updatedConversationIDs) {
        quint64 numMessages = m_known_messages[conversationID].size();
        Q_EMIT conversationLoaded(conversationID, numMessages);
    }

    waitingForMessagesLock.lock();
    // Remove the waiting flag for all conversations which we just processed
    conversationsWaitingForMessages.subtract(updatedConversationIDs);
    waitingForMessages.wakeAll();
    waitingForMessagesLock.unlock();
}
This adds a list of messages to the internal list of conversations, and marks them for processing. It first checks that the message has not already been processed, if it is the first message in the list, and if it is the latest message in the conversation, it will be added to the list of known messages. Next, it iterates over the messages of the given list, and, for each conversation, retrieves its number of messages from the conversation. It also checks that the message is not already in the list, if it is the latest message in the list, and marks it for emitting events. Then, the set of updated conversations are now waiting for messages to be processed. Finally, the set is updated in the list.
void ConversationsDbusInterface::removeMessage(const QString &internalId)
{
    // TODO: Delete the specified message from our internal structures
    Q_UNUSED(internalId);
}
This removes the message of the given internal id, from the internal structures.
QList ConversationsDbusInterface::getConversation(const qint64 &conversationID) const
{
    return m_conversations.value(conversationID).values();
}
Returns a list of messages associated with a given conversation id.
void ConversationsDbusInterface::updateConversation(const qint64 &conversationID)
{
    waitingForMessagesLock.lock();
    if (conversationsWaitingForMessages.contains(conversationID)) {
        // This conversation is already being waited on, don't allow more than one thread to wait at a time
        qCDebug(KDECONNECT_CONVERSATIONS) << "Not allowing two threads to wait for conversationID" << conversationID;
        waitingForMessagesLock.unlock();
        return;
    }
    qCDebug(KDECONNECT_CONVERSATIONS) << "Requesting conversation with ID" << conversationID << "from remote";
    conversationsWaitingForMessages.insert(conversationID);

    // Request a window of messages
    qint64 rangeStartTimestamp;
    qint64 numberToRequest;
    if (m_conversations.contains(conversationID) && m_conversations[conversationID].count() > 0) {
        rangeStartTimestamp = m_conversations[conversationID].first().date(); // Request starting from the oldest-available message
        numberToRequest = m_conversations[conversationID].count(); // Request an increasing number of messages by requesting more equal to the amount we have
    } else {
        rangeStartTimestamp = -1; // Value < 0 indicates to return the newest messages
        numberToRequest = MIN_NUMBER_TO_REQUEST; // Start off with a small batch
    }
    if (numberToRequest < MIN_NUMBER_TO_REQUEST) {
        numberToRequest = MIN_NUMBER_TO_REQUEST;
    }
    m_smsInterface.requestConversation(conversationID, rangeStartTimestamp, numberToRequest);

    while (conversationsWaitingForMessages.contains(conversationID)) {
        waitingForMessages.wait(&waitingForMessagesLock);
    }
    waitingForMessagesLock.unlock();
}
This requests a conversation from the remote end, and waits for the messages to arrive. It requests a window of the first available message, and increases the number of messages by requesting a minimum number. If the number is greater than the number of messages, the method returns early. Otherwise, the method requests a window of the oldest available message, and returns the newest message. If the number is greater than the number, the method requests a small number of messages, by requesting more than the amount we have. If the number is less than the amount we have, it will start from the earliest available message. If the number is greater than the minimum number of messages, the method will request a number of messages by requesting a minimum number.
void ConversationsDbusInterface::replyToConversation(const qint64 &conversationID, const QString &message, const QVariantList &attachmentUrls)
{
    const auto messagesList = m_conversations[conversationID];
    if (messagesList.isEmpty()) {
        qCWarning(KDECONNECT_CONVERSATIONS) << "Got a conversationID for a conversation with no messages!";
        return;
    }

    const QList &addressList = messagesList.first().addresses();
    QVariantList addresses;

    for (const auto &address : addressList) {
        addresses << QVariant::fromValue(address);
    }

    m_smsInterface.sendSms(addresses, message, attachmentUrls, messagesList.first().subID());
}
This sends a message to each address in the list of conversations, and sends it to the first message in the list.
void ConversationsDbusInterface::sendWithoutConversation(const QVariantList &addresses, const QString &message, const QVariantList &attachmentUrls)
{
    m_smsInterface.sendSms(addresses, message, attachmentUrls);
}
This sends a message to multiple addresses without triggering a conversation.
void ConversationsDbusInterface::requestAllConversationThreads()
{
    // Prepare the list of conversations by requesting the first in every thread
    m_smsInterface.requestAllConversations();
}
This requests the first conversation in all threads.
QString ConversationsDbusInterface::newId()
{
    return QString::number(++m_lastId);
}
Returns a new id.
void ConversationsDbusInterface::attachmentDownloaded(const QString &filePath, const QString &fileName)
{
    Q_EMIT attachmentReceived(filePath, fileName);
}
This emits an attachment downloaded signal with the given filePath and fileName.