[OpenDocString] kdeconnect-kde (cpp)
contactsplugin.cpp
ContactsPlugin::ContactsPlugin(QObject *parent, const QVariantList &args)
    : KdeConnectPlugin(parent, args)
    , vcardsPath(QString(*vcardsLocation).append(QStringLiteral("/kdeconnect-").append(device()->id())))
{
    // Register custom types with dbus
    qRegisterMetaType("uID");
    qDBusRegisterMetaType();

    qRegisterMetaType("uIDList_t");
    qDBusRegisterMetaType();

    // Create the storage directory if it doesn't exist
    if (!QDir().mkpath(vcardsPath)) {
        qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "Unable to create VCard directory";
    }

    qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Contacts constructor for device " << device()->name();
}
Constructs a new plugin object and registers custom types with kdeconnect, and creates the vcards directory.
void ContactsPlugin::connected()
{
    synchronizeRemoteWithLocal();
}
This synchronizes with the remote node and the local node.
bool ContactsPlugin::receivePacket(const NetworkPacket &np)
{
    // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Packet Received for device " << device()->name();
    // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << np.body();

    if (np.type() == PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS) {
        return handleResponseUIDsTimestamps(np);
    } else if (np.type() == PACKET_TYPE_CONTACTS_RESPONSE_VCARDS) {
        return handleResponseVCards(np);
    } else {
        // Is this check necessary?
        qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Unknown packet type received from device: " << device()->name() << ". Maybe you need to upgrade KDE Connect?";
        return false;
    }
}
It receives a NetworkPacket object np and processes it. It returns true on the first successful attempt. If the packet type is not a package type, it returns false.
void ContactsPlugin::synchronizeRemoteWithLocal()
{
    sendRequest(PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP);
}
This sends a sync request on the remote node and the local node.
bool ContactsPlugin::handleResponseUIDsTimestamps(const NetworkPacket &np)
{
    if (!np.has(QStringLiteral("uids"))) {
        qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:"
                                            << "Malformed packet does not have uids key";
        return false;
    }
    uIDList_t uIDsToUpdate;
    QDir vcardsDir(vcardsPath);

    // Get a list of all file info in this directory
    // Clean out IDs returned from the remote. Anything leftover should be deleted
    QFileInfoList localVCards = vcardsDir.entryInfoList({QStringLiteral("*.vcard"), QStringLiteral("*.vcf")});

    const QStringList &uIDs = np.get(QStringLiteral("uids"));

    // Check local storage for the contacts:
    //  If the contact is not found in local storage, request its vcard be sent
    //  If the contact is in local storage but not reported, delete it
    //  If the contact is in local storage, compare its timestamp. If different, request the contact
    for (const QString &ID : uIDs) {
        QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION);
        QFile vcardFile(filename);

        if (!QFile().exists(filename)) {
            // We do not have a vcard for this contact. Request it.
            uIDsToUpdate.push_back(ID);
            continue;
        }

        // Remove this file from the list of known files
        QFileInfo fileInfo(vcardFile);
        localVCards.removeOne(fileInfo);

        // Check if the vcard needs to be updated
        if (!vcardFile.open(QIODevice::ReadOnly)) {
            qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:"
                                                  << "Unable to open" << filename << "to read even though it was reported to exist";
            continue;
        }

        QTextStream fileReadStream(&vcardFile);
        QString line;
        while (!fileReadStream.atEnd()) {
            fileReadStream >> line;
            // TODO: Check that the saved ID is the same as the one we were expecting. This requires parsing the VCard
            if (!line.startsWith(QStringLiteral("X-KDECONNECT-TIMESTAMP:"))) {
                continue;
            }
            QStringList parts = line.split(QLatin1Char(':'));
            QString timestamp = parts[1];

            qint64 remoteTimestamp = np.get(ID);
            qint64 localTimestamp = timestamp.toLongLong();

            if (!(localTimestamp == remoteTimestamp)) {
                uIDsToUpdate.push_back(ID);
            }
        }
    }

    // Delete all locally-known files which were not reported by the remote device
    for (const QFileInfo &unknownFile : localVCards) {
        QFile toDelete(unknownFile.filePath());
        toDelete.remove();
    }

    sendRequestWithIDs(PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS, uIDsToUpdate);

    return true;
}
This function checks if the given network packet has the uids key, and if it exists, retrieves the list of uIDs from the vcards path, and checks if the file exists, if it exists, compare its timestamp with the remote timestamp, and request the contact.
bool ContactsPlugin::handleResponseVCards(const NetworkPacket &np)
{
    if (!np.has(QStringLiteral("uids"))) {
        qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
                                            << "Malformed packet does not have uids key";
        return false;
    }

    QDir vcardsDir(vcardsPath);
    const QStringList &uIDs = np.get(QStringLiteral("uids"));

    // Loop over all IDs, extract the VCard from the packet and write the file
    for (const auto &ID : uIDs) {
        // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Got VCard:" << np.get(ID);
        QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION);
        QFile vcardFile(filename);
        bool vcardFileOpened = vcardFile.open(QIODevice::WriteOnly); // Want to smash anything that might have already been there
        if (!vcardFileOpened) {
            qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
                                                  << "Unable to open" << filename;
            continue;
        }

        QTextStream fileWriteStream(&vcardFile);
        const QString &vcard = np.get(ID);
        fileWriteStream << vcard;
    }
    qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
                                        << "Got" << uIDs.size() << "VCards";
    Q_EMIT localCacheSynchronized(uIDs);
    return true;
}
This method extracts the VCard from the network packet, and writes it to the vcard files. It returns true on the first successful attempt.
bool ContactsPlugin::sendRequest(const QString &packetType)
{
    NetworkPacket np(packetType);
    bool success = sendPacket(np);
    qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "sendRequest: Sending " << packetType << success;

    return success;
}
This sends a network packet of the given type, and returns true on the first successful attempt.
bool ContactsPlugin::sendRequestWithIDs(const QString &packetType, const uIDList_t &uIDs)
{
    NetworkPacket np(packetType);

    np.set(QStringLiteral("uids"), uIDs);
    bool success = sendPacket(np);
    return success;
}
This sends a network packet of the given type and sets the list of UIDs to the packet. It returns true on the first successful attempt.
QString ContactsPlugin::dbusPath() const
{
    return QStringLiteral("/modules/kdeconnect/devices/") + device()->id() + QStringLiteral("/contacts");
}
Returns the dbus path as a QString.