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;
769 QHash<Collection::Id, QString> uidRidMap;
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"
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.
QList< Attribute * > List
Describes a list of attributes.
Definition attribute.h:137
@ 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()
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.
QList< Collection > List
Describes a list of collections.
Definition collection.h:84
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
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()
QDebug & nospace()
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
qsizetype length() const const
qsizetype size() const const
bool startsWith(parameter_type value) const const
T value(qsizetype i) 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
void push_back(QChar ch)
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-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:49:56 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.