Akonadi

collectionsync.cpp
1/*
2 SPDX-FileCopyrightText: 2007, 2009 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "akonadicore_debug.h"
8#include "collection.h"
9#include "collectioncreatejob.h"
10#include "collectiondeletejob.h"
11#include "collectionfetchjob.h"
12#include "collectionfetchscope.h"
13#include "collectionmodifyjob.h"
14#include "collectionmovejob.h"
15#include "collectionsync_p.h"
16
17#include "cachepolicy.h"
18
19#include <KLocalizedString>
20#include <QHash>
21#include <QList>
22
23#include <functional>
24
25using namespace Akonadi;
26
27static const char CONTENTMIMETYPES[] = "CONTENTMIMETYPES";
28
29static const char ROOTPARENTRID[] = "AKONADI_ROOT_COLLECTION";
30
31class RemoteId
32{
33public:
34 explicit RemoteId()
35 {
36 }
37
38 explicit inline RemoteId(const QStringList &ridChain)
39 : ridChain(ridChain)
40 {
41 }
42
43 explicit inline RemoteId(const QString &rid)
44 {
45 ridChain.append(rid);
46 }
47
48 inline bool isAbsolute() const
49 {
50 return ridChain.last() == QString::fromLatin1(ROOTPARENTRID);
51 }
52
53 inline bool isEmpty() const
54 {
55 return ridChain.isEmpty();
56 }
57
58 inline bool operator==(const RemoteId &other) const
59 {
60 return ridChain == other.ridChain;
61 }
62
63 QStringList ridChain;
64
65 static RemoteId rootRid;
66};
67
68RemoteId RemoteId::rootRid = RemoteId(QStringList() << QString::fromLatin1(ROOTPARENTRID));
69
70Q_DECLARE_METATYPE(RemoteId)
71
72size_t qHash(const RemoteId &rid, size_t seed = 0) noexcept
73{
74 return qHashRange(rid.ridChain.constBegin(), rid.ridChain.constEnd(), seed);
75}
76
77inline bool operator<(const RemoteId &r1, const RemoteId &r2)
78{
79 if (r1.ridChain.length() == r2.ridChain.length()) {
80 auto it1 = r1.ridChain.constBegin();
81 auto end1 = r1.ridChain.constEnd();
82 auto it2 = r2.ridChain.constBegin();
83 while (it1 != end1) {
84 if ((*it1) == (*it2)) {
85 ++it1;
86 ++it2;
87 continue;
88 }
89 return (*it1) < (*it2);
90 }
91 } else {
92 return r1.ridChain.length() < r2.ridChain.length();
93 }
94 return false;
95}
96
97QDebug operator<<(QDebug s, const RemoteId &rid)
98{
99 s.nospace() << "RemoteId(" << rid.ridChain << ")";
100 return s;
101}
102
103/**
104 * @internal
105 */
106class Akonadi::CollectionSyncPrivate
107{
108public:
109 explicit CollectionSyncPrivate(CollectionSync *parent)
110 : q(parent)
111 , pendingJobs(0)
112 , progress(0)
113 , currentTransaction(nullptr)
114 , incremental(false)
115 , streaming(false)
116 , hierarchicalRIDs(false)
117 , localListDone(false)
118 , deliveryDone(false)
119 , akonadiRootCollection(Collection::root())
120 , resultEmitted(false)
121 {
122 }
123
124 ~CollectionSyncPrivate()
125 {
126 }
127
128 RemoteId remoteIdForCollection(const Collection &collection) const
129 {
130 if (collection == Collection::root()) {
131 return RemoteId::rootRid;
132 }
133
134 if (!hierarchicalRIDs) {
135 return RemoteId(collection.remoteId());
136 }
137
138 RemoteId rid;
139 Collection parent = collection;
140 while (parent.isValid() || !parent.remoteId().isEmpty()) {
141 QString prid = parent.remoteId();
142 if (prid.isEmpty() && parent.isValid()) {
143 prid = uidRidMap.value(parent.id());
144 }
145 if (prid.isEmpty()) {
146 break;
147 }
148 rid.ridChain.append(prid);
149 parent = parent.parentCollection();
150 if (parent == akonadiRootCollection) {
151 rid.ridChain.append(QString::fromLatin1(ROOTPARENTRID));
152 break;
153 }
154 }
155 return rid;
156 }
157
158 void addRemoteColection(const Collection &collection, bool removed = false)
159 {
160 QHash<RemoteId, Collection::List> &map = (removed ? removedRemoteCollections : remoteCollections);
161 const Collection parentCollection = collection.parentCollection();
162 if (parentCollection.remoteId() == akonadiRootCollection.remoteId() || parentCollection.id() == akonadiRootCollection.id()) {
163 Collection c2(collection);
164 c2.setParentCollection(akonadiRootCollection);
165 map[RemoteId::rootRid].append(c2);
166 } else {
167 Q_ASSERT(!parentCollection.remoteId().isEmpty());
168 map[remoteIdForCollection(parentCollection)].append(collection);
169 }
170 }
171
172 /* Compares collections by remoteId and falls back to name comparison in case
173 * local collection does not have remoteId (which can happen in some cases)
174 */
175 bool matchLocalAndRemoteCollection(const Collection &local, const Collection &remote)
176 {
177 if (!local.remoteId().isEmpty()) {
178 return local.remoteId() == remote.remoteId();
179 } else {
180 return local.name() == remote.name();
181 }
182 }
183
184 void localCollectionsReceived(const Akonadi::Collection::List &localCols)
185 {
186 for (const Akonadi::Collection &collection : localCols) {
187 const RemoteId parentRid = remoteIdForCollection(collection.parentCollection());
188 localCollections[parentRid] += collection;
189 }
190 }
191
192 void processCollections(const RemoteId &parentRid)
193 {
194 Collection::List remoteChildren = remoteCollections.value(parentRid);
195 Collection::List removedChildren = removedRemoteCollections.value(parentRid);
196 Collection::List localChildren = localCollections.value(parentRid);
197
198 // Iterate over the list of local children of localParent
199 for (auto localIter = localChildren.begin(), localEnd = localChildren.end(); localIter != localEnd;) {
200 const Collection localCollection = *localIter;
201 bool matched = false;
202 uidRidMap.insert(localIter->id(), localIter->remoteId());
203
204 // Try to map removed remote collections (from incremental sync) to local collections
205 for (auto removedIter = removedChildren.begin(), removedEnd = removedChildren.end(); removedIter != removedEnd;) {
206 Collection removedCollection = *removedIter;
207
208 if (matchLocalAndRemoteCollection(localCollection, removedCollection)) {
209 matched = true;
210 if (!localCollection.remoteId().isEmpty()) {
211 localCollectionsToRemove.append(localCollection);
212 }
213 // Remove the matched removed collection from the list so that
214 // we don't have to iterate over it again next time.
215 removedIter = removedChildren.erase(removedIter);
216 removedEnd = removedChildren.end();
217 break;
218 } else {
219 // Keep looking
220 ++removedIter;
221 }
222 }
223
224 if (matched) {
225 // Remove the matched local collection from the list, because we
226 // have already put it into localCollectionsToRemove
227 localIter = localChildren.erase(localIter);
228 localEnd = localChildren.end();
229 continue;
230 }
231
232 // Try to find a matching collection in the list of remote children
233 for (auto remoteIter = remoteChildren.begin(), remoteEnd = remoteChildren.end(); !matched && remoteIter != remoteEnd;) {
234 Collection remoteCollection = *remoteIter;
235
236 // Yay, we found a match!
237 if (matchLocalAndRemoteCollection(localCollection, remoteCollection)) {
238 matched = true;
239
240 // "Virtual" flag cannot be updated: we need to recreate
241 // the collection from scratch.
242 if (localCollection.isVirtual() != remoteCollection.isVirtual()) {
243 // Mark the local collection and all its children for deletion and re-creation
244 QList<QPair<Collection /*local*/, Collection /*remote*/>> parents = {{localCollection, remoteCollection}};
245 while (!parents.empty()) {
246 auto parent = parents.takeFirst();
247 qCDebug(AKONADICORE_LOG) << "Local collection " << parent.first.name() << " will be recreated";
248 localCollectionsToRemove.push_back(parent.first);
249 remoteCollectionsToCreate.push_back(parent.second);
250 for (auto it = localChildren.begin(), end = localChildren.end(); it != end;) {
251 if (it->parentCollection() == parent.first) {
252 Collection remoteParent;
253 auto remoteIt = std::find_if(
254 remoteChildren.begin(),
255 remoteChildren.end(),
256 std::bind(&CollectionSyncPrivate::matchLocalAndRemoteCollection, this, parent.first, std::placeholders::_1));
257 if (remoteIt != remoteChildren.end()) {
258 remoteParent = *remoteIt;
259 remoteEnd = remoteChildren.erase(remoteIt);
260 }
261 parents.push_back({*it, remoteParent});
262 it = localChildren.erase(it);
263 localEnd = end = localChildren.end();
264 } else {
265 ++it;
266 }
267 }
268 }
269 } else if (collectionNeedsUpdate(localCollection, remoteCollection)) {
270 // We need to store both local and remote collections, so that
271 // we can copy over attributes to be preserved
272 remoteCollectionsToUpdate.append(qMakePair(localCollection, remoteCollection));
273 } else {
274 // Collections are the same, no need to update anything
275 }
276
277 // Remove the matched remote collection from the list so that
278 // in the end we are left with list of collections that don't
279 // exist locally (i.e. new collections)
280 remoteIter = remoteChildren.erase(remoteIter);
281 remoteEnd = remoteChildren.end();
282 break;
283 } else {
284 // Keep looking
285 ++remoteIter;
286 }
287 }
288
289 if (matched) {
290 // Remove the matched local collection from the list so that
291 // in the end we are left with list of collections that don't
292 // exist remotely (i.e. removed collections)
293 localIter = localChildren.erase(localIter);
294 localEnd = localChildren.end();
295 } else {
296 ++localIter;
297 }
298 }
299
300 if (!removedChildren.isEmpty()) {
301 removedRemoteCollections[parentRid] = removedChildren;
302 } else {
303 removedRemoteCollections.remove(parentRid);
304 }
305
306 if (!remoteChildren.isEmpty()) {
307 remoteCollections[parentRid] = remoteChildren;
308 } else {
309 remoteCollections.remove(parentRid);
310 }
311
312 if (!localChildren.isEmpty()) {
313 localCollections[parentRid] = localChildren;
314 } else {
315 localCollections.remove(parentRid);
316 }
317 }
318
319 void processLocalCollections(const RemoteId &parentRid, const Collection &parentCollection)
320 {
321 const Collection::List originalChildren = localCollections.value(parentRid);
322 processCollections(parentRid);
323
324 const Collection::List remoteChildren = remoteCollections.take(parentRid);
325 const Collection::List localChildren = localCollections.take(parentRid);
326
327 // At this point remoteChildren contains collections that don't exist locally yet
328 if (!remoteChildren.isEmpty()) {
329 for (Collection c : remoteChildren) {
330 c.setParentCollection(parentCollection);
331 remoteCollectionsToCreate.append(c);
332 }
333 }
334 // At this point localChildren contains collections that don't exist remotely anymore
335 if (!localChildren.isEmpty() && !incremental) {
336 for (const auto &c : localChildren) {
337 if (!c.remoteId().isEmpty()) {
338 localCollectionsToRemove.push_back(c);
339 }
340 }
341 }
342
343 // Recurse into children
344 for (const Collection &c : originalChildren) {
345 processLocalCollections(remoteIdForCollection(c), c);
346 }
347 }
348
349 void localCollectionFetchResult(KJob *job)
350 {
351 if (job->error()) {
352 return; // handled by the base class
353 }
354
355 processLocalCollections(RemoteId::rootRid, akonadiRootCollection);
356 localListDone = true;
357 execute();
358 }
359
360 bool ignoreAttributeChanges(const Akonadi::Collection &col, const QByteArray &attribute) const
361 {
362 return (keepLocalChanges.contains(attribute) || col.keepLocalChanges().contains(attribute));
363 }
364
365 /**
366 Checks if the given localCollection and remoteCollection are different
367 */
368 bool collectionNeedsUpdate(const Collection &localCollection, const Collection &remoteCollection) const
369 {
370 if (!ignoreAttributeChanges(remoteCollection, CONTENTMIMETYPES)) {
371 if (localCollection.contentMimeTypes().size() != remoteCollection.contentMimeTypes().size()) {
372 return true;
373 } else {
374 for (qsizetype i = 0, total = remoteCollection.contentMimeTypes().size(); i < total; ++i) {
375 const auto contentMimeTypes = remoteCollection.contentMimeTypes();
376 const QString mimeType = contentMimeTypes.at(i);
377 if (!localCollection.contentMimeTypes().contains(mimeType)) {
378 return true;
379 }
380 }
381 }
382 }
383
384 if (localCollection.parentCollection().remoteId() != remoteCollection.parentCollection().remoteId()) {
385 return true;
386 }
387 if (localCollection.name() != remoteCollection.name()) {
388 return true;
389 }
390 if (localCollection.remoteId() != remoteCollection.remoteId()) {
391 return true;
392 }
393 if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) {
394 return true;
395 }
396 if (!(localCollection.cachePolicy() == remoteCollection.cachePolicy())) {
397 return true;
398 }
399 if (localCollection.enabled() != remoteCollection.enabled()) {
400 return true;
401 }
402
403 // CollectionModifyJob adds the remote attributes to the local collection
404 const Akonadi::Attribute::List lstAttr = remoteCollection.attributes();
405 for (const Attribute *attr : lstAttr) {
406 const Attribute *localAttr = localCollection.attribute(attr->type());
407 if (localAttr && ignoreAttributeChanges(remoteCollection, attr->type())) {
408 continue;
409 }
410 // The attribute must both exist and have equal contents
411 if (!localAttr || localAttr->serialized() != attr->serialized()) {
412 return true;
413 }
414 }
415
416 return false;
417 }
418
419 void createLocalCollections()
420 {
421 if (remoteCollectionsToCreate.isEmpty()) {
422 updateLocalCollections();
423 return;
424 }
425
426 for (auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end;) {
427 const Collection col = *iter;
428 const Collection parentCollection = col.parentCollection();
429 // The parent already exists locally
430 if (parentCollection == akonadiRootCollection || parentCollection.id() > 0) {
431 ++pendingJobs;
432 auto create = new CollectionCreateJob(col, currentTransaction);
433 QObject::connect(create, &KJob::result, q, [this](KJob *job) {
434 createLocalCollectionResult(job);
435 });
436
437 // Commit transaction after every 100 collections are created,
438 // otherwise it overloads database journal and things get veeery slow
439 if (pendingJobs % 100 == 0) {
440 currentTransaction->commit();
441 createTransaction();
442 }
443
444 iter = remoteCollectionsToCreate.erase(iter);
445 end = remoteCollectionsToCreate.end();
446 } else {
447 // Skip the collection, we'll try again once we create all the other
448 // collection we already have a parent for
449 ++iter;
450 }
451 }
452 }
453
454 void createLocalCollectionResult(KJob *job)
455 {
456 --pendingJobs;
457 if (job->error()) {
458 return; // handled by the base class
459 }
460
461 q->setProcessedAmount(KJob::Bytes, ++progress);
462
463 const Collection newLocal = static_cast<CollectionCreateJob *>(job)->collection();
464 uidRidMap.insert(newLocal.id(), newLocal.remoteId());
465 const RemoteId newLocalRID = remoteIdForCollection(newLocal);
466
467 // See if there are any pending collections that this collection is parent of and
468 // update them if so
469 for (auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end; ++iter) {
470 const Collection parentCollection = iter->parentCollection();
471 if (parentCollection != akonadiRootCollection && parentCollection.id() <= 0) {
472 const RemoteId remoteRID = remoteIdForCollection(*iter);
473 if (remoteRID.isAbsolute()) {
474 if (newLocalRID == remoteIdForCollection(*iter)) {
475 iter->setParentCollection(newLocal);
476 }
477 } else if (!hierarchicalRIDs) {
478 if (remoteRID.ridChain.startsWith(parentCollection.remoteId())) {
479 iter->setParentCollection(newLocal);
480 }
481 }
482 }
483 }
484
485 // Enqueue all pending remote collections that are children of the just-created
486 // collection
487 Collection::List collectionsToCreate = remoteCollections.take(newLocalRID);
488 if (collectionsToCreate.isEmpty() && !hierarchicalRIDs) {
489 collectionsToCreate = remoteCollections.take(RemoteId(newLocal.remoteId()));
490 }
491 for (Collection col : std::as_const(collectionsToCreate)) {
492 col.setParentCollection(newLocal);
493 remoteCollectionsToCreate.append(col);
494 }
495
496 // If there are still any collections to create left, try if we just created
497 // a parent for any of them
498 if (!remoteCollectionsToCreate.isEmpty()) {
499 createLocalCollections();
500 } else if (pendingJobs == 0) {
501 Q_ASSERT(remoteCollectionsToCreate.isEmpty());
502 if (!remoteCollections.isEmpty()) {
503 currentTransaction->rollback();
504 q->setError(CollectionSync::Unknown);
505 q->setErrorText(i18n("Found unresolved orphan collections"));
506 qCWarning(AKONADICORE_LOG) << "found unresolved orphan collection";
507 emitResult();
508 return;
509 }
510
511 currentTransaction->commit();
512 createTransaction();
513
514 // Otherwise move to next task: updating existing collections
515 updateLocalCollections();
516 }
517 /*
518 * else if (!remoteCollections.isEmpty()) {
519 currentTransaction->rollback();
520 q->setError(Unknown);
521 q->setErrorText(i18n("Incomplete collection tree"));
522 emitResult();
523 return;
524 }
525 */
526 }
527
528 /**
529 Performs a local update for the given node pair.
530 */
531 void updateLocalCollections()
532 {
533 if (remoteCollectionsToUpdate.isEmpty()) {
534 deleteLocalCollections();
535 return;
536 }
537
538 using CollectionPair = QPair<Collection, Collection>;
539 for (const CollectionPair &pair : std::as_const(remoteCollectionsToUpdate)) {
540 const Collection local = pair.first;
541 const Collection remote = pair.second;
542 Collection upd(remote);
543
544 Q_ASSERT(!upd.remoteId().isEmpty());
545 Q_ASSERT(currentTransaction);
546 upd.setId(local.id());
547 if (ignoreAttributeChanges(remote, CONTENTMIMETYPES)) {
548 upd.setContentMimeTypes(local.contentMimeTypes());
549 }
550 const auto remoteAttributes = upd.attributes();
551 for (Attribute *remoteAttr : remoteAttributes) {
552 if (ignoreAttributeChanges(remote, remoteAttr->type()) && local.hasAttribute(remoteAttr->type())) {
553 // We don't want to overwrite the attribute changes with the defaults provided by the resource.
554 const Attribute *localAttr = local.attribute(remoteAttr->type());
555 upd.removeAttribute(localAttr->type());
556 upd.addAttribute(localAttr->clone());
557 }
558 }
559
560 // ### HACK to work around the implicit move attempts of CollectionModifyJob
561 // which we do explicitly below
562 Collection c(upd);
563 c.setParentCollection(local.parentCollection());
564 ++pendingJobs;
565 auto mod = new CollectionModifyJob(c, currentTransaction);
566 QObject::connect(mod, &KJob::result, q, [this](KJob *job) {
567 updateLocalCollectionResult(job);
568 });
569
570 // detecting moves is only possible with global RIDs
571 if (!hierarchicalRIDs) {
572 if (remote.parentCollection().isValid() && remote.parentCollection().id() != local.parentCollection().id()) {
573 ++pendingJobs;
574 auto move = new CollectionMoveJob(upd, remote.parentCollection(), currentTransaction);
575 QObject::connect(move, &KJob::result, q, [this](KJob *job) {
576 updateLocalCollectionResult(job);
577 });
578 }
579 }
580 }
581 }
582
583 void updateLocalCollectionResult(KJob *job)
584 {
585 --pendingJobs;
586 if (job->error()) {
587 return; // handled by the base class
588 }
589 if (qobject_cast<CollectionModifyJob *>(job)) {
590 q->setProcessedAmount(KJob::Bytes, ++progress);
591 }
592
593 // All updates are done, time to move on to next task: deletion
594 if (pendingJobs == 0) {
595 currentTransaction->commit();
596 createTransaction();
597
598 deleteLocalCollections();
599 }
600 }
601
602 void deleteLocalCollections()
603 {
604 if (localCollectionsToRemove.isEmpty()) {
605 done();
606 return;
607 }
608
609 for (const Collection &col : std::as_const(localCollectionsToRemove)) {
610 Q_ASSERT(!col.remoteId().isEmpty()); // empty RID -> stuff we haven't even written to the remote side yet
611
612 ++pendingJobs;
613 Q_ASSERT(currentTransaction);
614 auto job = new CollectionDeleteJob(col, currentTransaction);
615 QObject::connect(job, &KJob::result, q, [this](KJob *job) {
616 deleteLocalCollectionsResult(job);
617 });
618
619 // It can happen that the groupware servers report us deleted collections
620 // twice, in this case this collection delete job will fail on the second try.
621 // To avoid a rollback of the complete transaction we gracefully allow the job
622 // to fail :)
623 currentTransaction->setIgnoreJobFailure(job);
624 }
625 }
626
627 void deleteLocalCollectionsResult(KJob * /*unused*/)
628 {
629 --pendingJobs;
630 q->setProcessedAmount(KJob::Bytes, ++progress);
631
632 if (pendingJobs == 0) {
633 currentTransaction->commit();
634 currentTransaction = nullptr;
635
636 done();
637 }
638 }
639
640 void done()
641 {
642 if (currentTransaction) {
643 // This can trigger a direct call of transactionSequenceResult
644 currentTransaction->commit();
645 currentTransaction = nullptr;
646 }
647
648 if (!remoteCollections.isEmpty()) {
649 q->setError(CollectionSync::Unknown);
650 q->setErrorText(i18n("Found unresolved orphan collections"));
651 }
652 emitResult();
653 }
654
655 void emitResult()
656 {
657 // Prevent double result emission
658 Q_ASSERT(!resultEmitted);
659 if (!resultEmitted) {
660 if (q->hasSubjobs()) {
661 // If there are subjobs, pick one, wait for it to finish, then
662 // try again. This way we make sure we don't emit result() signal
663 // while there is still a Transaction job running
664 KJob *subjob = q->subjobs().at(0);
666 subjob,
668 q,
669 [this](KJob * /*unused*/) {
670 emitResult();
671 },
673 } else {
674 resultEmitted = true;
675 q->emitResult();
676 }
677 }
678 }
679
680 void createTransaction()
681 {
682 currentTransaction = new TransactionSequence(q);
683 currentTransaction->setAutomaticCommittingEnabled(false);
684 q->connect(currentTransaction, &TransactionSequence::finished, q, [this](KJob *job) {
685 transactionSequenceResult(job);
686 });
687 }
688
689 /** After the transaction has finished report we're done as well. */
690 void transactionSequenceResult(KJob *job)
691 {
692 if (job->error()) {
693 return; // handled by the base class
694 }
695
696 // If this was the last transaction, then finish, otherwise there's
697 // a new transaction in the queue already
698 if (job == currentTransaction) {
699 currentTransaction = nullptr;
700 }
701 }
702
703 /**
704 Process what's currently available.
705 */
706 void execute()
707 {
708 qCDebug(AKONADICORE_LOG) << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
709 if (!localListDone && !deliveryDone) {
710 return;
711 }
712
713 if (!localListDone && deliveryDone) {
714 Job *parent = (currentTransaction ? static_cast<Job *>(currentTransaction) : static_cast<Job *>(q));
715 auto job = new CollectionFetchJob(akonadiRootCollection, CollectionFetchJob::Recursive, parent);
716 job->fetchScope().setResource(resourceId);
717 job->fetchScope().setListFilter(CollectionFetchScope::NoFilter);
718 job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All);
719 q->connect(job, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) {
720 localCollectionsReceived(cols);
721 });
722 q->connect(job, &KJob::result, q, [this](KJob *job) {
723 localCollectionFetchResult(job);
724 });
725 return;
726 }
727
728 // If a transaction is not started yet, it means we just finished local listing
729 if (!currentTransaction) {
730 // There's nothing to do after local listing -> we are done!
731 if (remoteCollectionsToCreate.isEmpty() && remoteCollectionsToUpdate.isEmpty() && localCollectionsToRemove.isEmpty()) {
732 qCDebug(AKONADICORE_LOG) << "Nothing to do";
733 emitResult();
734 return;
735 }
736 // Ok, there's some work to do, so create a transaction we can use
737 createTransaction();
738 }
739
740 createLocalCollections();
741 }
742
743 CollectionSync *const q;
744
745 QString resourceId;
746
747 int pendingJobs;
748 int progress;
749
750 TransactionSequence *currentTransaction;
751
752 bool incremental;
753 bool streaming;
754 bool hierarchicalRIDs;
755
756 bool localListDone;
757 bool deliveryDone;
758
759 // List of parts where local changes should not be overwritten
760 QSet<QByteArray> keepLocalChanges;
761
762 QHash<RemoteId /* parent */, Collection::List /* children */> removedRemoteCollections;
763 QHash<RemoteId /* parent */, Collection::List /* children */> remoteCollections;
764 QHash<RemoteId /* parent */, Collection::List /* children */> localCollections;
765
766 Collection::List localCollectionsToRemove;
767 Collection::List remoteCollectionsToCreate;
768 QList<QPair<Collection /* local */, Collection /* remote */>> remoteCollectionsToUpdate;
770
771 // HACK: To workaround Collection copy constructor being very expensive, we
772 // store the Collection::root() collection in a variable here for faster
773 // access
774 Collection akonadiRootCollection;
775
776 bool resultEmitted;
777};
778
779CollectionSync::CollectionSync(const QString &resourceId, QObject *parent)
780 : Job(parent)
781 , d(new CollectionSyncPrivate(this))
782{
783 d->resourceId = resourceId;
784 setTotalAmount(KJob::Bytes, 0);
785}
786
787CollectionSync::~CollectionSync() = default;
788
789void CollectionSync::setRemoteCollections(const Collection::List &remoteCollections)
790{
791 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + remoteCollections.count());
792 for (const Collection &c : remoteCollections) {
793 d->addRemoteColection(c);
794 }
795
796 if (!d->streaming) {
797 d->deliveryDone = true;
798 }
799 d->execute();
800}
801
802void CollectionSync::setRemoteCollections(const Collection::List &changedCollections, const Collection::List &removedCollections)
803{
804 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + changedCollections.count());
805 d->incremental = true;
806 for (const Collection &c : changedCollections) {
807 d->addRemoteColection(c);
808 }
809 for (const Collection &c : removedCollections) {
810 d->addRemoteColection(c, true);
811 }
812
813 if (!d->streaming) {
814 d->deliveryDone = true;
815 }
816 d->execute();
817}
818
819void CollectionSync::doStart()
820{
821}
822
823void CollectionSync::setStreamingEnabled(bool streaming)
824{
825 d->streaming = streaming;
826}
827
828void CollectionSync::retrievalDone()
829{
830 d->deliveryDone = true;
831 d->execute();
832}
833
834void CollectionSync::setHierarchicalRemoteIds(bool hierarchical)
835{
836 d->hierarchicalRIDs = hierarchical;
837}
838
839void CollectionSync::rollback()
840{
841 if (d->currentTransaction) {
842 d->currentTransaction->rollback();
843 } else {
844 setError(UserCanceled);
845 emitResult();
846 }
847}
848
849void CollectionSync::setKeepLocalChanges(const QSet<QByteArray> &parts)
850{
851 d->keepLocalChanges = parts;
852}
853
854#include "moc_collectionsync_p.cpp"
Provides interface for custom attributes for Entity.
Definition attribute.h:132
virtual QByteArray serialized() const =0
Returns a QByteArray representation of the attribute which will be storaged.
virtual Attribute * clone() const =0
Creates a copy of this attribute.
virtual QByteArray type() const =0
Returns the type of the attribute.
Job that creates a new collection in the Akonadi storage.
Job that deletes a collection in the Akonadi storage.
Job that fetches collections from the Akonadi storage.
@ Recursive
List all sub-collections.
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
@ NoFilter
No filtering, retrieve all collections.
@ All
Retrieve all ancestors, up to Collection::root()
Job that modifies a collection in the Akonadi storage.
Job that moves a collection in the Akonadi storage to a new parent collection.
Represents a collection of PIM items.
Definition collection.h:62
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
static Collection root()
Returns the root collection.
Attribute::List attributes() const
Returns a list of all attributes of the collection.
CachePolicy cachePolicy() const
Returns the cache policy of the collection.
Collection parentCollection() const
Returns the parent collection of this object.
QSet< QByteArray > keepLocalChanges() const
Returns what parts are only default values.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
QString remoteId() const
Returns the remote id of the collection.
Base class for all actions in the Akonadi storage.
Definition job.h:81
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
void commit()
Commits the transaction as soon as all pending sub-jobs finished successfully.
void setIgnoreJobFailure(KJob *job)
Sets which job of the sequence might fail without rolling back the complete transaction.
void rollback()
Rolls back the current transaction as soon as possible.
void setAutomaticCommittingEnabled(bool enable)
Disable automatic committing.
int error() const
void result(KJob *job)
void finished(KJob *job)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
QAction * create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
KTEXTEDITOR_EXPORT QDebug operator<<(QDebug s, const MovingCursor &cursor)
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
QDebug & nospace()
iterator insert(const Key &key, const T &value)
T value(const Key &key) const const
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
T & last()
qsizetype length() const const
void push_back(parameter_type value)
qsizetype size() const const
bool startsWith(parameter_type value) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool contains(const QSet< T > &other) const const
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QueuedConnection
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:11:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.