File indexing completed on 2024-04-21 04:56:49

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 <contactsplugin.h>
0008 
0009 #include <KPluginFactory>
0010 
0011 #include <QDBusConnection>
0012 #include <QDBusMetaType>
0013 #include <QDir>
0014 #include <QFile>
0015 #include <QIODevice>
0016 
0017 #include <core/device.h>
0018 
0019 #include "plugin_contacts_debug.h"
0020 
0021 K_PLUGIN_CLASS_WITH_JSON(ContactsPlugin, "kdeconnect_contacts.json")
0022 
0023 ContactsPlugin::ContactsPlugin(QObject *parent, const QVariantList &args)
0024     : KdeConnectPlugin(parent, args)
0025     , vcardsPath(QString(*vcardsLocation).append(QStringLiteral("/kdeconnect-").append(device()->id())))
0026 {
0027     // Register custom types with dbus
0028     qRegisterMetaType<uID>("uID");
0029     qDBusRegisterMetaType<uID>();
0030 
0031     qRegisterMetaType<uIDList_t>("uIDList_t");
0032     qDBusRegisterMetaType<uIDList_t>();
0033 
0034     // Create the storage directory if it doesn't exist
0035     if (!QDir().mkpath(vcardsPath)) {
0036         qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "Unable to create VCard directory";
0037     }
0038 
0039     qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Contacts constructor for device " << device()->name();
0040 }
0041 
0042 void ContactsPlugin::connected()
0043 {
0044     synchronizeRemoteWithLocal();
0045 }
0046 
0047 void ContactsPlugin::receivePacket(const NetworkPacket &np)
0048 {
0049     // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Packet Received for device " << device()->name();
0050     // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << np.body();
0051 
0052     if (np.type() == PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS) {
0053         handleResponseUIDsTimestamps(np);
0054     } else if (np.type() == PACKET_TYPE_CONTACTS_RESPONSE_VCARDS) {
0055         handleResponseVCards(np);
0056     }
0057 }
0058 
0059 void ContactsPlugin::synchronizeRemoteWithLocal()
0060 {
0061     sendRequest(PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP);
0062 }
0063 
0064 bool ContactsPlugin::handleResponseUIDsTimestamps(const NetworkPacket &np)
0065 {
0066     if (!np.has(QStringLiteral("uids"))) {
0067         qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:"
0068                                             << "Malformed packet does not have uids key";
0069         return false;
0070     }
0071     uIDList_t uIDsToUpdate;
0072     QDir vcardsDir(vcardsPath);
0073 
0074     // Get a list of all file info in this directory
0075     // Clean out IDs returned from the remote. Anything leftover should be deleted
0076     QFileInfoList localVCards = vcardsDir.entryInfoList({QStringLiteral("*.vcard"), QStringLiteral("*.vcf")});
0077 
0078     const QStringList &uIDs = np.get<QStringList>(QStringLiteral("uids"));
0079 
0080     // Check local storage for the contacts:
0081     //  If the contact is not found in local storage, request its vcard be sent
0082     //  If the contact is in local storage but not reported, delete it
0083     //  If the contact is in local storage, compare its timestamp. If different, request the contact
0084     for (const QString &ID : uIDs) {
0085         QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION);
0086         QFile vcardFile(filename);
0087 
0088         if (!QFile().exists(filename)) {
0089             // We do not have a vcard for this contact. Request it.
0090             uIDsToUpdate.push_back(ID);
0091             continue;
0092         }
0093 
0094         // Remove this file from the list of known files
0095         QFileInfo fileInfo(vcardFile);
0096         localVCards.removeOne(fileInfo);
0097 
0098         // Check if the vcard needs to be updated
0099         if (!vcardFile.open(QIODevice::ReadOnly)) {
0100             qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseUIDsTimestamps:"
0101                                                   << "Unable to open" << filename << "to read even though it was reported to exist";
0102             continue;
0103         }
0104 
0105         QTextStream fileReadStream(&vcardFile);
0106         QString line;
0107         while (!fileReadStream.atEnd()) {
0108             fileReadStream >> line;
0109             // TODO: Check that the saved ID is the same as the one we were expecting. This requires parsing the VCard
0110             if (!line.startsWith(QStringLiteral("X-KDECONNECT-TIMESTAMP:"))) {
0111                 continue;
0112             }
0113             QStringList parts = line.split(QLatin1Char(':'));
0114             QString timestamp = parts[1];
0115 
0116             qint64 remoteTimestamp = np.get<qint64>(ID);
0117             qint64 localTimestamp = timestamp.toLongLong();
0118 
0119             if (!(localTimestamp == remoteTimestamp)) {
0120                 uIDsToUpdate.push_back(ID);
0121             }
0122         }
0123     }
0124 
0125     // Delete all locally-known files which were not reported by the remote device
0126     for (const QFileInfo &unknownFile : localVCards) {
0127         QFile toDelete(unknownFile.filePath());
0128         toDelete.remove();
0129     }
0130 
0131     sendRequestWithIDs(PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS, uIDsToUpdate);
0132 
0133     return true;
0134 }
0135 
0136 bool ContactsPlugin::handleResponseVCards(const NetworkPacket &np)
0137 {
0138     if (!np.has(QStringLiteral("uids"))) {
0139         qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
0140                                             << "Malformed packet does not have uids key";
0141         return false;
0142     }
0143 
0144     QDir vcardsDir(vcardsPath);
0145     const QStringList &uIDs = np.get<QStringList>(QStringLiteral("uids"));
0146 
0147     // Loop over all IDs, extract the VCard from the packet and write the file
0148     for (const auto &ID : uIDs) {
0149         // qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "Got VCard:" << np.get<QString>(ID);
0150         QString filename = vcardsDir.filePath(ID + VCARD_EXTENSION);
0151         QFile vcardFile(filename);
0152         bool vcardFileOpened = vcardFile.open(QIODevice::WriteOnly); // Want to smash anything that might have already been there
0153         if (!vcardFileOpened) {
0154             qCWarning(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
0155                                                   << "Unable to open" << filename;
0156             continue;
0157         }
0158 
0159         QTextStream fileWriteStream(&vcardFile);
0160         const QString &vcard = np.get<QString>(ID);
0161         fileWriteStream << vcard;
0162     }
0163     qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "handleResponseVCards:"
0164                                         << "Got" << uIDs.size() << "VCards";
0165     Q_EMIT localCacheSynchronized(uIDs);
0166     return true;
0167 }
0168 
0169 bool ContactsPlugin::sendRequest(const QString &packetType)
0170 {
0171     NetworkPacket np(packetType);
0172     bool success = sendPacket(np);
0173     qCDebug(KDECONNECT_PLUGIN_CONTACTS) << "sendRequest: Sending " << packetType << success;
0174 
0175     return success;
0176 }
0177 
0178 bool ContactsPlugin::sendRequestWithIDs(const QString &packetType, const uIDList_t &uIDs)
0179 {
0180     NetworkPacket np(packetType);
0181 
0182     np.set<uIDList_t>(QStringLiteral("uids"), uIDs);
0183     bool success = sendPacket(np);
0184     return success;
0185 }
0186 
0187 QString ContactsPlugin::dbusPath() const
0188 {
0189     return QLatin1String("/modules/kdeconnect/devices/%1/contacts").arg(device()->id());
0190 }
0191 
0192 #include "contactsplugin.moc"
0193 #include "moc_contactsplugin.cpp"