Akonadi

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

KDE's Doxygen guidelines are available online.