/
socialnetworksyncadaptor.cpp
466 lines (413 loc) · 15.8 KB
1
2
/****************************************************************************
**
3
** Copyright (C) 2013-2014 Jolla Ltd.
4
5
** Contact: Chris Adams <chris.adams@jollamobile.com>
**
6
7
8
9
10
11
12
13
14
15
16
17
18
19
** 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
**
20
****************************************************************************/
21
22
#include "socialnetworksyncadaptor.h"
23
#include "socialdnetworkaccessmanager_p.h"
24
25
#include "trace.h"
26
#include <QtCore/QJsonDocument>
27
#include <QtCore/QTimer>
28
29
30
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
31
#include <QtSql/QSqlRecord>
32
33
#include <QtNetwork/QNetworkAccessManager>
34
#include <QtNetwork/QNetworkReply>
35
36
#include "buteosyncfw_p.h"
37
38
39
40
41
// libaccounts-qt5
#include <Accounts/Manager>
#include <Accounts/Account>
#include <Accounts/Service>
42
43
// libsocialcache
44
45
#include <socialcache/socialimagesdatabase.h>
#include <socialcache/socialnetworksyncdatabase.h>
46
47
48
49
50
51
52
53
54
55
56
57
namespace {
QStringList validDataTypesInitialiser()
{
return QStringList()
<< QStringLiteral("Contacts")
<< QStringLiteral("Calendars")
<< QStringLiteral("Notifications")
<< QStringLiteral("Images")
<< QStringLiteral("Videos")
<< QStringLiteral("Posts")
<< QStringLiteral("Messages")
58
59
<< QStringLiteral("Emails")
<< QStringLiteral("Signon");
60
61
62
63
64
}
}
SocialNetworkSyncAdaptor::SocialNetworkSyncAdaptor(const QString &serviceName,
SocialNetworkSyncAdaptor::DataType dataType,
65
QNetworkAccessManager *qnam,
66
QObject *parent)
67
: QObject(parent)
68
69
, m_dataType(dataType)
, m_accountManager(new Accounts::Manager(this))
70
, m_networkAccessManager(qnam != 0 ? qnam : new SocialdNetworkAccessManager)
71
, m_accountSyncProfile(NULL)
72
, m_syncDb(new SocialNetworkSyncDatabase())
73
, m_status(SocialNetworkSyncAdaptor::Invalid)
74
75
, m_enabled(false)
, m_syncAborted(false)
76
, m_serviceName(serviceName)
77
78
79
80
81
{
}
SocialNetworkSyncAdaptor::~SocialNetworkSyncAdaptor()
{
82
delete m_networkAccessManager;
83
delete m_accountSyncProfile;
84
delete m_syncDb;
85
86
}
87
88
89
90
// The SocialNetworkSyncAdaptor takes ownership of the sync profiles.
void SocialNetworkSyncAdaptor::setAccountSyncProfile(Buteo::SyncProfile* perAccountSyncProfile)
{
delete m_accountSyncProfile;
91
m_accountSyncProfile = perAccountSyncProfile;
92
93
}
94
95
96
97
98
99
100
101
102
103
SocialNetworkSyncAdaptor::Status SocialNetworkSyncAdaptor::status() const
{
return m_status;
}
bool SocialNetworkSyncAdaptor::enabled() const
{
return m_enabled;
}
104
QString SocialNetworkSyncAdaptor::serviceName() const
105
106
107
108
{
return m_serviceName;
}
109
110
111
112
113
bool SocialNetworkSyncAdaptor::syncAborted() const
{
return m_syncAborted;
}
114
void SocialNetworkSyncAdaptor::sync(const QString &dataType, int accountId)
115
116
{
Q_UNUSED(dataType)
117
Q_UNUSED(accountId)
118
SOCIALD_LOG_ERROR("sync() must be overridden by derived types");
119
120
}
121
122
123
124
125
126
127
void SocialNetworkSyncAdaptor::abortSync(Sync::SyncStatus status)
{
SOCIALD_LOG_INFO("forcing timeout of outstanding replies due to abort:" << status);
m_syncAborted = true;
triggerReplyTimeouts();
}
128
129
130
131
132
133
134
135
/*!
* \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.
*/
136
bool SocialNetworkSyncAdaptor::checkAccount(Accounts::Account *account)
137
{
138
bool globallyEnabled = account->enabled();
139
Accounts::Service srv(m_accountManager->service(syncServiceName()));
140
if (!srv.isValid()) {
141
142
SOCIALD_LOG_INFO("invalid service" << syncServiceName() <<
"specified, account" << account->id() <<
143
"will be disabled for" << m_serviceName << dataTypeName(m_dataType) << "sync");
144
145
146
147
148
149
return false;
}
account->selectService(srv);
bool serviceEnabled = account->enabled();
account->selectService(Accounts::Service());
return globallyEnabled && serviceEnabled;
150
151
}
152
153
154
155
156
157
158
159
160
161
162
/*!
\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()
{
}
163
164
165
166
/*!
\internal
Called when the semaphores decreased to 0, this method is used
to finalize something, like saving all data to a database.
167
168
169
170
171
172
173
174
175
176
177
178
179
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.
180
*/
181
void SocialNetworkSyncAdaptor::finalize(int accountId)
182
{
183
Q_UNUSED(accountId)
184
185
}
186
187
188
189
190
191
/*!
\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.
*/
192
193
194
QDateTime SocialNetworkSyncAdaptor::lastSyncTimestamp(const QString &serviceName,
const QString &dataType,
int accountId) const
195
{
196
return m_syncDb->lastSyncTimestamp(serviceName, dataType, accountId);
197
198
199
200
201
202
}
/*!
\internal
Updates the last sync timestamp for the given service, account and data type to the given \a timestamp.
*/
203
204
205
206
bool SocialNetworkSyncAdaptor::updateLastSyncTimestamp(const QString &serviceName,
const QString &dataType,
int accountId,
const QDateTime ×tamp)
207
{
208
209
210
// Workaround
// TODO: do better, with a queue
m_syncDb->addSyncTimestamp(serviceName, dataType, accountId, timestamp);
211
212
213
m_syncDb->commit();
m_syncDb->wait();
return m_syncDb->writeStatus() == AbstractSocialCacheDatabase::Finished;
214
215
216
}
/*!
217
218
\internal
Returns the list of identifiers of accounts which have been synced for
219
the given \a dataType.
220
*/
221
QList<int> SocialNetworkSyncAdaptor::syncedAccounts(const QString &dataType)
222
{
223
return m_syncDb->syncedAccounts(m_serviceName, dataType);
224
225
}
226
227
228
229
/*!
* \internal
* Changes status if there is real change and emits statusChanged() signal.
*/
230
void SocialNetworkSyncAdaptor::setStatus(Status status)
231
232
233
234
235
236
{
if (m_status != status) {
m_status = status;
emit statusChanged();
}
}
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*!
* \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;
}
}
254
255
256
257
258
259
260
261
262
263
/*!
* \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()
{
264
finalCleanup();
265
SOCIALD_LOG_INFO("Finished" << m_serviceName << SocialNetworkSyncAdaptor::dataTypeName(m_dataType) <<
266
"sync at:" << QDateTime::currentDateTime().toString(Qt::ISODate));
267
268
269
setStatus(SocialNetworkSyncAdaptor::Inactive);
}
270
271
272
273
274
void SocialNetworkSyncAdaptor::incrementSemaphore(int accountId)
{
int semaphoreValue = m_accountSyncSemaphores.value(accountId);
semaphoreValue += 1;
m_accountSyncSemaphores.insert(accountId, semaphoreValue);
275
SOCIALD_LOG_DEBUG("incremented busy semaphore for account" << accountId << "to:" << semaphoreValue);
276
277
278
279
280
}
void SocialNetworkSyncAdaptor::decrementSemaphore(int accountId)
{
if (!m_accountSyncSemaphores.contains(accountId)) {
281
SOCIALD_LOG_ERROR("no such semaphore for account" << accountId);
282
283
284
285
286
return;
}
int semaphoreValue = m_accountSyncSemaphores.value(accountId);
semaphoreValue -= 1;
287
SOCIALD_LOG_DEBUG("decremented busy semaphore for account" << accountId << "to:" << semaphoreValue);
288
if (semaphoreValue < 0) {
289
SOCIALD_LOG_ERROR("busy semaphore is negative for account" << accountId);
290
291
292
293
294
return;
}
m_accountSyncSemaphores.insert(accountId, semaphoreValue);
if (semaphoreValue == 0) {
295
296
297
298
299
300
301
302
finalize(accountId);
// With the newer implementation, in finalize we can rereaise 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;
}
303
304
305
// finished all outstanding sync requests for this account.
// update the sync time in the global sociald database.
306
updateLastSyncTimestamp(m_serviceName,
307
SocialNetworkSyncAdaptor::dataTypeName(m_dataType), accountId,
308
QDateTime::currentDateTime().toTimeSpec(Qt::UTC));
309
310
311
312
313
314
315
316
317
318
319
320
321
// 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) {
322
setFinishedInactive(); // Finished!
323
324
325
}
}
}
326
327
328
329
330
331
332
void SocialNetworkSyncAdaptor::timeoutReply()
{
QTimer *timer = qobject_cast<QTimer*>(sender());
QNetworkReply *reply = timer->property("networkReply").value<QNetworkReply*>();
int accountId = timer->property("accountId").toInt();
333
SOCIALD_LOG_ERROR("network request timed out while performing sync with account" << accountId);
334
335
336
337
338
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();
339
340
}
341
void SocialNetworkSyncAdaptor::setupReplyTimeout(int accountId, QNetworkReply *reply, int msecs)
342
343
344
345
{
// this function should be called whenever a new network request is performed.
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
346
timer->setInterval(msecs);
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
timer->setProperty("accountId", accountId);
timer->setProperty("networkReply", QVariant::fromValue<QNetworkReply*>(reply));
connect(timer, SIGNAL(timeout()), this, SLOT(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);
}
366
367
368
369
370
371
372
373
374
375
376
377
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();
}
}
}
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
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();
}
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/*
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");
426
case SocialNetworkSyncAdaptor::Signon: return QStringLiteral("Signon");
427
case SocialNetworkSyncAdaptor::Backup: return QStringLiteral("Backup");
428
429
430
431
432
default: break;
}
return QString();
}
433
434
435
436
437
438
439
440
441
void SocialNetworkSyncAdaptor::purgeCachedImages(SocialImagesDatabase *database,
int accountId)
{
database->queryImages(accountId);
database->wait();
QList<SocialImage::ConstPtr> images = database->images();
foreach (SocialImage::ConstPtr image, images) {
442
SOCIALD_LOG_DEBUG("Purge cached image " << image->imageFile() << " for account " << image->accountId());
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
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) {
459
SOCIALD_LOG_DEBUG("Purge expired image " << image->imageFile() << " for account " << image->accountId());
460
461
462
463
464
465
466
QFile::remove(image->imageFile());
}
database->removeImages(images);
database->commit();
database->wait();
}