File indexing completed on 2024-11-10 04:40:39
0001 /* 0002 SPDX-FileCopyrightText: 2007, 2009 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "akonadicore_debug.h" 0008 #include "collection.h" 0009 #include "collectioncreatejob.h" 0010 #include "collectiondeletejob.h" 0011 #include "collectionfetchjob.h" 0012 #include "collectionfetchscope.h" 0013 #include "collectionmodifyjob.h" 0014 #include "collectionmovejob.h" 0015 #include "collectionsync_p.h" 0016 0017 #include "cachepolicy.h" 0018 0019 #include <KLocalizedString> 0020 #include <QHash> 0021 #include <QList> 0022 0023 #include <functional> 0024 0025 using namespace Akonadi; 0026 0027 static const char CONTENTMIMETYPES[] = "CONTENTMIMETYPES"; 0028 0029 static const char ROOTPARENTRID[] = "AKONADI_ROOT_COLLECTION"; 0030 0031 class RemoteId 0032 { 0033 public: 0034 explicit RemoteId() 0035 { 0036 } 0037 0038 explicit inline RemoteId(const QStringList &ridChain) 0039 : ridChain(ridChain) 0040 { 0041 } 0042 0043 explicit inline RemoteId(const QString &rid) 0044 { 0045 ridChain.append(rid); 0046 } 0047 0048 inline bool isAbsolute() const 0049 { 0050 return ridChain.last() == QString::fromLatin1(ROOTPARENTRID); 0051 } 0052 0053 inline bool isEmpty() const 0054 { 0055 return ridChain.isEmpty(); 0056 } 0057 0058 inline bool operator==(const RemoteId &other) const 0059 { 0060 return ridChain == other.ridChain; 0061 } 0062 0063 QStringList ridChain; 0064 0065 static RemoteId rootRid; 0066 }; 0067 0068 RemoteId RemoteId::rootRid = RemoteId(QStringList() << QString::fromLatin1(ROOTPARENTRID)); 0069 0070 Q_DECLARE_METATYPE(RemoteId) 0071 0072 size_t qHash(const RemoteId &rid, size_t seed = 0) noexcept 0073 { 0074 return qHashRange(rid.ridChain.constBegin(), rid.ridChain.constEnd(), seed); 0075 } 0076 0077 inline bool operator<(const RemoteId &r1, const RemoteId &r2) 0078 { 0079 if (r1.ridChain.length() == r2.ridChain.length()) { 0080 auto it1 = r1.ridChain.constBegin(); 0081 auto end1 = r1.ridChain.constEnd(); 0082 auto it2 = r2.ridChain.constBegin(); 0083 while (it1 != end1) { 0084 if ((*it1) == (*it2)) { 0085 ++it1; 0086 ++it2; 0087 continue; 0088 } 0089 return (*it1) < (*it2); 0090 } 0091 } else { 0092 return r1.ridChain.length() < r2.ridChain.length(); 0093 } 0094 return false; 0095 } 0096 0097 QDebug operator<<(QDebug s, const RemoteId &rid) 0098 { 0099 s.nospace() << "RemoteId(" << rid.ridChain << ")"; 0100 return s; 0101 } 0102 0103 /** 0104 * @internal 0105 */ 0106 class Akonadi::CollectionSyncPrivate 0107 { 0108 public: 0109 explicit CollectionSyncPrivate(CollectionSync *parent) 0110 : q(parent) 0111 , pendingJobs(0) 0112 , progress(0) 0113 , currentTransaction(nullptr) 0114 , incremental(false) 0115 , streaming(false) 0116 , hierarchicalRIDs(false) 0117 , localListDone(false) 0118 , deliveryDone(false) 0119 , akonadiRootCollection(Collection::root()) 0120 , resultEmitted(false) 0121 { 0122 } 0123 0124 ~CollectionSyncPrivate() 0125 { 0126 } 0127 0128 RemoteId remoteIdForCollection(const Collection &collection) const 0129 { 0130 if (collection == Collection::root()) { 0131 return RemoteId::rootRid; 0132 } 0133 0134 if (!hierarchicalRIDs) { 0135 return RemoteId(collection.remoteId()); 0136 } 0137 0138 RemoteId rid; 0139 Collection parent = collection; 0140 while (parent.isValid() || !parent.remoteId().isEmpty()) { 0141 QString prid = parent.remoteId(); 0142 if (prid.isEmpty() && parent.isValid()) { 0143 prid = uidRidMap.value(parent.id()); 0144 } 0145 if (prid.isEmpty()) { 0146 break; 0147 } 0148 rid.ridChain.append(prid); 0149 parent = parent.parentCollection(); 0150 if (parent == akonadiRootCollection) { 0151 rid.ridChain.append(QString::fromLatin1(ROOTPARENTRID)); 0152 break; 0153 } 0154 } 0155 return rid; 0156 } 0157 0158 void addRemoteColection(const Collection &collection, bool removed = false) 0159 { 0160 QHash<RemoteId, Collection::List> &map = (removed ? removedRemoteCollections : remoteCollections); 0161 const Collection parentCollection = collection.parentCollection(); 0162 if (parentCollection.remoteId() == akonadiRootCollection.remoteId() || parentCollection.id() == akonadiRootCollection.id()) { 0163 Collection c2(collection); 0164 c2.setParentCollection(akonadiRootCollection); 0165 map[RemoteId::rootRid].append(c2); 0166 } else { 0167 Q_ASSERT(!parentCollection.remoteId().isEmpty()); 0168 map[remoteIdForCollection(parentCollection)].append(collection); 0169 } 0170 } 0171 0172 /* Compares collections by remoteId and falls back to name comparison in case 0173 * local collection does not have remoteId (which can happen in some cases) 0174 */ 0175 bool matchLocalAndRemoteCollection(const Collection &local, const Collection &remote) 0176 { 0177 if (!local.remoteId().isEmpty()) { 0178 return local.remoteId() == remote.remoteId(); 0179 } else { 0180 return local.name() == remote.name(); 0181 } 0182 } 0183 0184 void localCollectionsReceived(const Akonadi::Collection::List &localCols) 0185 { 0186 for (const Akonadi::Collection &collection : localCols) { 0187 const RemoteId parentRid = remoteIdForCollection(collection.parentCollection()); 0188 localCollections[parentRid] += collection; 0189 } 0190 } 0191 0192 void processCollections(const RemoteId &parentRid) 0193 { 0194 Collection::List remoteChildren = remoteCollections.value(parentRid); 0195 Collection::List removedChildren = removedRemoteCollections.value(parentRid); 0196 Collection::List localChildren = localCollections.value(parentRid); 0197 0198 // Iterate over the list of local children of localParent 0199 for (auto localIter = localChildren.begin(), localEnd = localChildren.end(); localIter != localEnd;) { 0200 const Collection localCollection = *localIter; 0201 bool matched = false; 0202 uidRidMap.insert(localIter->id(), localIter->remoteId()); 0203 0204 // Try to map removed remote collections (from incremental sync) to local collections 0205 for (auto removedIter = removedChildren.begin(), removedEnd = removedChildren.end(); removedIter != removedEnd;) { 0206 Collection removedCollection = *removedIter; 0207 0208 if (matchLocalAndRemoteCollection(localCollection, removedCollection)) { 0209 matched = true; 0210 if (!localCollection.remoteId().isEmpty()) { 0211 localCollectionsToRemove.append(localCollection); 0212 } 0213 // Remove the matched removed collection from the list so that 0214 // we don't have to iterate over it again next time. 0215 removedIter = removedChildren.erase(removedIter); 0216 removedEnd = removedChildren.end(); 0217 break; 0218 } else { 0219 // Keep looking 0220 ++removedIter; 0221 } 0222 } 0223 0224 if (matched) { 0225 // Remove the matched local collection from the list, because we 0226 // have already put it into localCollectionsToRemove 0227 localIter = localChildren.erase(localIter); 0228 localEnd = localChildren.end(); 0229 continue; 0230 } 0231 0232 // Try to find a matching collection in the list of remote children 0233 for (auto remoteIter = remoteChildren.begin(), remoteEnd = remoteChildren.end(); !matched && remoteIter != remoteEnd;) { 0234 Collection remoteCollection = *remoteIter; 0235 0236 // Yay, we found a match! 0237 if (matchLocalAndRemoteCollection(localCollection, remoteCollection)) { 0238 matched = true; 0239 0240 // "Virtual" flag cannot be updated: we need to recreate 0241 // the collection from scratch. 0242 if (localCollection.isVirtual() != remoteCollection.isVirtual()) { 0243 // Mark the local collection and all its children for deletion and re-creation 0244 QList<QPair<Collection /*local*/, Collection /*remote*/>> parents = {{localCollection, remoteCollection}}; 0245 while (!parents.empty()) { 0246 auto parent = parents.takeFirst(); 0247 qCDebug(AKONADICORE_LOG) << "Local collection " << parent.first.name() << " will be recreated"; 0248 localCollectionsToRemove.push_back(parent.first); 0249 remoteCollectionsToCreate.push_back(parent.second); 0250 for (auto it = localChildren.begin(), end = localChildren.end(); it != end;) { 0251 if (it->parentCollection() == parent.first) { 0252 Collection remoteParent; 0253 auto remoteIt = std::find_if( 0254 remoteChildren.begin(), 0255 remoteChildren.end(), 0256 std::bind(&CollectionSyncPrivate::matchLocalAndRemoteCollection, this, parent.first, std::placeholders::_1)); 0257 if (remoteIt != remoteChildren.end()) { 0258 remoteParent = *remoteIt; 0259 remoteEnd = remoteChildren.erase(remoteIt); 0260 } 0261 parents.push_back({*it, remoteParent}); 0262 it = localChildren.erase(it); 0263 localEnd = end = localChildren.end(); 0264 } else { 0265 ++it; 0266 } 0267 } 0268 } 0269 } else if (collectionNeedsUpdate(localCollection, remoteCollection)) { 0270 // We need to store both local and remote collections, so that 0271 // we can copy over attributes to be preserved 0272 remoteCollectionsToUpdate.append(qMakePair(localCollection, remoteCollection)); 0273 } else { 0274 // Collections are the same, no need to update anything 0275 } 0276 0277 // Remove the matched remote collection from the list so that 0278 // in the end we are left with list of collections that don't 0279 // exist locally (i.e. new collections) 0280 remoteIter = remoteChildren.erase(remoteIter); 0281 remoteEnd = remoteChildren.end(); 0282 break; 0283 } else { 0284 // Keep looking 0285 ++remoteIter; 0286 } 0287 } 0288 0289 if (matched) { 0290 // Remove the matched local collection from the list so that 0291 // in the end we are left with list of collections that don't 0292 // exist remotely (i.e. removed collections) 0293 localIter = localChildren.erase(localIter); 0294 localEnd = localChildren.end(); 0295 } else { 0296 ++localIter; 0297 } 0298 } 0299 0300 if (!removedChildren.isEmpty()) { 0301 removedRemoteCollections[parentRid] = removedChildren; 0302 } else { 0303 removedRemoteCollections.remove(parentRid); 0304 } 0305 0306 if (!remoteChildren.isEmpty()) { 0307 remoteCollections[parentRid] = remoteChildren; 0308 } else { 0309 remoteCollections.remove(parentRid); 0310 } 0311 0312 if (!localChildren.isEmpty()) { 0313 localCollections[parentRid] = localChildren; 0314 } else { 0315 localCollections.remove(parentRid); 0316 } 0317 } 0318 0319 void processLocalCollections(const RemoteId &parentRid, const Collection &parentCollection) 0320 { 0321 const Collection::List originalChildren = localCollections.value(parentRid); 0322 processCollections(parentRid); 0323 0324 const Collection::List remoteChildren = remoteCollections.take(parentRid); 0325 const Collection::List localChildren = localCollections.take(parentRid); 0326 0327 // At this point remoteChildren contains collections that don't exist locally yet 0328 if (!remoteChildren.isEmpty()) { 0329 for (Collection c : remoteChildren) { 0330 c.setParentCollection(parentCollection); 0331 remoteCollectionsToCreate.append(c); 0332 } 0333 } 0334 // At this point localChildren contains collections that don't exist remotely anymore 0335 if (!localChildren.isEmpty() && !incremental) { 0336 for (const auto &c : localChildren) { 0337 if (!c.remoteId().isEmpty()) { 0338 localCollectionsToRemove.push_back(c); 0339 } 0340 } 0341 } 0342 0343 // Recurse into children 0344 for (const Collection &c : originalChildren) { 0345 processLocalCollections(remoteIdForCollection(c), c); 0346 } 0347 } 0348 0349 void localCollectionFetchResult(KJob *job) 0350 { 0351 if (job->error()) { 0352 return; // handled by the base class 0353 } 0354 0355 processLocalCollections(RemoteId::rootRid, akonadiRootCollection); 0356 localListDone = true; 0357 execute(); 0358 } 0359 0360 bool ignoreAttributeChanges(const Akonadi::Collection &col, const QByteArray &attribute) const 0361 { 0362 return (keepLocalChanges.contains(attribute) || col.keepLocalChanges().contains(attribute)); 0363 } 0364 0365 /** 0366 Checks if the given localCollection and remoteCollection are different 0367 */ 0368 bool collectionNeedsUpdate(const Collection &localCollection, const Collection &remoteCollection) const 0369 { 0370 if (!ignoreAttributeChanges(remoteCollection, CONTENTMIMETYPES)) { 0371 if (localCollection.contentMimeTypes().size() != remoteCollection.contentMimeTypes().size()) { 0372 return true; 0373 } else { 0374 for (qsizetype i = 0, total = remoteCollection.contentMimeTypes().size(); i < total; ++i) { 0375 const auto contentMimeTypes = remoteCollection.contentMimeTypes(); 0376 const QString mimeType = contentMimeTypes.at(i); 0377 if (!localCollection.contentMimeTypes().contains(mimeType)) { 0378 return true; 0379 } 0380 } 0381 } 0382 } 0383 0384 if (localCollection.parentCollection().remoteId() != remoteCollection.parentCollection().remoteId()) { 0385 return true; 0386 } 0387 if (localCollection.name() != remoteCollection.name()) { 0388 return true; 0389 } 0390 if (localCollection.remoteId() != remoteCollection.remoteId()) { 0391 return true; 0392 } 0393 if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) { 0394 return true; 0395 } 0396 if (!(localCollection.cachePolicy() == remoteCollection.cachePolicy())) { 0397 return true; 0398 } 0399 if (localCollection.enabled() != remoteCollection.enabled()) { 0400 return true; 0401 } 0402 0403 // CollectionModifyJob adds the remote attributes to the local collection 0404 const Akonadi::Attribute::List lstAttr = remoteCollection.attributes(); 0405 for (const Attribute *attr : lstAttr) { 0406 const Attribute *localAttr = localCollection.attribute(attr->type()); 0407 if (localAttr && ignoreAttributeChanges(remoteCollection, attr->type())) { 0408 continue; 0409 } 0410 // The attribute must both exist and have equal contents 0411 if (!localAttr || localAttr->serialized() != attr->serialized()) { 0412 return true; 0413 } 0414 } 0415 0416 return false; 0417 } 0418 0419 void createLocalCollections() 0420 { 0421 if (remoteCollectionsToCreate.isEmpty()) { 0422 updateLocalCollections(); 0423 return; 0424 } 0425 0426 for (auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end;) { 0427 const Collection col = *iter; 0428 const Collection parentCollection = col.parentCollection(); 0429 // The parent already exists locally 0430 if (parentCollection == akonadiRootCollection || parentCollection.id() > 0) { 0431 ++pendingJobs; 0432 auto create = new CollectionCreateJob(col, currentTransaction); 0433 QObject::connect(create, &KJob::result, q, [this](KJob *job) { 0434 createLocalCollectionResult(job); 0435 }); 0436 0437 // Commit transaction after every 100 collections are created, 0438 // otherwise it overloads database journal and things get veeery slow 0439 if (pendingJobs % 100 == 0) { 0440 currentTransaction->commit(); 0441 createTransaction(); 0442 } 0443 0444 iter = remoteCollectionsToCreate.erase(iter); 0445 end = remoteCollectionsToCreate.end(); 0446 } else { 0447 // Skip the collection, we'll try again once we create all the other 0448 // collection we already have a parent for 0449 ++iter; 0450 } 0451 } 0452 } 0453 0454 void createLocalCollectionResult(KJob *job) 0455 { 0456 --pendingJobs; 0457 if (job->error()) { 0458 return; // handled by the base class 0459 } 0460 0461 q->setProcessedAmount(KJob::Bytes, ++progress); 0462 0463 const Collection newLocal = static_cast<CollectionCreateJob *>(job)->collection(); 0464 uidRidMap.insert(newLocal.id(), newLocal.remoteId()); 0465 const RemoteId newLocalRID = remoteIdForCollection(newLocal); 0466 0467 // See if there are any pending collections that this collection is parent of and 0468 // update them if so 0469 for (auto iter = remoteCollectionsToCreate.begin(), end = remoteCollectionsToCreate.end(); iter != end; ++iter) { 0470 const Collection parentCollection = iter->parentCollection(); 0471 if (parentCollection != akonadiRootCollection && parentCollection.id() <= 0) { 0472 const RemoteId remoteRID = remoteIdForCollection(*iter); 0473 if (remoteRID.isAbsolute()) { 0474 if (newLocalRID == remoteIdForCollection(*iter)) { 0475 iter->setParentCollection(newLocal); 0476 } 0477 } else if (!hierarchicalRIDs) { 0478 if (remoteRID.ridChain.startsWith(parentCollection.remoteId())) { 0479 iter->setParentCollection(newLocal); 0480 } 0481 } 0482 } 0483 } 0484 0485 // Enqueue all pending remote collections that are children of the just-created 0486 // collection 0487 Collection::List collectionsToCreate = remoteCollections.take(newLocalRID); 0488 if (collectionsToCreate.isEmpty() && !hierarchicalRIDs) { 0489 collectionsToCreate = remoteCollections.take(RemoteId(newLocal.remoteId())); 0490 } 0491 for (Collection col : std::as_const(collectionsToCreate)) { 0492 col.setParentCollection(newLocal); 0493 remoteCollectionsToCreate.append(col); 0494 } 0495 0496 // If there are still any collections to create left, try if we just created 0497 // a parent for any of them 0498 if (!remoteCollectionsToCreate.isEmpty()) { 0499 createLocalCollections(); 0500 } else if (pendingJobs == 0) { 0501 Q_ASSERT(remoteCollectionsToCreate.isEmpty()); 0502 if (!remoteCollections.isEmpty()) { 0503 currentTransaction->rollback(); 0504 q->setError(CollectionSync::Unknown); 0505 q->setErrorText(i18n("Found unresolved orphan collections")); 0506 qCWarning(AKONADICORE_LOG) << "found unresolved orphan collection"; 0507 emitResult(); 0508 return; 0509 } 0510 0511 currentTransaction->commit(); 0512 createTransaction(); 0513 0514 // Otherwise move to next task: updating existing collections 0515 updateLocalCollections(); 0516 } 0517 /* 0518 * else if (!remoteCollections.isEmpty()) { 0519 currentTransaction->rollback(); 0520 q->setError(Unknown); 0521 q->setErrorText(i18n("Incomplete collection tree")); 0522 emitResult(); 0523 return; 0524 } 0525 */ 0526 } 0527 0528 /** 0529 Performs a local update for the given node pair. 0530 */ 0531 void updateLocalCollections() 0532 { 0533 if (remoteCollectionsToUpdate.isEmpty()) { 0534 deleteLocalCollections(); 0535 return; 0536 } 0537 0538 using CollectionPair = QPair<Collection, Collection>; 0539 for (const CollectionPair &pair : std::as_const(remoteCollectionsToUpdate)) { 0540 const Collection local = pair.first; 0541 const Collection remote = pair.second; 0542 Collection upd(remote); 0543 0544 Q_ASSERT(!upd.remoteId().isEmpty()); 0545 Q_ASSERT(currentTransaction); 0546 upd.setId(local.id()); 0547 if (ignoreAttributeChanges(remote, CONTENTMIMETYPES)) { 0548 upd.setContentMimeTypes(local.contentMimeTypes()); 0549 } 0550 const auto remoteAttributes = upd.attributes(); 0551 for (Attribute *remoteAttr : remoteAttributes) { 0552 if (ignoreAttributeChanges(remote, remoteAttr->type()) && local.hasAttribute(remoteAttr->type())) { 0553 // We don't want to overwrite the attribute changes with the defaults provided by the resource. 0554 const Attribute *localAttr = local.attribute(remoteAttr->type()); 0555 upd.removeAttribute(localAttr->type()); 0556 upd.addAttribute(localAttr->clone()); 0557 } 0558 } 0559 0560 // ### HACK to work around the implicit move attempts of CollectionModifyJob 0561 // which we do explicitly below 0562 Collection c(upd); 0563 c.setParentCollection(local.parentCollection()); 0564 ++pendingJobs; 0565 auto mod = new CollectionModifyJob(c, currentTransaction); 0566 QObject::connect(mod, &KJob::result, q, [this](KJob *job) { 0567 updateLocalCollectionResult(job); 0568 }); 0569 0570 // detecting moves is only possible with global RIDs 0571 if (!hierarchicalRIDs) { 0572 if (remote.parentCollection().isValid() && remote.parentCollection().id() != local.parentCollection().id()) { 0573 ++pendingJobs; 0574 auto move = new CollectionMoveJob(upd, remote.parentCollection(), currentTransaction); 0575 QObject::connect(move, &KJob::result, q, [this](KJob *job) { 0576 updateLocalCollectionResult(job); 0577 }); 0578 } 0579 } 0580 } 0581 } 0582 0583 void updateLocalCollectionResult(KJob *job) 0584 { 0585 --pendingJobs; 0586 if (job->error()) { 0587 return; // handled by the base class 0588 } 0589 if (qobject_cast<CollectionModifyJob *>(job)) { 0590 q->setProcessedAmount(KJob::Bytes, ++progress); 0591 } 0592 0593 // All updates are done, time to move on to next task: deletion 0594 if (pendingJobs == 0) { 0595 currentTransaction->commit(); 0596 createTransaction(); 0597 0598 deleteLocalCollections(); 0599 } 0600 } 0601 0602 void deleteLocalCollections() 0603 { 0604 if (localCollectionsToRemove.isEmpty()) { 0605 done(); 0606 return; 0607 } 0608 0609 for (const Collection &col : std::as_const(localCollectionsToRemove)) { 0610 Q_ASSERT(!col.remoteId().isEmpty()); // empty RID -> stuff we haven't even written to the remote side yet 0611 0612 ++pendingJobs; 0613 Q_ASSERT(currentTransaction); 0614 auto job = new CollectionDeleteJob(col, currentTransaction); 0615 QObject::connect(job, &KJob::result, q, [this](KJob *job) { 0616 deleteLocalCollectionsResult(job); 0617 }); 0618 0619 // It can happen that the groupware servers report us deleted collections 0620 // twice, in this case this collection delete job will fail on the second try. 0621 // To avoid a rollback of the complete transaction we gracefully allow the job 0622 // to fail :) 0623 currentTransaction->setIgnoreJobFailure(job); 0624 } 0625 } 0626 0627 void deleteLocalCollectionsResult(KJob * /*unused*/) 0628 { 0629 --pendingJobs; 0630 q->setProcessedAmount(KJob::Bytes, ++progress); 0631 0632 if (pendingJobs == 0) { 0633 currentTransaction->commit(); 0634 currentTransaction = nullptr; 0635 0636 done(); 0637 } 0638 } 0639 0640 void done() 0641 { 0642 if (currentTransaction) { 0643 // This can trigger a direct call of transactionSequenceResult 0644 currentTransaction->commit(); 0645 currentTransaction = nullptr; 0646 } 0647 0648 if (!remoteCollections.isEmpty()) { 0649 q->setError(CollectionSync::Unknown); 0650 q->setErrorText(i18n("Found unresolved orphan collections")); 0651 } 0652 emitResult(); 0653 } 0654 0655 void emitResult() 0656 { 0657 // Prevent double result emission 0658 Q_ASSERT(!resultEmitted); 0659 if (!resultEmitted) { 0660 if (q->hasSubjobs()) { 0661 // If there are subjobs, pick one, wait for it to finish, then 0662 // try again. This way we make sure we don't emit result() signal 0663 // while there is still a Transaction job running 0664 KJob *subjob = q->subjobs().at(0); 0665 QObject::connect( 0666 subjob, 0667 &KJob::result, 0668 q, 0669 [this](KJob * /*unused*/) { 0670 emitResult(); 0671 }, 0672 Qt::QueuedConnection); 0673 } else { 0674 resultEmitted = true; 0675 q->emitResult(); 0676 } 0677 } 0678 } 0679 0680 void createTransaction() 0681 { 0682 currentTransaction = new TransactionSequence(q); 0683 currentTransaction->setAutomaticCommittingEnabled(false); 0684 q->connect(currentTransaction, &TransactionSequence::finished, q, [this](KJob *job) { 0685 transactionSequenceResult(job); 0686 }); 0687 } 0688 0689 /** After the transaction has finished report we're done as well. */ 0690 void transactionSequenceResult(KJob *job) 0691 { 0692 if (job->error()) { 0693 return; // handled by the base class 0694 } 0695 0696 // If this was the last transaction, then finish, otherwise there's 0697 // a new transaction in the queue already 0698 if (job == currentTransaction) { 0699 currentTransaction = nullptr; 0700 } 0701 } 0702 0703 /** 0704 Process what's currently available. 0705 */ 0706 void execute() 0707 { 0708 qCDebug(AKONADICORE_LOG) << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone; 0709 if (!localListDone && !deliveryDone) { 0710 return; 0711 } 0712 0713 if (!localListDone && deliveryDone) { 0714 Job *parent = (currentTransaction ? static_cast<Job *>(currentTransaction) : static_cast<Job *>(q)); 0715 auto job = new CollectionFetchJob(akonadiRootCollection, CollectionFetchJob::Recursive, parent); 0716 job->fetchScope().setResource(resourceId); 0717 job->fetchScope().setListFilter(CollectionFetchScope::NoFilter); 0718 job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All); 0719 q->connect(job, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) { 0720 localCollectionsReceived(cols); 0721 }); 0722 q->connect(job, &KJob::result, q, [this](KJob *job) { 0723 localCollectionFetchResult(job); 0724 }); 0725 return; 0726 } 0727 0728 // If a transaction is not started yet, it means we just finished local listing 0729 if (!currentTransaction) { 0730 // There's nothing to do after local listing -> we are done! 0731 if (remoteCollectionsToCreate.isEmpty() && remoteCollectionsToUpdate.isEmpty() && localCollectionsToRemove.isEmpty()) { 0732 qCDebug(AKONADICORE_LOG) << "Nothing to do"; 0733 emitResult(); 0734 return; 0735 } 0736 // Ok, there's some work to do, so create a transaction we can use 0737 createTransaction(); 0738 } 0739 0740 createLocalCollections(); 0741 } 0742 0743 CollectionSync *const q; 0744 0745 QString resourceId; 0746 0747 int pendingJobs; 0748 int progress; 0749 0750 TransactionSequence *currentTransaction; 0751 0752 bool incremental; 0753 bool streaming; 0754 bool hierarchicalRIDs; 0755 0756 bool localListDone; 0757 bool deliveryDone; 0758 0759 // List of parts where local changes should not be overwritten 0760 QSet<QByteArray> keepLocalChanges; 0761 0762 QHash<RemoteId /* parent */, Collection::List /* children */> removedRemoteCollections; 0763 QHash<RemoteId /* parent */, Collection::List /* children */> remoteCollections; 0764 QHash<RemoteId /* parent */, Collection::List /* children */> localCollections; 0765 0766 Collection::List localCollectionsToRemove; 0767 Collection::List remoteCollectionsToCreate; 0768 QList<QPair<Collection /* local */, Collection /* remote */>> remoteCollectionsToUpdate; 0769 QHash<Collection::Id, QString> uidRidMap; 0770 0771 // HACK: To workaround Collection copy constructor being very expensive, we 0772 // store the Collection::root() collection in a variable here for faster 0773 // access 0774 Collection akonadiRootCollection; 0775 0776 bool resultEmitted; 0777 }; 0778 0779 CollectionSync::CollectionSync(const QString &resourceId, QObject *parent) 0780 : Job(parent) 0781 , d(new CollectionSyncPrivate(this)) 0782 { 0783 d->resourceId = resourceId; 0784 setTotalAmount(KJob::Bytes, 0); 0785 } 0786 0787 CollectionSync::~CollectionSync() = default; 0788 0789 void CollectionSync::setRemoteCollections(const Collection::List &remoteCollections) 0790 { 0791 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + remoteCollections.count()); 0792 for (const Collection &c : remoteCollections) { 0793 d->addRemoteColection(c); 0794 } 0795 0796 if (!d->streaming) { 0797 d->deliveryDone = true; 0798 } 0799 d->execute(); 0800 } 0801 0802 void CollectionSync::setRemoteCollections(const Collection::List &changedCollections, const Collection::List &removedCollections) 0803 { 0804 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + changedCollections.count()); 0805 d->incremental = true; 0806 for (const Collection &c : changedCollections) { 0807 d->addRemoteColection(c); 0808 } 0809 for (const Collection &c : removedCollections) { 0810 d->addRemoteColection(c, true); 0811 } 0812 0813 if (!d->streaming) { 0814 d->deliveryDone = true; 0815 } 0816 d->execute(); 0817 } 0818 0819 void CollectionSync::doStart() 0820 { 0821 } 0822 0823 void CollectionSync::setStreamingEnabled(bool streaming) 0824 { 0825 d->streaming = streaming; 0826 } 0827 0828 void CollectionSync::retrievalDone() 0829 { 0830 d->deliveryDone = true; 0831 d->execute(); 0832 } 0833 0834 void CollectionSync::setHierarchicalRemoteIds(bool hierarchical) 0835 { 0836 d->hierarchicalRIDs = hierarchical; 0837 } 0838 0839 void CollectionSync::rollback() 0840 { 0841 if (d->currentTransaction) { 0842 d->currentTransaction->rollback(); 0843 } else { 0844 setError(UserCanceled); 0845 emitResult(); 0846 } 0847 } 0848 0849 void CollectionSync::setKeepLocalChanges(const QSet<QByteArray> &parts) 0850 { 0851 d->keepLocalChanges = parts; 0852 } 0853 0854 #include "moc_collectionsync_p.cpp"