/
caldavclient.cpp
803 lines (718 loc) · 29.7 KB
1
/*
2
* This file is part of buteo-sync-plugin-caldav package
3
*
4
* Copyright (c) 2013 - 2021 Jolla Ltd. and/or its subsidiary(-ies).
5
* Copyright (c) 2020 Open Mobile Platform LLC.
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
*
* Contributors: Mani Chandrasekar <maninc@gmail.com>
*
* 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.
*
* 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 St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include "caldavclient.h"
26
#include "propfind.h"
27
#include "notebooksyncagent.h"
28
29
30
#include <sailfishkeyprovider_iniparser.h>
31
32
33
34
#include <extendedcalendar.h>
#include <extendedstorage.h>
#include <notebook.h>
35
#include <QNetworkAccessManager>
36
37
#include <QNetworkReply>
#include <QDateTime>
38
#include <QtGlobal>
39
#include <QStandardPaths>
40
41
42
43
44
#include <Accounts/Manager>
#include <Accounts/Account>
#include <PluginCbInterface.h>
45
#include "logging.h"
46
47
48
#include <ProfileEngineDefs.h>
#include <ProfileManager.h>
49
namespace {
50
51
52
53
54
55
56
57
58
const QString cleanSyncMarkersFileDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
+ QStringLiteral("/system/privileged/Sync");
const QString cleanSyncMarkersFile = cleanSyncMarkersFileDir + QStringLiteral("/caldav.ini");
const char * const SYNC_PREV_PERIOD_KEY = "Sync Previous Months Span";
const char * const SYNC_NEXT_PERIOD_KEY = "Sync Next Months Span";
}
59
60
61
62
63
Buteo::ClientPlugin* CalDavClientLoader::createClientPlugin(
const QString& pluginName,
const Buteo::SyncProfile& profile,
Buteo::PluginCbInterface* cbInterface)
64
{
65
return new CalDavClient(pluginName, profile, cbInterface);
66
67
68
69
}
CalDavClient::CalDavClient(const QString& aPluginName,
const Buteo::SyncProfile& aProfile,
70
71
Buteo::PluginCbInterface *aCbInterface)
: ClientPlugin(aPluginName, aProfile, aCbInterface)
72
73
74
75
, mManager(0)
, mAuth(0)
, mCalendar(0)
, mStorage(0)
76
, mAccountId(0)
77
{
78
FUNCTION_CALL_TRACE(lcCalDavTrace);
79
80
}
81
82
CalDavClient::~CalDavClient()
{
83
FUNCTION_CALL_TRACE(lcCalDavTrace);
84
85
}
86
87
bool CalDavClient::init()
{
88
FUNCTION_CALL_TRACE(lcCalDavTrace);
89
90
mNAManager = new QNetworkAccessManager(this);
91
92
if (initConfig()) {
93
94
95
96
97
98
99
100
return true;
} else {
// Uninitialize everything that was initialized before failure.
uninit();
return false;
}
}
101
102
bool CalDavClient::uninit()
{
103
FUNCTION_CALL_TRACE(lcCalDavTrace);
104
105
106
return true;
}
107
108
bool CalDavClient::startSync()
{
109
FUNCTION_CALL_TRACE(lcCalDavTrace);
110
111
if (!mAuth)
112
113
return false;
114
mAuth->authenticate();
115
116
qCDebug(lcCalDav) << "Init done. Continuing with sync";
117
118
119
120
return true;
}
121
122
void CalDavClient::abortSync(Sync::SyncStatus aStatus)
{
123
Q_UNUSED(aStatus);
124
FUNCTION_CALL_TRACE(lcCalDavTrace);
125
126
127
128
129
for (NotebookSyncAgent *agent: mNotebookSyncAgents) {
disconnect(agent, &NotebookSyncAgent::finished,
this, &CalDavClient::notebookSyncFinished);
agent->abort();
}
130
syncFinished(Buteo::SyncResults::ABORTED, QLatin1String("Sync aborted"));
131
132
}
133
134
bool CalDavClient::cleanUp()
{
135
FUNCTION_CALL_TRACE(lcCalDavTrace);
136
137
138
139
140
// This function is called after the account has been deleted to allow the plugin to remove
// all the notebooks associated with the account.
QString accountIdString = iProfile.key(Buteo::KEY_ACCOUNT_ID);
141
142
int accountId = accountIdString.toInt();
if (accountId == 0) {
143
qCWarning(lcCalDav) << "profile does not specify" << Buteo::KEY_ACCOUNT_ID;
144
return false;
145
}
146
147
mAccountId = accountId;
148
mKCal::ExtendedCalendar::Ptr calendar = mKCal::ExtendedCalendar::Ptr(new mKCal::ExtendedCalendar(QTimeZone::utc()));
149
mKCal::ExtendedStorage::Ptr storage = mKCal::ExtendedCalendar::defaultStorage(calendar);
150
151
if (!storage->open()) {
calendar->close();
152
qCWarning(lcCalDav) << "unable to open calendar storage";
153
154
155
return false;
}
156
157
158
159
160
161
deleteNotebooksForAccount(accountId, calendar, storage);
storage->close();
calendar->close();
return true;
}
162
void CalDavClient::deleteNotebooksForAccount(int accountId, mKCal::ExtendedCalendar::Ptr, mKCal::ExtendedStorage::Ptr storage)
163
{
164
FUNCTION_CALL_TRACE(lcCalDavTrace);
165
166
167
168
if (storage) {
QString notebookAccountPrefix = QString::number(accountId) + "-"; // for historical reasons!
QString accountIdStr = QString::number(accountId);
169
const mKCal::Notebook::List notebookList = storage->notebooks();
170
qCDebug(lcCalDav) << "Total Number of Notebooks in device = " << notebookList.count();
171
int deletedCount = 0;
172
for (mKCal::Notebook::Ptr notebook : notebookList) {
173
174
175
176
if (notebook->account() == accountIdStr || notebook->account().startsWith(notebookAccountPrefix)) {
if (storage->deleteNotebook(notebook)) {
deletedCount++;
}
177
}
178
}
179
qCDebug(lcCalDav) << "Deleted" << deletedCount << "notebooks";
180
}
181
182
183
184
}
bool CalDavClient::cleanSyncRequired(int accountId)
{
185
186
187
static const QByteArray iniFileDir = cleanSyncMarkersFileDir.toUtf8();
static const QByteArray iniFile = cleanSyncMarkersFile.toUtf8();
188
189
190
// multiple CalDavClient processes might be spawned (e.g. sync with different accounts)
// so use a process mutex to ensure that only one will access the clean sync marker file at any time.
if (!mProcessMutex) {
191
mProcessMutex.reset(new Sailfish::KeyProvider::ProcessMutex(iniFile.constData()));
192
193
194
195
}
mProcessMutex->lock();
char *cleaned_value = SailfishKeyProvider_ini_read(
196
iniFile.constData(),
197
198
199
200
201
"General",
QStringLiteral("%1-cleaned").arg(accountId).toLatin1());
bool alreadyClean = cleaned_value != 0 && strncmp(cleaned_value, "true", 4) == 0;
free(cleaned_value);
202
203
if (!alreadyClean) {
// first, delete any data associated with this account, so this sync will be a clean sync.
204
qCWarning(lcCalDav) << "Deleting caldav notebooks associated with this account:" << accountId << "due to clean sync";
205
206
deleteNotebooksForAccount(accountId, mCalendar, mStorage);
// now delete notebooks for non-existent accounts.
207
qCWarning(lcCalDav) << "Deleting caldav notebooks associated with nonexistent accounts due to clean sync";
208
209
// a) find out which accounts are associated with each of our notebooks.
QList<int> notebookAccountIds;
210
211
const mKCal::Notebook::List allNotebooks = mStorage->notebooks();
for (mKCal::Notebook::Ptr nb : allNotebooks) {
212
213
214
215
216
217
218
219
220
221
222
QString nbAccount = nb->account();
if (!nbAccount.isEmpty() && nb->pluginName().contains(QStringLiteral("caldav"))) {
// caldav notebook->account() values used to be like: "55-/user/calendars/someCalendar"
int indexOfHyphen = nbAccount.indexOf('-');
if (indexOfHyphen > 0) {
// this is an old caldav notebook which used "accountId-remoteServerPath" form
nbAccount.chop(nbAccount.length() - indexOfHyphen);
}
bool ok = true;
int notebookAccountId = nbAccount.toInt(&ok);
if (!ok) {
223
qCWarning(lcCalDav) << "notebook account value was strange:" << nb->account() << "->" << nbAccount << "->" << "not ok";
224
} else {
225
qCWarning(lcCalDav) << "found account id:" << notebookAccountId << "for" << nb->account() << "->" << nbAccount;
226
227
228
229
230
231
232
233
if (!notebookAccountIds.contains(notebookAccountId)) {
notebookAccountIds.append(notebookAccountId);
}
}
}
}
// b) find out if any of those accounts don't exist - if not,
Accounts::AccountIdList accountIdList = mManager->accountList();
234
for (int notebookAccountId : const_cast<const QList<int>&>(notebookAccountIds)) {
235
if (!accountIdList.contains(notebookAccountId)) {
236
qCWarning(lcCalDav) << "purging notebooks for deleted caldav account" << notebookAccountId;
237
238
239
240
deleteNotebooksForAccount(notebookAccountId, mCalendar, mStorage);
}
}
241
242
// mark this account as having been cleaned.
if (SailfishKeyProvider_ini_write(
243
244
iniFileDir.constData(),
iniFile.constData(),
245
246
247
"General",
QStringLiteral("%1-cleaned").arg(accountId).toLatin1(),
"true") != 0) {
248
qCWarning(lcCalDav) << "Failed to mark account as clean! Next sync will be unnecessarily cleaned also!";
249
250
}
251
// finished; return true because this will be a clean sync.
252
qCWarning(lcCalDav) << "Finished pre-sync cleanup with caldav account" << accountId;
253
mProcessMutex->unlock();
254
return true;
255
}
256
257
mProcessMutex->unlock();
258
return false;
259
260
}
261
262
void CalDavClient::connectivityStateChanged(Sync::ConnectivityType aType, bool aState)
{
263
264
FUNCTION_CALL_TRACE(lcCalDavTrace);
qCDebug(lcCalDav) << "Received connectivity change event:" << aType << " changed to " << aState;
265
266
267
268
if (aType == Sync::CONNECTIVITY_INTERNET && !aState) {
// we lost connectivity during sync.
abortSync(Sync::SYNC_CONNECTION_ERROR);
}
269
270
}
271
Accounts::Account* CalDavClient::getAccountForCalendars(Accounts::Service *service) const
272
{
273
Accounts::Account *account = mManager->account(mAccountId);
274
if (!account) {
275
qCWarning(lcCalDav) << "cannot find account" << mAccountId;
276
277
278
return NULL;
}
if (!account->enabled()) {
279
qCWarning(lcCalDav) << "Account" << mAccountId << "is disabled!";
280
281
return NULL;
}
282
Accounts::Service calendarService;
283
284
const Accounts::ServiceList caldavServices = account->services("caldav");
for (const Accounts::Service &currService : caldavServices) {
285
account->selectService(currService);
286
if (account->value("caldav-sync/profile_id").toString() == getProfileName()) {
287
calendarService = currService;
288
289
290
break;
}
}
291
if (!calendarService.isValid()) {
292
qCWarning(lcCalDav) << "cannot find a service for account" << mAccountId << "with a valid calendar list";
293
294
295
return NULL;
}
296
account->selectService(calendarService);
297
if (!account->enabled()) {
298
qCWarning(lcCalDav) << "Account" << mAccountId << "service:" << service->name() << "is disabled!";
299
300
301
return NULL;
}
302
303
304
if (service) {
*service = calendarService;
}
305
306
307
return account;
}
308
309
310
311
312
313
314
315
316
317
318
319
struct CalendarSettings
{
public:
CalendarSettings(Accounts::Account *account)
: paths(account->value("calendars").toStringList())
, displayNames(account->value("calendar_display_names").toStringList())
, colors(account->value("calendar_colors").toStringList())
, enabled(account->value("enabled_calendars").toStringList())
{
if (enabled.count() > paths.count()
|| paths.count() != displayNames.count()
|| paths.count() != colors.count()) {
320
qCWarning(lcCalDav) << "Bad calendar data for account" << account->id();
321
322
323
324
325
326
paths.clear();
displayNames.clear();
colors.clear();
enabled.clear();
}
};
327
328
// Constructs a list of CalendarInfo from value stored in settings.
QList<PropFind::CalendarInfo> toCalendars()
329
{
330
QList<PropFind::CalendarInfo> allCalendarInfo;
331
for (int i = 0; i < paths.count(); i++) {
332
333
allCalendarInfo << PropFind::CalendarInfo(paths[i],
displayNames[i], colors[i]);
334
335
336
337
338
339
340
341
}
return allCalendarInfo;
};
QList<PropFind::CalendarInfo> enabledCalendars(const QList<PropFind::CalendarInfo> &calendars)
{
QList<PropFind::CalendarInfo> filteredCalendarInfo;
for (const PropFind::CalendarInfo &info : calendars) {
if (!enabled.contains(info.remotePath)) {
342
343
continue;
}
344
filteredCalendarInfo << info;
345
}
346
return filteredCalendarInfo;
347
};
348
void add(const PropFind::CalendarInfo &infos)
349
350
351
352
353
354
{
paths.append(infos.remotePath);
enabled.append(infos.remotePath);
displayNames.append(infos.displayName);
colors.append(infos.color);
};
355
bool update(const PropFind::CalendarInfo &infos, bool &modified)
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
{
int i = paths.indexOf(infos.remotePath);
if (i < 0) {
return false;
}
if (displayNames[i] != infos.displayName || colors[i] != infos.color) {
displayNames[i] = infos.displayName;
colors[i] = infos.color;
modified = true;
}
return true;
};
bool remove(const QString &path)
{
int at = paths.indexOf(path);
if (at >= 0) {
paths.removeAt(at);
enabled.removeAll(path);
displayNames.removeAt(at);
colors.removeAt(at);
}
return (at >= 0);
}
void store(Accounts::Account *account, const Accounts::Service &srv)
{
account->selectService(srv);
account->setValue("calendars", paths);
account->setValue("enabled_calendars", enabled);
account->setValue("calendar_display_names", displayNames);
account->setValue("calendar_colors", colors);
account->selectService(Accounts::Service());
account->syncAndBlock();
};
private:
QStringList paths;
QStringList displayNames;
QStringList colors;
QStringList enabled;
};
396
QList<PropFind::CalendarInfo> CalDavClient::loadAccountCalendars() const
397
{
398
Accounts::Service srv;
399
Accounts::Account *account = getAccountForCalendars(&srv);
400
401
if (!account) {
return QList<PropFind::CalendarInfo>();
402
}
403
struct CalendarSettings calendarSettings(account);
404
405
account->selectService(Accounts::Service());
406
return calendarSettings.enabledCalendars(calendarSettings.toCalendars());
407
408
}
409
QList<PropFind::CalendarInfo> CalDavClient::mergeAccountCalendars(const QList<PropFind::CalendarInfo> &calendars) const
410
411
{
Accounts::Service srv;
412
Accounts::Account *account = getAccountForCalendars(&srv);
413
if (!account) {
414
return QList<PropFind::CalendarInfo>();
415
}
416
struct CalendarSettings calendarSettings(account);
417
418
419
account->selectService(Accounts::Service());
bool modified = false;
420
for (QList<PropFind::CalendarInfo>::ConstIterator it = calendars.constBegin();
421
it != calendars.constEnd(); ++it) {
422
if (!calendarSettings.update(*it, modified)) {
423
qCDebug(lcCalDav) << "Found a new upstream calendar:" << it->remotePath << it->displayName;
424
calendarSettings.add(*it);
425
426
modified = true;
} else {
427
qCDebug(lcCalDav) << "Already existing calendar:" << it->remotePath << it->displayName << it->color;
428
429
430
}
}
if (modified) {
431
qCDebug(lcCalDav) << "Store modifications to calendar settings.";
432
calendarSettings.store(account, srv);
433
}
434
435
return calendarSettings.enabledCalendars(calendars);
436
437
}
438
void CalDavClient::removeAccountCalendars(const QStringList &paths)
439
440
{
Accounts::Service srv;
441
Accounts::Account *account = getAccountForCalendars(&srv);
442
443
444
if (!account) {
return;
}
445
struct CalendarSettings calendarSettings(account);
446
447
448
449
450
account->selectService(Accounts::Service());
bool modified = false;
for (QStringList::ConstIterator it = paths.constBegin();
it != paths.constEnd(); ++it) {
451
if (calendarSettings.remove(*it)) {
452
qCDebug(lcCalDav) << "Found a deleted upstream calendar:" << *it;
453
454
455
456
modified = true;
}
}
if (modified) {
457
calendarSettings.store(account, srv);
458
459
460
}
}
461
462
bool CalDavClient::initConfig()
{
463
464
FUNCTION_CALL_TRACE(lcCalDavTrace);
qCDebug(lcCalDav) << "Initiating config...";
465
466
467
468
if (!mManager) {
mManager = new Accounts::Manager(this);
}
469
470
471
472
473
QString accountIdString = iProfile.key(Buteo::KEY_ACCOUNT_ID);
bool accountIdOk = false;
int accountId = accountIdString.toInt(&accountIdOk);
if (!accountIdOk) {
474
qCWarning(lcCalDav) << "no account id specified," << Buteo::KEY_ACCOUNT_ID << "not found in profile";
475
476
return false;
}
477
mAccountId = accountId;
478
479
Accounts::Service srv;
480
Accounts::Account *account = getAccountForCalendars(&srv);
481
if (!account) {
482
return false;
483
}
484
485
486
mSettings.setServerAddress(account->value("server_address").toString());
if (mSettings.serverAddress().isEmpty()) {
487
qCWarning(lcCalDav) << "remote_address not found in service settings";
488
return false;
489
}
490
mSettings.setDavRootPath(account->value("webdav_path").toString());
491
492
mSettings.setIgnoreSSLErrors(account->value("ignore_ssl_errors").toBool());
account->selectService(Accounts::Service());
493
494
mAuth = new AuthHandler(mManager, accountId, srv.name());
495
if (!mAuth->init()) {
496
497
return false;
}
498
499
connect(mAuth, SIGNAL(success()), this, SLOT(start()));
connect(mAuth, SIGNAL(failed()), this, SLOT(authenticationError()));
500
501
mSettings.setAccountId(accountId);
502
503
504
505
506
507
508
mSyncDirection = iProfile.syncDirection();
mConflictResPolicy = iProfile.conflictResolutionPolicy();
return true;
}
509
510
void CalDavClient::syncFinished(Buteo::SyncResults::MinorCode minorErrorCode,
const QString &message)
511
{
512
FUNCTION_CALL_TRACE(lcCalDavTrace);
513
514
515
clearAgents();
516
517
518
519
520
if (mCalendar) {
mCalendar->close();
}
if (mStorage) {
mStorage->close();
521
mStorage.clear();
522
}
523
524
525
if (minorErrorCode == Buteo::SyncResults::NO_ERROR
|| minorErrorCode == Buteo::SyncResults::ITEM_FAILURES) {
526
qCDebug(lcCalDav) << "CalDAV sync succeeded!" << message;
527
mResults.setMajorCode(Buteo::SyncResults::SYNC_RESULT_SUCCESS);
528
mResults.setMinorCode(minorErrorCode);
529
emit success(getProfileName(), message);
530
} else {
531
qCWarning(lcCalDav) << "CalDAV sync failed:" << minorErrorCode << message;
532
533
534
535
mResults.setMajorCode(minorErrorCode == Buteo::SyncResults::ABORTED
? Buteo::SyncResults::SYNC_RESULT_CANCELLED
: Buteo::SyncResults::SYNC_RESULT_FAILED);
mResults.setMinorCode(minorErrorCode);
536
537
if (minorErrorCode == Buteo::SyncResults::AUTHENTICATION_FAILURE) {
538
539
540
setCredentialsNeedUpdate(mSettings.accountId());
}
541
emit error(getProfileName(), message, minorErrorCode);
542
543
544
}
}
545
546
void CalDavClient::authenticationError()
{
547
548
syncFinished(Buteo::SyncResults::AUTHENTICATION_FAILURE,
QLatin1String("Authentication failed"));
549
550
}
551
Buteo::SyncProfile::SyncDirection CalDavClient::syncDirection()
552
{
553
FUNCTION_CALL_TRACE(lcCalDavTrace);
554
555
556
return mSyncDirection;
}
557
Buteo::SyncProfile::ConflictResolutionPolicy CalDavClient::conflictResolutionPolicy()
558
{
559
FUNCTION_CALL_TRACE(lcCalDavTrace);
560
561
562
return mConflictResPolicy;
}
563
Buteo::SyncResults CalDavClient::getSyncResults() const
564
{
565
FUNCTION_CALL_TRACE(lcCalDavTrace);
566
567
568
569
return mResults;
}
570
571
572
void CalDavClient::getSyncDateRange(const QDateTime &sourceDate, QDateTime *fromDateTime, QDateTime *toDateTime)
{
if (!fromDateTime || !toDateTime) {
573
qCWarning(lcCalDav) << "fromDate or toDate is invalid";
574
575
return;
}
576
577
578
579
580
581
const Buteo::Profile* client = iProfile.clientProfile();
bool valid = (client != 0);
uint prevPeriod = (valid) ? client->key(SYNC_PREV_PERIOD_KEY).toUInt(&valid) : 0;
*fromDateTime = sourceDate.addMonths((valid) ? -int(qMin(prevPeriod, uint(120))) : -6);
uint nextPeriod = (valid) ? client->key(SYNC_NEXT_PERIOD_KEY).toUInt(&valid) : 0;
*toDateTime = sourceDate.addMonths((valid) ? int(qMin(nextPeriod, uint(120))) : 12);
582
583
}
584
void CalDavClient::start()
585
{
586
FUNCTION_CALL_TRACE(lcCalDavTrace);
587
588
589
590
if (!mAuth->username().isEmpty() && !mAuth->password().isEmpty()) {
mSettings.setUsername(mAuth->username());
mSettings.setPassword(mAuth->password());
591
}
592
mSettings.setAuthToken(mAuth->token());
593
594
595
596
597
598
599
600
601
// read the calendar user address set, to get their mailto href.
PropFind *userAddressSetRequest = new PropFind(mNAManager, &mSettings, this);
connect(userAddressSetRequest, &Request::finished, [this, userAddressSetRequest] {
const QString userPrincipal = userAddressSetRequest->userPrincipal();
userAddressSetRequest->deleteLater();
if (!userPrincipal.isEmpty()) {
// determine the mailto href for this user.
mSettings.setUserPrincipal(userPrincipal);
602
603
604
605
606
PropFind *userHrefsRequest = new PropFind(mNAManager, &mSettings, this);
connect(userHrefsRequest, &Request::finished, [this, userHrefsRequest] {
userHrefsRequest->deleteLater();
mSettings.setUserMailtoHref(userHrefsRequest->userMailtoHref());
listCalendars(userHrefsRequest->userHomeHref());
607
});
608
userHrefsRequest->listUserAddressSet(userPrincipal);
609
610
611
612
613
614
615
616
} else {
// just continue normal calendar sync.
listCalendars();
}
});
userAddressSetRequest->listCurrentUserPrincipal();
}
617
void CalDavClient::listCalendars(const QString &home)
618
{
619
620
QString remoteHome(home);
if (remoteHome.isEmpty()) {
621
qCWarning(lcCalDav) << "Cannot find the calendar root for this user, guess it from account.";
622
Accounts::Service srv;
623
Accounts::Account *account = getAccountForCalendars(&srv);
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
if (!account) {
syncFinished(Buteo::SyncResults::INTERNAL_ERROR,
QLatin1String("unable to find account for calendar detection"));
return;
}
struct CalendarSettings calendarSettings(account);
QList<PropFind::CalendarInfo> allCalendarInfo = calendarSettings.toCalendars();
if (allCalendarInfo.isEmpty()) {
syncFinished(Buteo::SyncResults::INTERNAL_ERROR,
QLatin1String("no calendar listed for detection"));
return;
}
// Hacky here, try to guess the root for calendars from known
// calendar paths, by removing one level.
int lastIndex = allCalendarInfo[0].remotePath.lastIndexOf('/', -2);
remoteHome = allCalendarInfo[0].remotePath.left(lastIndex + 1);
640
641
}
PropFind *calendarRequest = new PropFind(mNAManager, &mSettings, this);
642
643
connect(calendarRequest, &Request::finished, this, [this, calendarRequest] {
calendarRequest->deleteLater();
644
645
646
if (calendarRequest->errorCode() == Buteo::SyncResults::NO_ERROR
// Request silently ignores this QNetworkReply::NetworkError
&& calendarRequest->networkError() != QNetworkReply::ContentOperationNotPermittedError) {
647
648
syncCalendars(mergeAccountCalendars(calendarRequest->calendars()));
} else {
649
qCWarning(lcCalDav) << "Cannot list calendars, fallback to stored ones in account.";
650
651
652
syncCalendars(loadAccountCalendars());
}
});
653
calendarRequest->listCalendars(remoteHome);
654
655
}
656
void CalDavClient::syncCalendars(const QList<PropFind::CalendarInfo> &allCalendarInfo)
657
658
{
if (allCalendarInfo.isEmpty()) {
659
660
syncFinished(Buteo::SyncResults::NO_ERROR,
QLatin1String("No calendars for this account"));
661
662
return;
}
663
mCalendar = mKCal::ExtendedCalendar::Ptr(new mKCal::ExtendedCalendar(QTimeZone::utc()));
664
mStorage = mKCal::ExtendedCalendar::defaultStorage(mCalendar);
665
if (!mStorage || !mStorage->open()) {
666
667
syncFinished(Buteo::SyncResults::DATABASE_FAILURE,
QLatin1String("unable to open calendar storage"));
668
669
return;
}
670
mCalendar->setUpdateLastModifiedOnChange(false);
671
672
cleanSyncRequired(mAccountId);
673
674
675
QDateTime fromDateTime;
QDateTime toDateTime;
676
getSyncDateRange(QDateTime::currentDateTime().toUTC(), &fromDateTime, &toDateTime);
677
678
// for each calendar path we need to sync:
679
// - if it is mapped to a known notebook, we need to perform quick sync
680
// - if no known notebook exists for it, we need to create one and perform clean sync
681
for (const PropFind::CalendarInfo &calendarInfo : allCalendarInfo) {
682
// TODO: could use some unused field from Notebook to store "need clean sync" flag?
683
684
NotebookSyncAgent *agent = new NotebookSyncAgent
(mCalendar, mStorage, mNAManager, &mSettings,
685
calendarInfo.remotePath, calendarInfo.readOnly, this);
686
687
688
const QString &email = (calendarInfo.userPrincipal == mSettings.userPrincipal()
|| calendarInfo.userPrincipal.isEmpty())
? mSettings.userMailtoHref() : QString();
689
690
if (!agent->setNotebookFromInfo(calendarInfo.displayName,
calendarInfo.color,
691
email,
692
693
694
calendarInfo.allowEvents,
calendarInfo.allowTodos,
calendarInfo.allowJournals,
695
696
697
698
QString::number(mAccountId),
getPluginName(),
getProfileName())) {
syncFinished(Buteo::SyncResults::DATABASE_FAILURE,
699
QLatin1String("unable to load calendar storage"));
700
return;
701
}
702
703
connect(agent, &NotebookSyncAgent::finished,
this, &CalDavClient::notebookSyncFinished);
704
705
mNotebookSyncAgents.append(agent);
706
707
708
agent->startSync(fromDateTime, toDateTime,
mSyncDirection != Buteo::SyncProfile::SYNC_DIRECTION_FROM_REMOTE,
mSyncDirection != Buteo::SyncProfile::SYNC_DIRECTION_TO_REMOTE);
709
}
710
if (mNotebookSyncAgents.isEmpty()) {
711
712
syncFinished(Buteo::SyncResults::INTERNAL_ERROR,
QLatin1String("Could not add or find existing notebooks for this account"));
713
}
714
}
715
716
void CalDavClient::clearAgents()
717
{
718
FUNCTION_CALL_TRACE(lcCalDavTrace);
719
720
721
for (int i=0; i<mNotebookSyncAgents.count(); i++) {
mNotebookSyncAgents[i]->deleteLater();
722
}
723
mNotebookSyncAgents.clear();
724
725
}
726
void CalDavClient::notebookSyncFinished()
727
{
728
729
FUNCTION_CALL_TRACE(lcCalDavTrace);
qCInfo(lcCalDav) << "Notebook sync finished. Total agents:" << mNotebookSyncAgents.count();
730
731
NotebookSyncAgent *agent = qobject_cast<NotebookSyncAgent*>(sender());
732
733
734
if (!agent) {
syncFinished(Buteo::SyncResults::INTERNAL_ERROR,
QLatin1String("cannot get NotebookSyncAgent object"));
735
736
return;
}
737
agent->disconnect(this);
738
739
740
741
742
743
bool finished = true;
for (int i=0; i<mNotebookSyncAgents.count(); i++) {
if (!mNotebookSyncAgents[i]->isFinished()) {
finished = false;
break;
744
}
745
}
746
if (finished) {
747
bool hasFatalError = false;
748
bool hasDatabaseErrors = false;
749
bool hasDownloadErrors = false;
750
bool hasUploadErrors = false;
751
QStringList deletedNotebooks;
752
for (int i=0; i<mNotebookSyncAgents.count(); i++) {
753
hasFatalError = hasFatalError || !mNotebookSyncAgents[i]->isCompleted();
754
hasDownloadErrors = hasDownloadErrors || mNotebookSyncAgents[i]->hasDownloadErrors();
755
hasUploadErrors = hasUploadErrors || mNotebookSyncAgents[i]->hasUploadErrors();
756
if (!mNotebookSyncAgents[i]->applyRemoteChanges()) {
757
qCWarning(lcCalDav) << "Unable to write notebook changes for notebook at index:" << i;
758
hasDatabaseErrors = true;
759
}
760
761
if (mNotebookSyncAgents[i]->isDeleted()) {
deletedNotebooks += mNotebookSyncAgents[i]->path();
762
} else {
763
mResults.addTargetResults(mNotebookSyncAgents[i]->result());
764
}
765
mNotebookSyncAgents[i]->finalize();
766
}
767
removeAccountCalendars(deletedNotebooks);
768
if (hasFatalError) {
769
syncFinished(Buteo::SyncResults::CONNECTION_ERROR,
770
771
772
QLatin1String("unable to complete the sync process"));
} else if (hasDownloadErrors) {
syncFinished(Buteo::SyncResults::ITEM_FAILURES,
773
774
QLatin1String("unable to fetch all upstream changes"));
} else if (hasUploadErrors) {
775
syncFinished(Buteo::SyncResults::ITEM_FAILURES,
776
777
QLatin1String("unable to upsync all local changes"));
} else if (hasDatabaseErrors) {
778
syncFinished(Buteo::SyncResults::ITEM_FAILURES,
779
QLatin1String("unable to apply all remote changes"));
780
} else {
781
qCDebug(lcCalDav) << "Calendar storage saved successfully after writing notebook changes!";
782
783
syncFinished(Buteo::SyncResults::NO_ERROR);
}
784
}
785
}
786
787
788
789
790
void CalDavClient::setCredentialsNeedUpdate(int accountId)
{
Accounts::Account *account = mManager->account(accountId);
if (account) {
791
792
const Accounts::ServiceList services = account->services();
for (const Accounts::Service &currService : services) {
793
794
795
796
797
798
799
800
801
802
803
account->selectService(currService);
if (!account->value("calendars").toStringList().isEmpty()) {
account->setValue(QStringLiteral("CredentialsNeedUpdate"), QVariant::fromValue<bool>(true));
account->setValue(QStringLiteral("CredentialsNeedUpdateFrom"), QVariant::fromValue<QString>(QString::fromLatin1("caldav-sync")));
account->selectService(Accounts::Service());
account->syncAndBlock();
break;
}
}
}
}