diff options
Diffstat (limited to 'buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp')
| -rw-r--r-- | buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp b/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp new file mode 100644 index 0000000..4451d5f --- /dev/null +++ b/buteo-plugins/buteo-common/socialnetworksyncadaptor.cpp @@ -0,0 +1,470 @@ +/**************************************************************************** + ** + ** Copyright (C) 2013-2014 Jolla Ltd. + ** Contact: Chris Adams <chris.adams@jollamobile.com> + ** + ** This program/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. + ** + ** This program/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 program/library; if not, write to the Free + ** Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + ** 02110-1301 USA + ** + ****************************************************************************/ + +#include "socialnetworksyncadaptor.h" +#include "socialdnetworkaccessmanager_p.h" +#include "trace.h" + +#include <QtCore/QJsonDocument> +#include <QtCore/QTimer> +#include <QtSql/QSqlDatabase> +#include <QtSql/QSqlQuery> +#include <QtSql/QSqlError> +#include <QtSql/QSqlRecord> + +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> + +#include "buteosyncfw_p.h" + +// libaccounts-qt5 +#include <Accounts/Manager> +#include <Accounts/Account> +#include <Accounts/Service> + +// libsocialcache +#include <socialcache/socialimagesdatabase.h> +#include <socialcache/socialnetworksyncdatabase.h> + +namespace { + QStringList validDataTypesInitialiser() + { + return QStringList() + << QStringLiteral("Contacts") + << QStringLiteral("Calendars") + << QStringLiteral("Notifications") + << QStringLiteral("Images") + << QStringLiteral("Videos") + << QStringLiteral("Posts") + << QStringLiteral("Messages") + << QStringLiteral("Emails") + << QStringLiteral("Signon") + << QStringLiteral("Backup") + << QStringLiteral("BackupQuery") + << QStringLiteral("BackupRestore"); + } +} + +SocialNetworkSyncAdaptor::SocialNetworkSyncAdaptor(const QString &serviceName, + SocialNetworkSyncAdaptor::DataType dataType, + QNetworkAccessManager *qnam, + QObject *parent) + : QObject(parent) + , m_dataType(dataType) + , m_accountManager(new Accounts::Manager(this)) + , m_networkAccessManager(qnam != 0 ? qnam : new SocialdNetworkAccessManager) + , m_accountSyncProfile(NULL) + , m_syncDb(new SocialNetworkSyncDatabase()) + , m_status(SocialNetworkSyncAdaptor::Invalid) + , m_enabled(false) + , m_syncAborted(false) + , m_serviceName(serviceName) +{ +} + +SocialNetworkSyncAdaptor::~SocialNetworkSyncAdaptor() +{ + delete m_networkAccessManager; + delete m_accountSyncProfile; + delete m_syncDb; +} + +// The SocialNetworkSyncAdaptor takes ownership of the sync profiles. +void SocialNetworkSyncAdaptor::setAccountSyncProfile(Buteo::SyncProfile* perAccountSyncProfile) +{ + delete m_accountSyncProfile; + m_accountSyncProfile = perAccountSyncProfile; +} + +SocialNetworkSyncAdaptor::Status SocialNetworkSyncAdaptor::status() const +{ + return m_status; +} + +bool SocialNetworkSyncAdaptor::enabled() const +{ + return m_enabled; +} + +QString SocialNetworkSyncAdaptor::serviceName() const +{ + return m_serviceName; +} + +bool SocialNetworkSyncAdaptor::syncAborted() const +{ + return m_syncAborted; +} + +void SocialNetworkSyncAdaptor::sync(const QString &dataType, int accountId) +{ + Q_UNUSED(dataType) + Q_UNUSED(accountId) + qCWarning(lcSocialPlugin) << "sync() must be overridden by derived types"; +} + +void SocialNetworkSyncAdaptor::abortSync(Sync::SyncStatus status) +{ + qCInfo(lcSocialPlugin) << "forcing timeout of outstanding replies due to abort:" << status; + m_syncAborted = true; + triggerReplyTimeouts(); +} + +/*! + * \brief SocialNetworkSyncAdaptor::checkAccount + * \param account + * \return true if synchronization of this adaptor's datatype is enabled for the account + * + * The default implementation checks that the account is enabled + * with the accounts&sso service associated with this sync adaptor. + */ +bool SocialNetworkSyncAdaptor::checkAccount(Accounts::Account *account) +{ + bool globallyEnabled = account->enabled(); + Accounts::Service srv(m_accountManager->service(syncServiceName())); + if (!srv.isValid()) { + qCInfo(lcSocialPlugin) << "invalid service" << syncServiceName() << "specified, account" << account->id() + << "will be disabled for" << m_serviceName << dataTypeName(m_dataType) << "sync"; + return false; + } + account->selectService(srv); + bool serviceEnabled = account->enabled(); + account->selectService(Accounts::Service()); + return globallyEnabled && serviceEnabled; +} + +/*! + \internal + Called when the semaphores for all accounts have been decreased + to zero. This is the final function which is called prior to + telling buteo that the sync plugin can be destroyed. + The implementation MUST be synchronous. +*/ +void SocialNetworkSyncAdaptor::finalCleanup() +{ +} + +/*! + \internal + Called when the semaphores decreased to 0, this method is used + to finalize something, like saving all data to a database. + + You can call incrementSemaphore to perform asynchronous tasks + in this method. finalize will then be called again when the + asynchronous task is finished (and when decrementSemaphore is + called), be sure to have a condition check in order not to run + into an infinite loop. + + It is unsafe to call decrementSemaphore in this method, as + the semaphore handling method will find that the semaphore + went to 0 twice and will perform cleanup operations twice. + Please call decrementSemaphore at the end of the asynchronous + task (preferably in a slot), and only call incrementSemaphore + for asynchronous tasks. + */ +void SocialNetworkSyncAdaptor::finalize(int accountId) +{ + Q_UNUSED(accountId) +} + +/*! + \internal + Returns the last sync timestamp for the given service, account and data type. + If data from prior to this timestamp is received in subsequent requests, it does not need to be synced. + This function will return an invalid QDateTime if no synchronisation has occurred. +*/ +QDateTime SocialNetworkSyncAdaptor::lastSyncTimestamp(const QString &serviceName, + const QString &dataType, + int accountId) const +{ + return m_syncDb->lastSyncTimestamp(serviceName, dataType, accountId); +} + +/*! + \internal + Updates the last sync timestamp for the given service, account and data type to the given \a timestamp. +*/ +bool SocialNetworkSyncAdaptor::updateLastSyncTimestamp(const QString &serviceName, + const QString &dataType, + int accountId, + const QDateTime ×tamp) +{ + // Workaround + // TODO: do better, with a queue + m_syncDb->addSyncTimestamp(serviceName, dataType, accountId, timestamp); + m_syncDb->commit(); + m_syncDb->wait(); + return m_syncDb->writeStatus() == AbstractSocialCacheDatabase::Finished; +} + +/*! + \internal + Returns the list of identifiers of accounts which have been synced for + the given \a dataType. +*/ +QList<int> SocialNetworkSyncAdaptor::syncedAccounts(const QString &dataType) +{ + return m_syncDb->syncedAccounts(m_serviceName, dataType); +} + +/*! + * \internal + * Changes status if there is real change and emits statusChanged() signal. + */ +void SocialNetworkSyncAdaptor::setStatus(Status status) +{ + if (m_status != status) { + m_status = status; + emit statusChanged(); + } +} + +/*! + * \internal + * Should be used in constructors to set the initial state + * of enabled and status, without emitting signals + * + */ +void SocialNetworkSyncAdaptor::setInitialActive(bool enabled) +{ + m_enabled = enabled; + if (enabled) { + m_status = Inactive; + } else { + m_status = Invalid; + } +} + +/*! + * \internal + * Should be called by any specific sync adapter when + * they've finished syncing data. The transition from + * busy status to inactive status is what causes the + * Buteo plugin to emit the sync results (and allows + * subsequent syncs to occur). + */ +void SocialNetworkSyncAdaptor::setFinishedInactive() +{ + finalCleanup(); + qCInfo(lcSocialPlugin) << "Finished" << m_serviceName << SocialNetworkSyncAdaptor::dataTypeName(m_dataType) + << "sync at:" << QDateTime::currentDateTime().toString(Qt::ISODate); + setStatus(SocialNetworkSyncAdaptor::Inactive); +} + +void SocialNetworkSyncAdaptor::incrementSemaphore(int accountId) +{ + int semaphoreValue = m_accountSyncSemaphores.value(accountId); + semaphoreValue += 1; + m_accountSyncSemaphores.insert(accountId, semaphoreValue); + qCDebug(lcSocialPlugin) << "incremented busy semaphore for account" << accountId << "to:" << semaphoreValue; +} + +void SocialNetworkSyncAdaptor::decrementSemaphore(int accountId) +{ + if (!m_accountSyncSemaphores.contains(accountId)) { + qCWarning(lcSocialPlugin) << "no such semaphore for account" << accountId; + return; + } + + int semaphoreValue = m_accountSyncSemaphores.value(accountId); + semaphoreValue -= 1; + qCDebug(lcSocialPlugin) << "decremented busy semaphore for account" << accountId << "to:" << semaphoreValue; + if (semaphoreValue < 0) { + qCWarning(lcSocialPlugin) << "busy semaphore is negative for account" << accountId; + return; + } + m_accountSyncSemaphores.insert(accountId, semaphoreValue); + + if (semaphoreValue == 0) { + finalize(accountId); + + // With the newer implementation, in finalize we can raise semaphores, + // so if after calling finalize, the semaphore count is not the same anymore, + // we shouldn't update the sync timestamp + if (m_accountSyncSemaphores.value(accountId) > 0) { + return; + } + + // finished all outstanding sync requests for this account. + // update the sync time in the global sociald database. + updateLastSyncTimestamp(m_serviceName, + SocialNetworkSyncAdaptor::dataTypeName(m_dataType), accountId, + QDateTime::currentDateTime().toTimeSpec(Qt::UTC)); + + // if all outstanding requests for all accounts have finished, + // then update our status to Inactive / ready to handle more sync requests. + bool allAreZero = true; + QList<int> semaphores = m_accountSyncSemaphores.values(); + foreach (int sv, semaphores) { + if (sv != 0) { + allAreZero = false; + break; + } + } + + if (allAreZero) { + setFinishedInactive(); // Finished! + } + } +} + +void SocialNetworkSyncAdaptor::timeoutReply() +{ + QTimer *timer = qobject_cast<QTimer*>(sender()); + QNetworkReply *reply = timer->property("networkReply").value<QNetworkReply*>(); + int accountId = timer->property("accountId").toInt(); + + qCWarning(lcSocialPlugin) << "network request timed out while performing sync with account" << accountId; + + m_networkReplyTimeouts[accountId].remove(reply); + reply->setProperty("isError", QVariant::fromValue<bool>(true)); + reply->finished(); // invoke finished, so that the error handling there decrements the semaphore etc. + reply->disconnect(); +} + +void SocialNetworkSyncAdaptor::setupReplyTimeout(int accountId, QNetworkReply *reply, int msecs) +{ + // this function should be called whenever a new network request is performed. + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + timer->setInterval(msecs); + timer->setProperty("accountId", accountId); + timer->setProperty("networkReply", QVariant::fromValue<QNetworkReply*>(reply)); + connect(timer, &QTimer::timeout, this, &SocialNetworkSyncAdaptor::timeoutReply); + timer->start(); + m_networkReplyTimeouts[accountId].insert(reply, timer); +} + +void SocialNetworkSyncAdaptor::removeReplyTimeout(int accountId, QNetworkReply *reply) +{ + // this function should be called by the finished() handler for the reply. + QTimer *timer = m_networkReplyTimeouts[accountId].value(reply); + if (!reply) { + return; + } + + delete timer; + m_networkReplyTimeouts[accountId].remove(reply); +} + +void SocialNetworkSyncAdaptor::triggerReplyTimeouts() +{ + // if we've lost network connectivity, we should immediately timeout all replies. + Q_FOREACH (int accountId, m_networkReplyTimeouts.keys()) { + Q_FOREACH (QTimer *timer, m_networkReplyTimeouts[accountId]) { + timer->stop(); + timer->setInterval(1); + timer->start(); + } + } +} + +QJsonObject SocialNetworkSyncAdaptor::parseJsonObjectReplyData(const QByteArray &replyData, bool *ok) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(replyData); + *ok = !jsonDocument.isEmpty(); + if (*ok && jsonDocument.isObject()) { + return jsonDocument.object(); + } + *ok = false; + return QJsonObject(); +} + +QJsonArray SocialNetworkSyncAdaptor::parseJsonArrayReplyData(const QByteArray &replyData, bool *ok) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(replyData); + *ok = !jsonDocument.isEmpty(); + if (*ok && jsonDocument.isArray()) { + return jsonDocument.array(); + } + *ok = false; + return QJsonArray(); +} + +/* + Valid data types are data types which are known to the API. + Note that just because a data type is valid does not mean + that it will necessarily be supported by a given social network + sync adaptor. +*/ +QStringList SocialNetworkSyncAdaptor::validDataTypes() +{ + static QStringList retn(validDataTypesInitialiser()); + return retn; +} + +/* + String for Enum since the DBus API uses strings +*/ +QString SocialNetworkSyncAdaptor::dataTypeName(SocialNetworkSyncAdaptor::DataType t) +{ + switch (t) { + case SocialNetworkSyncAdaptor::Contacts: return QStringLiteral("Contacts"); + case SocialNetworkSyncAdaptor::Calendars: return QStringLiteral("Calendars"); + case SocialNetworkSyncAdaptor::Notifications: return QStringLiteral("Notifications"); + case SocialNetworkSyncAdaptor::Images: return QStringLiteral("Images"); + case SocialNetworkSyncAdaptor::Videos: return QStringLiteral("Videos"); + case SocialNetworkSyncAdaptor::Posts: return QStringLiteral("Posts"); + case SocialNetworkSyncAdaptor::Messages: return QStringLiteral("Messages"); + case SocialNetworkSyncAdaptor::Emails: return QStringLiteral("Emails"); + case SocialNetworkSyncAdaptor::Signon: return QStringLiteral("Signon"); + case SocialNetworkSyncAdaptor::Backup: return QStringLiteral("Backup"); + case SocialNetworkSyncAdaptor::BackupQuery: return QStringLiteral("BackupQuery"); + case SocialNetworkSyncAdaptor::BackupRestore: return QStringLiteral("BackupRestore"); + default: break; + } + + return QString(); +} + +void SocialNetworkSyncAdaptor::purgeCachedImages(SocialImagesDatabase *database, + int accountId) +{ + database->queryImages(accountId); + database->wait(); + + QList<SocialImage::ConstPtr> images = database->images(); + foreach (SocialImage::ConstPtr image, images) { + qCDebug(lcSocialPlugin) << "Purge cached image " << image->imageFile() << " for account " << image->accountId(); + QFile::remove(image->imageFile()); + } + + database->removeImages(images); + database->commit(); + database->wait(); +} + +void SocialNetworkSyncAdaptor::purgeExpiredImages(SocialImagesDatabase *database, + int accountId) +{ + database->queryExpired(accountId); + database->wait(); + + QList<SocialImage::ConstPtr> images = database->images(); + foreach (SocialImage::ConstPtr image, images) { + qCDebug(lcSocialPlugin) << "Purge expired image " << image->imageFile() << " for account " << image->accountId(); + QFile::remove(image->imageFile()); + } + + database->removeImages(images); + database->commit(); + database->wait(); +} |
