Akonadi

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

KDE's Doxygen guidelines are available online.