diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(ping) add_subdirectory(clipboard) +add_subdirectory(contacts) add_subdirectory(telephony) add_subdirectory(share) add_subdirectory(notifications) diff --git a/plugins/contacts/CMakeLists.txt b/plugins/contacts/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/contacts/CMakeLists.txt @@ -0,0 +1,11 @@ +set(kdeconnect_contacts_SRCS + contactsplugin.cpp +) + +kdeconnect_add_plugin(kdeconnect_contacts JSON kdeconnect_contacts.json SOURCES ${kdeconnect_contacts_SRCS}) + +target_link_libraries(kdeconnect_contacts + kdeconnectcore + Qt5::DBus + KF5::I18n +) \ No newline at end of file diff --git a/plugins/contacts/README b/plugins/contacts/README new file mode 100644 --- /dev/null +++ b/plugins/contacts/README @@ -0,0 +1,3 @@ +This plugin allows communicating with the paired device to access its contacts +book, either by downloading the entire list of contacts or by requesting a +specific contact \ No newline at end of file diff --git a/plugins/contacts/contactsplugin.h b/plugins/contacts/contactsplugin.h new file mode 100644 --- /dev/null +++ b/plugins/contacts/contactsplugin.h @@ -0,0 +1,157 @@ +/** + * Copyright 2018 Simon Redman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CONTACTSPLUGIN_H +#define CONTACTSPLUGIN_H + +#include +#include + +#include + +/** + * Used to request the device send the unique ID and last-changed timestamp of every contact + */ +#define PACKET_TYPE_CONTACTS_REQUEST_ALL_UIDS_TIMESTAMP QStringLiteral("kdeconnect.contacts.request_all_uids_timestamps") + +/** + * Used to request the vcards for the contacts corresponding to a list of UIDs + * + * It shall contain the key "uids", which will have a list of uIDs (long int, as string) + */ +#define PACKET_TYPE_CONTACTS_REQUEST_VCARDS_BY_UIDS QStringLiteral("kdeconnect.contacts.request_vcards_by_uid") + +/** + * Response indicating the package contains a list of all contact uIDs and last-changed timestamps + * + * It shall contain the key "uids", which will mark a list of uIDs (long int, as string) + * then, for each UID, there shall be a field with the key of that UID and the value of the timestamp (int, as string) + * + * For example: + * ( 'uids' : ['1', '3', '15'], + * '1' : '973486597', + * '3' : '973485443', + * '15' : '973492390' ) + * + * The returned IDs can be used in future requests for more information about the contact + */ +#define PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS QStringLiteral("kdeconnect.contacts.response_uids_timestamps") + +/** + * Response indicating the package contains a list of contact vcards + * + * It shall contain the key "uids", which will mark a list of uIDs (long int, as string) + * then, for each UID, there shall be a field with the key of that UID and the value of the remote's vcard for that contact + * + * For example: + * ( 'uids' : ['1', '3', '15'], + * '1' : 'BEGIN:VCARD\n....\nEND:VCARD', + * '3' : 'BEGIN:VCARD\n....\nEND:VCARD', + * '15' : 'BEGIN:VCARD\n....\nEND:VCARD' ) + */ +#define PACKET_TYPE_CONTACTS_RESPONSE_VCARDS QStringLiteral("kdeconnect.contacts.response_vcards") + +/** + * Where the synchronizer will write vcards and other metadata + * TODO: Per-device folders since each device *will* have different uIDs + */ +Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation, (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard"))) + +#define VCARD_EXTENSION QStringLiteral(".vcf") +#define METADATA_EXTENSION QStringLiteral(".meta") + +typedef qint64 uID_t; + +typedef QList uIDList_t; + +class Q_DECL_EXPORT ContactsPlugin + : public KdeConnectPlugin +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.device.contacts") + +public: + explicit ContactsPlugin(QObject *parent, const QVariantList &args); + ~ContactsPlugin() override; + + bool receivePacket(const NetworkPacket& np) override; + void connected() override {} + + QString dbusPath() const override; + +protected: + /** + * Path where this instance of the plugin stores its synchronized contacts + */ + QString vcardsPath; + + +public Q_SLOTS: + + /** + * Query the remote device for all its uIDs and last-changed timestamps, then: + * Delete any contacts which are known locally but not reported by the remote + * Update any contacts which are known locally but have an older timestamp + * Add any contacts which are not known locally but are reported by the remote + */ + Q_SCRIPTABLE void synchronizeRemoteWithLocal(); + +public: Q_SIGNALS: + /** + * Emitted to indicate that we have locally cached all remote contacts + */ + Q_SCRIPTABLE void localCacheSynchronized(); + +protected: + + /** + * Handle a packet of type PACKAGE_TYPE_CONTACTS_RESPONSE_UIDS_TIMESTAMPS + * + * For every uID in the reply: + * Delete any from local storage if it does not appear in the reply + * Compare the modified timestamp for each in the reply and update any which should have changed + * Request the details any IDs which were not locally cached + */ + bool handleResponseUIDsTimestamps(const NetworkPacket&); + + /** + * Handle a packet of type PACKET_TYPE_CONTACTS_RESPONSE_VCARDS + */ + bool handleResponseVCards(const NetworkPacket&); + + /** + * Send a request-type packet which contains no body + * + * @return True if the send was successful, false otherwise + */ + bool sendRequest(QString packetType); + + /** + * Send a request-type packet which has a body with the key 'uids' and the value the list of + * specified uIDs + * + * @param packageType Type of package to send + * @param uIDs List of uIDs to request + * @return True if the send was successful, false otherwise + */ + bool sendRequestWithIDs(QString packetType, uIDList_t uIDs); +}; + +#endif // CONTACTSPLUGIN_H diff --git a/plugins/contacts/contactsplugin.cpp b/plugins/contacts/contactsplugin.cpp --- a/plugins/contacts/contactsplugin.cpp +++ b/plugins/contacts/contactsplugin.cpp @@ -120,7 +120,8 @@ // Remove this file from the list of known files QFileInfo fileInfo(vcardFile); - bool success = localVCards.removeOne(fileInfo); // TODO: Test + bool success = localVCards.removeOne(fileInfo); + Q_ASSERT(success); // We should have always been able to remove the existing file from our listing // Check if the vcard needs to be updated if (!vcardFile.open(QIODevice::ReadOnly)) @@ -134,7 +135,7 @@ while (!fileReadStream.atEnd()) { fileReadStream >> line; - // TODO: Check that the saved ID is the same as the one we were expecting + // TODO: Check that the saved ID is the same as the one we were expecting. This requires parsing the VCard if (!line.startsWith("X-KDECONNECT-TIMESTAMP:")) { continue; diff --git a/plugins/contacts/kdeconnect_contacts.json b/plugins/contacts/kdeconnect_contacts.json new file mode 100644 --- /dev/null +++ b/plugins/contacts/kdeconnect_contacts.json @@ -0,0 +1,33 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Authors": [ + { + "Email": "simon@ergotech.com", + "Name": "Simon Redman", + "Name[x-test]": "xxSimon Redmanxx" + } + ], + "Description": "Synchronize Contacts Between the Desktop and the Connected Device", + "Description[x-test]": "xxSynchronize Contacts Between the Desktop and the Connected Devicexx", + "EnabledByDefault": true, + "Icon": "dialog-ok", + "Id": "kdeconnect_contacts", + "License": "GPL", + "Name": "Contacts", + "Name[x-test]": "xxContactsxx", + "ServiceTypes": [ + "KdeConnect/Plugin" + ], + "Version": "0.1", + "Website": "http://albertvaka.wordpress.com" + }, + "X-KdeConnect-OutgoingPacketType": [ + "kdeconnect.contacts.request_all_uids_timestamps", + "kdeconnect.contacts.request_vcards_by_uid" + ], + "X-KdeConnect-SupportedPacketType": [ + "kdeconnect.contacts.response_uids_timestamps", + "kdeconnect.contacts.response_vcards" + ] +}