Commit 4653c072 authored by Tomi Leppänen's avatar Tomi Leppänen

[buteo-sync-plugins-social] Add knowncontacts plugin. Fixes JB#48443

This is new plugin that can sync contacts written to ini files. It
ensures that the contacts are not synced to any cloud services.
parent ad6470b6
......@@ -6,3 +6,7 @@ Makefile
*.qm
*.moc
*.obj
/RPMS/
*.list
*-client
tests/tst_*/tst_*
......@@ -16,6 +16,7 @@ BuildRequires: pkgconfig(libsignon-qt5)
BuildRequires: pkgconfig(accounts-qt5) >= 1.13
BuildRequires: pkgconfig(socialcache) >= 0.0.48
BuildRequires: pkgconfig(libsailfishkeyprovider)
BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions)
BuildRequires: qt5-qttools-linguist
BuildRequires: ssu-devel
Requires: buteo-syncfw-qt5-msyncd
......@@ -44,7 +45,7 @@ BuildRequires: pkgconfig(libkcalcoren-qt5)
Requires: %{name} = %{version}-%{release}
%description facebook-calendars
Provides calendar synchronisation with Facebook
%{summary}.
%files facebook-calendars
#out-of-process-plugin form:
......@@ -74,7 +75,7 @@ BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions)
Requires: %{name} = %{version}-%{release}
%description facebook-contacts
Provides contact synchronisation with Facebook
%{summary}
%files facebook-contacts
#out-of-process-plugin form:
......@@ -101,7 +102,7 @@ Summary: Provides image synchronisation with Facebook
Requires: %{name} = %{version}-%{release}
%description facebook-images
Provides image synchronisation with Facebook
%{summary}.
%files facebook-images
#out-of-process-plugin form:
......@@ -129,7 +130,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description facebook-signon
Provides signon credentials refreshing with Facebook
%{summary}.
%files facebook-signon
#out-of-process-plugin form:
......@@ -159,7 +160,7 @@ BuildRequires: pkgconfig(libkcalcoren-qt5)
Requires: %{name} = %{version}-%{release}
%description google-calendars
Provides calendar synchronisation with Google
%{summary}.
%files google-calendars
#out-of-process-plugin form:
......@@ -189,7 +190,7 @@ BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions) >= 0.1.58
Requires: %{name} = %{version}-%{release}
%description google-contacts
Provides contact synchronisation with Google
%{summary}.
%files google-contacts
#out-of-process-plugin form:
......@@ -217,7 +218,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description google-signon
Provides signon credentials refreshing with Google
%{summary}.
%files google-signon
#out-of-process-plugin form:
......@@ -249,7 +250,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description twitter-notifications
Provides notification synchronisation with Twitter
%{summary}.
%files twitter-notifications
#out-of-process-plugin form:
......@@ -283,7 +284,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description twitter-posts
Provides post synchronisation with Twitter
%{summary}.
%files twitter-posts
#out-of-process-plugin form:
......@@ -312,7 +313,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description onedrive-signon
Provides signon credentials refreshing with OneDrive
%{summary}.
%files onedrive-signon
#out-of-process-plugin form:
......@@ -342,7 +343,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description vk-posts
Provides post synchronisation with VK
%{summary}.
%files vk-posts
%{_datadir}/lipstick/notificationcategories/x-nemo.social.vk.statuspost.conf
......@@ -370,7 +371,7 @@ Summary: Provides image synchronisation with Dropbox
Requires: %{name} = %{version}-%{release}
%description dropbox-images
Provides image synchronisation with Dropbox
%{summary}.
%files dropbox-images
#out-of-process-plugin form:
......@@ -396,7 +397,7 @@ Summary: Provides image synchronisation with OneDrive
Requires: %{name} = %{version}-%{release}
%description onedrive-images
Provides image synchronisation with OneDrive
%{summary}.
%files onedrive-images
#out-of-process-plugin form:
......@@ -425,7 +426,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description onedrive-backup
Provides backup-blob synchronization for OneDrive
%{summary}.
%files onedrive-backup
#out-of-process-plugin form:
......@@ -454,7 +455,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description dropbox-backup
Provides backup-blob synchronization for Dropbox
%{summary}.
%files dropbox-backup
#out-of-process-plugin form:
......@@ -484,7 +485,7 @@ BuildRequires: qt5-qttools-linguist
Requires: %{name} = %{version}-%{release}
%description vk-notifications
Provides notification synchronisation with VK
%{summary}.
%files vk-notifications
#out-of-process-plugin form:
......@@ -514,7 +515,7 @@ BuildRequires: pkgconfig(libkcalcoren-qt5)
Requires: %{name} = %{version}-%{release}
%description vk-calendars
Provides calendar synchronisation with VK
%{summary}.
%files vk-calendars
#out-of-proces-plugin form:
......@@ -544,7 +545,7 @@ BuildRequires: pkgconfig(qtcontacts-sqlite-qt5-extensions)
Requires: %{name} = %{version}-%{release}
%description vk-contacts
Provides contact synchronisation with VK
%{summary}.
%files vk-contacts
#out-of-process-plugin form:
......@@ -571,7 +572,7 @@ Summary: Provides image synchronisation with VK
Requires: %{name} = %{version}-%{release}
%description vk-images
Provides image synchronisation with VK
%{summary}.
%files vk-images
#out-of-process-plugin form:
......@@ -593,12 +594,34 @@ done
systemctl-user restart msyncd.service || :
%package knowncontacts
Summary: Store locally created contacts
%description knowncontacts
Buteo sync plugin that stores locally created contacts, such as email
recipients.
%post knowncontacts
systemctl-user try-restart msyncd.service || :
%files knowncontacts
%defattr(-,root,root,-)
#out-of-process-plugin form:
%{_libdir}/buteo-plugins-qt5/oopp/knowncontacts-client
#in-process-plugin form:
#/usr/lib/buteo-plugins-qt5/knowncontacts-client.so
%{_sysconfdir}/buteo/profiles/client/knowncontacts.xml
%{_sysconfdir}/buteo/profiles/sync/knowncontacts.Contacts.xml
%package ts-devel
Summary: Translation source for sociald
Group: System/Applications
%description ts-devel
Translation source for sociald
%{summary}.
%files ts-devel
%defattr(-,root,root,-)
......@@ -612,7 +635,7 @@ Requires: qt5-qtdeclarative-devel-tools
Requires: qt5-qtdeclarative-import-qttest
%description tests
Automatable tests for sociald
%{summary}.
%files tests
%defattr(-,root,root,-)
......
Knowncontacts Sync Plugin
=========================
This is a sync plugin for Buteo framework. It stores locally created contacts,
such as email recipients that are not to be synced elsewhere and only ever
syncs to the device side.
In this case the "server" means QSettings files containing contacts. On sync
this plugin reads the files and creates local contacts from the information.
Contact file format
-------------------
A contact in QSettings file must have an id as group name and contact
information as key value pairs. There are no requirements for the id but it
must be consistent between syncs to avoid duplicating contact information. All
keys are optional. A file may have as many contacts as needed. The file must
end with file extension `ini` and they are stored in
`~/.local/share/system/privileged/Contacts/knowncontacts`.
```ini
[john.doe.example]
FirstName=John
LastName=Doe
EmailAddress=john@example.com
```
Supported keys:
- FirstName, LastName
- EmailAddress
- Phone, HomePhone, MobilePhone
- Company, Title, Office
<?xml version="1.0" encoding="UTF-8"?>
<profile name="knowncontacts.Contacts" type="sync" >
<key name="destinationtype" value="online"/>
<key name="displayname" value="KnownContacts"/>
<key name="enabled" value="true" />
<key name="hidden" value="true" />
<key name="use_accounts" value="true" />
<profile name="knowncontacts" type="client" >
<key name="Sync Transport" value="HTTP" />
<key name="Sync Direction" value="two-way" />
<key name="conflictpolicy" value="server-wins" />
</profile>
</profile>
TARGET = knowncontacts-client
DEFINES += "CLASSNAME=KnownContactsPlugin"
DEFINES += CLASSNAME_H=\\\"knowncontactsplugin.h\\\"
knowncontacts_sync_profile.path = /etc/buteo/profiles/sync
knowncontacts_sync_profile.files = knowncontacts.Contacts.xml
knowncontacts_client_plugin_xml.path = /etc/buteo/profiles/client
knowncontacts_client_plugin_xml.files = knowncontacts.xml
HEADERS += \
knowncontactsplugin.h \
knowncontactssyncer.h
SOURCES += \
knowncontactsplugin.cpp \
knowncontactssyncer.cpp
OTHER_FILES = \
knowncontacts.Contacts.xml
knowncontacts.xml
QT -= gui
QT += dbus
CONFIG += link_pkgconfig c++14
PKGCONFIG += buteosyncfw5 Qt5Contacts qtcontacts-sqlite-qt5-extensions
QMAKE_CXXFLAGS = -Wall \
-g \
-Wno-cast-align \
-O2 -finline-functions
TEMPLATE = app
target.path = /usr/lib/buteo-plugins-qt5/oopp
DEFINES += CLIENT_PLUGIN
INCLUDE_DIR = $$system(pkg-config --cflags buteosyncfw5|cut -f2 -d'I')
HEADERS += $$INCLUDE_DIR/ButeoPluginIfaceAdaptor.h \
$$INCLUDE_DIR/PluginCbImpl.h \
$$INCLUDE_DIR/PluginServiceObj.h
SOURCES += $$INCLUDE_DIR/ButeoPluginIfaceAdaptor.cpp \
$$INCLUDE_DIR/PluginCbImpl.cpp \
$$INCLUDE_DIR/PluginServiceObj.cpp \
$$INCLUDE_DIR/plugin_main.cpp
INSTALLS += target \
knowncontacts_sync_profile \
knowncontacts_client_plugin_xml
<?xml version="1.0" encoding="UTF-8"?>
<profile name="knowncontacts" type="client" >
<key name="Sync Transport" value="HTTP" />
<key name="Sync Direction" value="two-way" />
<key name="conflictpolicy" value="server-wins" />
</profile>
/*
* Buteo sync plugin that stores locally created contacts
* Copyright (C) 2020 Open Mobile Platform LLC.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* and appearing in the file LICENSE.LGPL included in the packaging
* of this file.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <buteosyncfw5/LogMacros.h>
#include <buteosyncfw5/PluginCbInterface.h>
#include <buteosyncfw5/ProfileEngineDefs.h>
#include <buteosyncfw5/ProfileManager.h>
#include "knowncontactsplugin.h"
#include "knowncontactssyncer.h"
const auto KnownContactsSyncFolder = QStringLiteral("system/privileged/Contacts/knowncontacts");
extern "C" KnownContactsPlugin* createPlugin(const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface *cbInterface)
{
return new KnownContactsPlugin(pluginName, profile, cbInterface);
}
extern "C" void destroyPlugin(KnownContactsPlugin *client)
{
delete client;
}
KnownContactsPlugin::KnownContactsPlugin(const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface *cbInterface)
: Buteo::ClientPlugin(pluginName, profile, cbInterface)
, m_syncer(nullptr)
{
FUNCTION_CALL_TRACE;
}
KnownContactsPlugin::~KnownContactsPlugin()
{
FUNCTION_CALL_TRACE;
}
/**
* \!brief Initialize the plugin for the actual sync to happen
* This method will be invoked by the framework
*/
bool KnownContactsPlugin::init()
{
FUNCTION_CALL_TRACE;
if (!m_syncer) {
auto path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
QDir::separator() + KnownContactsSyncFolder;
m_syncer = new KnownContactsSyncer(path, this);
LOG_DEBUG("KnownContacts plugin initialized for path" << path);
}
return true;
}
/**
* \!brief Uninitializes the plugin
* This method will be invoked by the framework
*/
bool KnownContactsPlugin::uninit()
{
FUNCTION_CALL_TRACE;
delete m_syncer;
m_syncer = nullptr;
LOG_DEBUG("KnownContacts plugin uninitialized");
return true;
}
/**
* \!brief Start the actual sync. This method will be invoked by the
* framework
*/
bool KnownContactsPlugin::startSync()
{
FUNCTION_CALL_TRACE;
if (!m_syncer)
return false;
connect(m_syncer, &KnownContactsSyncer::syncSucceeded,
this, &KnownContactsPlugin::syncSucceeded);
connect(m_syncer, &KnownContactsSyncer::syncFailed,
this, &KnownContactsPlugin::syncFailed);
LOG_DEBUG("Starting sync");
// Start the actual sync
return m_syncer->startSync();
}
/**
* \!brief Aborts sync. An abort can happen due to protocol errors,
* connection failures or by the user (via a UI)
*/
void KnownContactsPlugin::abortSync(Sync::SyncStatus status)
{
Q_UNUSED(status)
FUNCTION_CALL_TRACE;
LOG_DEBUG("Aborting is not supported");
// Not supported, syncing usually takes very little time
// and there is not much to abort
}
Buteo::SyncResults KnownContactsPlugin::getSyncResults() const
{
FUNCTION_CALL_TRACE;
return m_results;
}
/**
* This method is required if a profile has been deleted. The plugin
* has to implement the necessary cleanup (like temporary data, anchors etc.)
*/
bool KnownContactsPlugin::cleanUp()
{
FUNCTION_CALL_TRACE;
bool success;
init(); // Ensure that syncer exists
success = m_syncer->purgeData();
uninit(); // Destroy syncer
return success;
}
void KnownContactsPlugin::syncSucceeded()
{
FUNCTION_CALL_TRACE;
LOG_DEBUG("Sync successful");
m_results = Buteo::SyncResults(QDateTime::currentDateTimeUtc(),
Buteo::SyncResults::SYNC_RESULT_SUCCESS,
Buteo::SyncResults::NO_ERROR);
emit success(getProfileName(), QStringLiteral("Success"));
}
void KnownContactsPlugin::syncFailed(int errorCode)
{
FUNCTION_CALL_TRACE;
LOG_DEBUG("Sync failed: " << errorCode);
m_results = Buteo::SyncResults(iProfile.lastSuccessfulSyncTime(),
Buteo::SyncResults::SYNC_RESULT_FAILED, errorCode);
emit error(getProfileName(), QStringLiteral("Failure"), errorCode);
}
/**
* Signal from the protocol engine about connectivity state changes
*/
void KnownContactsPlugin::connectivityStateChanged(Sync::ConnectivityType type, bool state)
{
Q_UNUSED(type)
Q_UNUSED(state)
FUNCTION_CALL_TRACE;
// Stub
}
/*
* Buteo sync plugin that stores locally created contacts
* Copyright (C) 2020 Open Mobile Platform LLC.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* and appearing in the file LICENSE.LGPL included in the packaging
* of this file.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef KNOWNCONTACTSPLUGIN_H
#define KNOWNCONTACTSPLUGIN_H
#include <ClientPlugin.h>
#include <SyncResults.h>
class KnownContactsSyncer;
/*! \brief Implementation for client plugin
*
*/
class KnownContactsPlugin : public Buteo::ClientPlugin
{
Q_OBJECT;
public:
/*! \brief Constructor
*
* @param pluginName Name of this client plugin
* @param profile Sync profile
* @param cbInterface Pointer to the callback interface
*/
KnownContactsPlugin(const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface *cbInterface);
/*! \brief Destructor
*
* Call uninit before destroying the object.
*/
virtual ~KnownContactsPlugin();
//! @see SyncPluginBase::init
virtual bool init();
//! @see SyncPluginBase::uninit
virtual bool uninit();
//! @see ClientPlugin::startSync
virtual bool startSync();
//! @see SyncPluginBase::abortSync
virtual void abortSync(Sync::SyncStatus status = Sync::SYNC_ABORTED);
//! @see SyncPluginBase::getSyncResults
virtual Buteo::SyncResults getSyncResults() const;
//! @see SyncPluginBase::cleanUp
virtual bool cleanUp();
public slots:
//! @see SyncPluginBase::connectivityStateChanged
virtual void connectivityStateChanged(Sync::ConnectivityType type,
bool state);
protected slots:
void syncSucceeded();
void syncFailed(int errorCode);
private:
Buteo::SyncResults m_results;
KnownContactsSyncer *m_syncer;
};
/*! \brief Creates KnownContactsPlugin client plugin
*
* @param pluginName Name of this client plugin
* @param profile Profile to use
* @param cbInterface Pointer to the callback interface
* @return Client plugin on success, otherwise NULL
*/
extern "C" KnownContactsPlugin* createPlugin(const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface *cbInterface);
/*! \brief Destroys KnownContactsPlugin client plugin
*
* @param client KnownContactsPlugin client plugin instance to destroy
*/
extern "C" void destroyPlugin(KnownContactsPlugin *client);
#endif // KNOWNCONTACTSPLUGIN_H
/*
* Buteo sync plugin that stores locally created contacts
* Copyright (C) 2020 Open Mobile Platform LLC.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* and appearing in the file LICENSE.LGPL included in the packaging
* of this file.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <buteosyncfw5/LogMacros.h>
#include <QContactEmailAddress>
#include <QContactNickname>
#include <QContactOrganization>
#include <QDir>
#include <QLockFile>
#include <QStandardPaths>
#include <qtcontacts-extensions_manager_impl.h>
#include <twowaycontactsyncadapter_impl.h>
#include "knowncontactssyncer.h"
const auto KnownContactsSyncTarget = QLatin1String("knowncontacts");
const auto AccountId = QLatin1String("0");
static void setGuid(QContact *contact, const QString &id);
static void setNames(QContact *contact, const QSettings &file);
static void setPhoneNumbers(QContact *contact, const QSettings &file);
static void setEmailAddress(QContact *contact, const QSettings &file);
static void setCompanyInfo(QContact *contact, const QSettings &file);
KnownContactsSyncer::KnownContactsSyncer(QString path, QObject *parent)
: QObject(parent)
, QtContactsSqliteExtensions::TwoWayContactSyncAdapter(KnownContactsSyncTarget)
, m_syncFolder(path)
{
FUNCTION_CALL_TRACE;
}
KnownContactsSyncer::~KnownContactsSyncer()
{
FUNCTION_CALL_TRACE;
}
bool KnownContactsSyncer::startSync()
{
FUNCTION_CALL_TRACE;
QDateTime remoteSince;
if (!initSyncAdapter(AccountId, KnownContactsSyncTarget)
|| !readSyncStateData(&remoteSince, AccountId)) {
handleSyncFailure(InitFailure);
LOG_WARNING("Can not sync knowncontacts");
return false;
}
determineRemoteChanges(remoteSince);
return true;
}
void KnownContactsSyncer::determineRemoteChanges(const QDateTime &remoteSince)
{
FUNCTION_CALL_TRACE;
QDir syncDir(m_syncFolder);
QStringList files;
for (const auto &file : syncDir.entryList(QStringList() << "*.ini", QDir::Files)) {
QFileInfo info(syncDir, file);
if (info.lastModified() >= remoteSince)
files.append(info.absoluteFilePath());
}
LOG_DEBUG(files.size() << "files to sync");
if (!QMetaObject::invokeMethod(this, "asyncDeterminedRemoteChanges",
Qt::QueuedConnection,
Q_ARG(const QStringList, files))) {
handleSyncFailure(CallFailure);
}
}
void KnownContactsSyncer::asyncDeterminedRemoteChanges(const QStringList files)
{
FUNCTION_CALL_TRACE;
QList<QContact> contacts;
for (const auto &path : files) {
QSettings file(path, QSettings::IniFormat);
contacts.append(readContacts(file));
}
LOG_DEBUG(contacts.size() << "contacts to store");
if (!storeRemoteChanges(QList<QContact>(), &contacts, AccountId)) {
handleSyncFailure(StoreChangesFailure);
return;
}
QDateTime localSince;
QList<QContact> locallyAdded, locallyModified, locallyDeleted;
determineLocalChanges(&localSince, &locallyAdded, &locallyModified, &locallyDeleted, AccountId);
if (!storeSyncStateData(AccountId)) {
handleSyncFailure(StoreStateFailure);
return;
}
for (const auto &path : files) {
if (!QLockFile(path + QStringLiteral(".lock")).tryLock()) {
LOG_DEBUG("File in use, not removing" << path);
} else if (!QFile::remove(path)) {
LOG_WARNING("Could not remove" << path);
}
}
LOG_DEBUG("knowncontacts sync finished successfully");
emit syncSucceeded();
}
template <typename T>
static inline T findDetail(QContact &contact, int field, const QString &value)
{
T result;
QList<T> details = contact.details<T>();
for (T &detail : details) {
if (detail.value(field) == value) {
result = detail;
break;
}
}
return result;
}