1/*
2 Copyright (c) 2007, 2009 Volker Krause <vkrause@kde.org>
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
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 <kdebug.h>
33#include <KLocalizedString>
34#include <QtCore/QVariant>
35
36using namespace Akonadi;
37
38struct RemoteNode;
39
40/**
41 LocalNode is used to build a tree structure of all our locally existing collections.
42*/
43struct LocalNode
44{
45 LocalNode(const Collection &col)
46 : collection(col)
47 , processed(false)
48 {}
49
50 ~LocalNode()
51 {
52 qDeleteAll(childNodes);
53 qDeleteAll(pendingRemoteNodes);
54 }
55
56 Collection collection;
57 QList<LocalNode *> childNodes;
58 QHash<QString, LocalNode *> childRidMap;
59 /** When using hierarchical RIDs we attach a list of not yet processable remote nodes to
60 the closest already existing local ancestor node. They will be re-evaluated once a new
61 child node is added. */
62 QList<RemoteNode *> pendingRemoteNodes;
63 bool processed;
64};
65
66Q_DECLARE_METATYPE(LocalNode *)
67static const char LOCAL_NODE[] = "LocalNode";
68
69/**
70 RemoteNode is used as a container for remote collections which typically don't have a UID set
71 and thus cannot easily be compared or put into maps etc.
72*/
73struct RemoteNode
74{
75 RemoteNode(const Collection &col)
76 : collection(col)
77 {}
78
79 Collection collection;
80};
81
82Q_DECLARE_METATYPE(RemoteNode *)
83static const char REMOTE_NODE[] = "RemoteNode";
84
85static const char CONTENTMIMETYPES[] = "CONTENTMIMETYPES";
86
87/**
88 * @internal
89 */
90class CollectionSync::Private
91{
92public:
93 Private(CollectionSync *parent)
94 : q(parent)
95 , pendingJobs(0)
96 , progress(0)
97 , localRoot(0)
98 , currentTransaction(0)
99 , knownLocalCollections(0)
100 , incremental(false)
101 , streaming(false)
102 , hierarchicalRIDs(false)
103 , localListDone(false)
104 , deliveryDone(false)
105 {
106 }
107
108 ~Private()
109 {
110 delete localRoot;
111 qDeleteAll(rootRemoteNodes);
112 }
113
114 /** Utility method to reset the node tree. */
115 void resetNodeTree()
116 {
117 delete localRoot;
118 localRoot = new LocalNode(Collection::root());
119 localRoot->processed = true; // never try to delete that one
120 if (currentTransaction) {
121 // we are running the update transaction, initialize pending remote nodes
122 localRoot->pendingRemoteNodes.swap(rootRemoteNodes);
123 }
124
125 localUidMap.clear();
126 localRidMap.clear();
127 localUidMap.insert(localRoot->collection.id(), localRoot);
128 if (!hierarchicalRIDs) {
129 localRidMap.insert(QString(), localRoot);
130 }
131 }
132
133 /** Create a local node from the given local collection and integrate it into the local tree structure. */
134 LocalNode *createLocalNode(const Collection &col)
135 {
136 LocalNode *node = new LocalNode(col);
137 Q_ASSERT(!localUidMap.contains(col.id()));
138 localUidMap.insert(node->collection.id(), node);
139 if (!hierarchicalRIDs && !col.remoteId().isEmpty()) {
140 localRidMap.insert(node->collection.remoteId(), node);
141 }
142
143 // add already existing children
144 if (localPendingCollections.contains(col.id())) {
145 QVector<Collection::Id> childIds = localPendingCollections.take(col.id());
146 foreach (Collection::Id childId, childIds) {
147 Q_ASSERT(localUidMap.contains(childId));
148 LocalNode *childNode = localUidMap.value(childId);
149 node->childNodes.append(childNode);
150 if (!childNode->collection.remoteId().isEmpty()) {
151 node->childRidMap.insert(childNode->collection.remoteId(), childNode);
152 }
153 }
154 }
155
156 // set our parent and add ourselves as child
157 if (localUidMap.contains(col.parentCollection().id())) {
158 LocalNode *parentNode = localUidMap.value(col.parentCollection().id());
159 parentNode->childNodes.append(node);
160 if (!node->collection.remoteId().isEmpty()) {
161 parentNode->childRidMap.insert(node->collection.remoteId(), node);
162 }
163 } else {
164 localPendingCollections[col.parentCollection().id()].append(col.id());
165 }
166
167 return node;
168 }
169
170 /** Same as createLocalNode() for remote collections. */
171 void createRemoteNode(const Collection &col)
172 {
173 if (col.remoteId().isEmpty()) {
174 kWarning() << "Collection '" << col.name() << "' does not have a remote identifier - skipping";
175 return;
176 }
177 RemoteNode *node = new RemoteNode(col);
178 rootRemoteNodes.append(node);
179 }
180
181 /** Create local nodes as we receive the local listing from the Akonadi server. */
182 void localCollectionsReceived(const Akonadi::Collection::List &localCols)
183 {
184 foreach (const Collection &c, localCols) {
185 createLocalNode(c);
186 knownLocalCollections++;
187 }
188 }
189
190 /** Once the local collection listing finished we can continue with the interesting stuff. */
191 void localCollectionFetchResult(KJob *job)
192 {
193 if (job->error()) {
194 return; // handled by the base class
195 }
196
197 // safety check: the local tree has to be connected
198 if (!localPendingCollections.isEmpty()) {
199 q->setError(Unknown);
200 q->setErrorText(i18n("Inconsistent local collection tree detected."));
201 q->emitResult();
202 return;
203 }
204
205 localListDone = true;
206 execute();
207 }
208
209 /**
210 * Find a child node with matching collection name.
211 * @note This is used as a fallback if the resource lost the RID update somehow.
212 * This can be used because the Akonadi server enforces unique child collection names inside the hierarchy
213 */
214 LocalNode *findLocalChildNodeByName(LocalNode *localParentNode, const QString &name) const
215 {
216 if (name.isEmpty()) { // shouldn't happen...
217 return 0;
218 }
219
220 if (localParentNode == localRoot) { // possibly non-unique names on top-level
221 return 0;
222 }
223
224 foreach (LocalNode *childNode, localParentNode->childNodes) {
225 // the restriction on empty RIDs can possibly removed, but for now I only understand the implication for this case
226 if (childNode->collection.name() == name && childNode->collection.remoteId().isEmpty()) {
227 return childNode;
228 }
229 }
230 return 0;
231 }
232
233 /**
234 Find the local node that matches the given remote collection, returns 0
235 if that doesn't exist (yet).
236 */
237 LocalNode *findMatchingLocalNode(const Collection &collection) const
238 {
239 if (!hierarchicalRIDs) {
240 if (localRidMap.contains(collection.remoteId())) {
241 return localRidMap.value(collection.remoteId());
242 }
243 return 0;
244 } else {
245 if (collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId()) {
246 return localRoot;
247 }
248 LocalNode *localParent = 0;
249 if (collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty()) {
250 kWarning() << "Remote collection without valid parent found: " << collection;
251 return 0;
252 }
253 if (collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId()) {
254 localParent = localRoot;
255 } else {
256 localParent = findMatchingLocalNode(collection.parentCollection());
257 }
258
259 if (localParent) {
260 if (localParent->childRidMap.contains(collection.remoteId())) {
261 return localParent->childRidMap.value(collection.remoteId());
262 }
263 // check if we have a local folder with a matching name and no RID, if so let's use that one
264 // we would get an error if we don't do this anyway, as we'd try to create two sibling nodes with the same name
265 if (LocalNode *recoveredLocalNode = findLocalChildNodeByName(localParent, collection.name())) {
266 kDebug() << "Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
267 return recoveredLocalNode;
268 }
269 }
270 return 0;
271 }
272 }
273
274 /**
275 Find the local node that is the nearest ancestor of the given remote collection
276 (when using hierarchical RIDs only, otherwise it's always the local root node).
277 Never returns 0.
278 */
279 LocalNode *findBestLocalAncestor(const Collection &collection, bool *exactMatch = 0)
280 {
281 if (!hierarchicalRIDs) {
282 return localRoot;
283 }
284 if (collection == Collection::root()) {
285 if (exactMatch) {
286 *exactMatch = true;
287 }
288 return localRoot;
289 }
290 if (collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty()) {
291 kWarning() << "Remote collection without valid parent found: " << collection;
292 return 0;
293 }
294 bool parentIsExact = false;
295 LocalNode *localParent = findBestLocalAncestor(collection.parentCollection(), &parentIsExact);
296 if (!parentIsExact) {
297 if (exactMatch) {
298 *exactMatch = false;
299 }
300 return localParent;
301 }
302 if (localParent->childRidMap.contains(collection.remoteId())) {
303 if (exactMatch) {
304 *exactMatch = true;
305 }
306 return localParent->childRidMap.value(collection.remoteId());
307 }
308 if (exactMatch) {
309 *exactMatch = false;
310 }
311 return localParent;
312 }
313
314 /**
315 Checks if any of the remote nodes is not equal to the current local one. If so return true.
316 */
317 bool checkPendingRemoteNodes() const
318 {
319 if (rootRemoteNodes.size() != knownLocalCollections) {
320 return true;
321 }
322
323 foreach (RemoteNode *remoteNode, rootRemoteNodes) {
324 // every remote note should have a local node already
325 LocalNode *localNode = findMatchingLocalNode(remoteNode->collection);
326 if (localNode) {
327 if (checkLocalCollection(localNode, remoteNode)) {
328 return true;
329 }
330 } else {
331 return true;
332 }
333 }
334 return false;
335 }
336
337 /**
338 Checks the pending remote nodes attached to the given local root node
339 to see if any of them can be processed by now. If not, they are moved to
340 the closest ancestor available.
341 */
342 void processPendingRemoteNodes(LocalNode *_localRoot)
343 {
344 QList<RemoteNode *> pendingRemoteNodes(_localRoot->pendingRemoteNodes);
345 _localRoot->pendingRemoteNodes.clear();
346 QHash<LocalNode *, QList<RemoteNode *> > pendingCreations;
347 foreach (RemoteNode *remoteNode, pendingRemoteNodes) {
348 // step 1: see if we have a matching local node already
349 LocalNode *localNode = findMatchingLocalNode(remoteNode->collection);
350 if (localNode) {
351 Q_ASSERT(!localNode->processed);
352 updateLocalCollection(localNode, remoteNode);
353 continue;
354 }
355 // step 2: check if we have the parent at least, then we can create it
356 localNode = findMatchingLocalNode(remoteNode->collection.parentCollection());
357 if (localNode) {
358 pendingCreations[localNode].append(remoteNode);
359 continue;
360 }
361 // step 3: find the best matching ancestor and enqueue it for later processing
362 localNode = findBestLocalAncestor(remoteNode->collection);
363 if (!localNode) {
364 q->setError(Unknown);
365 q->setErrorText(i18n("Remote collection without root-terminated ancestor chain provided, resource is broken."));
366 q->emitResult();
367 return;
368 }
369 localNode->pendingRemoteNodes.append(remoteNode);
370 }
371
372 // process the now possible collection creations
373 for (QHash<LocalNode *, QList<RemoteNode *> >::const_iterator it = pendingCreations.constBegin();
374 it != pendingCreations.constEnd(); ++it) {
375 createLocalCollections(it.key(), it.value());
376 }
377 }
378
379 /**
380 Checks if the given localNode and remoteNode are different
381 */
382 bool checkLocalCollection(LocalNode *localNode, RemoteNode *remoteNode) const
383 {
384 const Collection &localCollection = localNode->collection;
385 const Collection &remoteCollection = remoteNode->collection;
386
387 if (!keepLocalChanges.contains(CONTENTMIMETYPES)) {
388 if (localCollection.contentMimeTypes().size() != remoteCollection.contentMimeTypes().size()) {
389 return true;
390 } else {
391 for (int i = 0; i < remoteCollection.contentMimeTypes().size(); i++) {
392 const QString &m = remoteCollection.contentMimeTypes().at(i);
393 if (!localCollection.contentMimeTypes().contains(m)) {
394 return true;
395 }
396 }
397 }
398 }
399
400 if (localCollection.parentCollection().remoteId() != remoteCollection.parentCollection().remoteId()) {
401 return true;
402 }
403 if (localCollection.name() != remoteCollection.name()) {
404 return true;
405 }
406 if (localCollection.remoteId() != remoteCollection.remoteId()) {
407 return true;
408 }
409 if (localCollection.remoteRevision() != remoteCollection.remoteRevision()) {
410 return true;
411 }
412 if (!(localCollection.cachePolicy() == remoteCollection.cachePolicy())) {
413 return true;
414 }
415
416 // CollectionModifyJob adds the remote attributes to the local collection
417 foreach (const Attribute *attr, remoteCollection.attributes()) {
418 const Attribute *localAttr = localCollection.attribute(attr->type());
419 if (localAttr && keepLocalChanges.contains(attr->type())) {
420 continue;
421 }
422 // The attribute must both exist and have equal contents
423 if (!localAttr || localAttr->serialized() != attr->serialized()) {
424 return true;
425 }
426 }
427
428 return false;
429 }
430
431 /**
432 Performs a local update for the given node pair.
433 */
434 void updateLocalCollection(LocalNode *localNode, RemoteNode *remoteNode)
435 {
436 Collection upd(remoteNode->collection);
437 Q_ASSERT(!upd.remoteId().isEmpty());
438 Q_ASSERT(currentTransaction);
439 upd.setId(localNode->collection.id());
440 if (keepLocalChanges.contains(CONTENTMIMETYPES)) {
441 upd.setContentMimeTypes(localNode->collection.contentMimeTypes());
442 }
443 foreach (Attribute *remoteAttr, upd.attributes()) {
444 if (keepLocalChanges.contains(remoteAttr->type()) && localNode->collection.hasAttribute(remoteAttr->type())) {
445 //We don't want to overwrite the attribute changes with the defaults provided by the resource.
446 Attribute *localAttr = localNode->collection.attribute(remoteAttr->type());
447 upd.removeAttribute(localAttr->type());
448 upd.addAttribute(localAttr->clone());
449 }
450 }
451
452 {
453 // ### HACK to work around the implicit move attempts of CollectionModifyJob
454 // which we do explicitly below
455 Collection c(upd);
456 c.setParentCollection(localNode->collection.parentCollection());
457 ++pendingJobs;
458 CollectionModifyJob *mod = new CollectionModifyJob(c, currentTransaction);
459 connect(mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)));
460 }
461
462 // detecting moves is only possible with global RIDs
463 if (!hierarchicalRIDs) {
464 LocalNode *oldParent = localUidMap.value(localNode->collection.parentCollection().id());
465 LocalNode *newParent = findMatchingLocalNode(remoteNode->collection.parentCollection());
466 // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
467 // local parent has been created
468 if (newParent && oldParent != newParent) {
469 ++pendingJobs;
470 CollectionMoveJob *move = new CollectionMoveJob(upd, newParent->collection, currentTransaction);
471 connect(move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)));
472 }
473 }
474
475 localNode->processed = true;
476 delete remoteNode;
477 }
478
479 void updateLocalCollectionResult(KJob *job)
480 {
481 --pendingJobs;
482 if (job->error()) {
483 return; // handled by the base class
484 }
485 if (qobject_cast<CollectionModifyJob *>(job)) {
486 ++progress;
487 }
488 checkDone();
489 }
490
491 /**
492 Creates local folders for the given local parent and remote nodes.
493 @todo group CollectionCreateJobs into a single one once it supports that
494 */
495 void createLocalCollections(LocalNode *localParent, QList<RemoteNode *> remoteNodes)
496 {
497 foreach (RemoteNode *remoteNode, remoteNodes) {
498 ++pendingJobs;
499 Collection col(remoteNode->collection);
500 Q_ASSERT(!col.remoteId().isEmpty());
501 col.setParentCollection(localParent->collection);
502 CollectionCreateJob *create = new CollectionCreateJob(col, currentTransaction);
503 create->setProperty(LOCAL_NODE, QVariant::fromValue(localParent));
504 create->setProperty(REMOTE_NODE, QVariant::fromValue(remoteNode));
505 connect(create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)));
506 }
507 }
508
509 void createLocalCollectionResult(KJob *job)
510 {
511 --pendingJobs;
512 if (job->error()) {
513 return; // handled by the base class
514 }
515
516 const Collection newLocal = static_cast<CollectionCreateJob *>(job)->collection();
517 LocalNode *localNode = createLocalNode(newLocal);
518 localNode->processed = true;
519
520 LocalNode *localParent = job->property(LOCAL_NODE).value<LocalNode *>();
521 Q_ASSERT(localParent->childNodes.contains(localNode));
522 RemoteNode *remoteNode = job->property(REMOTE_NODE).value<RemoteNode *>();
523 delete remoteNode;
524 ++progress;
525
526 processPendingRemoteNodes(localParent);
527 if (!hierarchicalRIDs) {
528 processPendingRemoteNodes(localRoot);
529 }
530
531 checkDone();
532 }
533
534 /**
535 Checks if the given local node has processed child nodes.
536 */
537 bool hasProcessedChildren(LocalNode *localNode) const
538 {
539 if (localNode->processed) {
540 return true;
541 }
542 foreach (LocalNode *child, localNode->childNodes) {
543 if (hasProcessedChildren(child)) {
544 return true;
545 }
546 }
547 return false;
548 }
549
550 /**
551 Find all local nodes that are not marked as processed and have no children that
552 are marked as processed.
553 */
554 Collection::List findUnprocessedLocalCollections(LocalNode *localNode) const
555 {
556 Collection::List rv;
557 if (!localNode->processed) {
558 if (hasProcessedChildren(localNode)) {
559 kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
560 kWarning() << localNode->collection;
561 return rv;
562 }
563 if (localNode->collection.remoteId().isEmpty()) {
564 kWarning() << "Found unprocessed local node without remoteId, excluding from deletion";
565 kWarning() << localNode->collection;
566 return rv;
567 }
568 rv.append(localNode->collection);
569 return rv;
570 }
571
572 foreach (LocalNode *child, localNode->childNodes) {
573 rv.append(findUnprocessedLocalCollections(child));
574 }
575 return rv;
576 }
577
578 /**
579 Deletes unprocessed local nodes, in non-incremental mode.
580 */
581 void deleteUnprocessedLocalNodes()
582 {
583 if (incremental) {
584 return;
585 }
586 const Collection::List cols = findUnprocessedLocalCollections(localRoot);
587 deleteLocalCollections(cols);
588 }
589
590 /**
591 Deletes the given collection list.
592 @todo optimize delete job to support batch operations
593 */
594 void deleteLocalCollections(const Collection::List &cols)
595 {
596 q->setTotalAmount(KJob::Bytes, q->totalAmount(KJob::Bytes) + cols.size());
597 foreach (const Collection &col, cols) {
598 Q_ASSERT(!col.remoteId().isEmpty()); // empty RID -> stuff we haven't even written to the remote side yet
599
600 ++pendingJobs;
601 Q_ASSERT(currentTransaction);
602 CollectionDeleteJob *job = new CollectionDeleteJob(col, currentTransaction);
603 connect(job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)));
604
605 // It can happen that the groupware servers report us deleted collections
606 // twice, in this case this collection delete job will fail on the second try.
607 // To avoid a rollback of the complete transaction we gracefully allow the job
608 // to fail :)
609 currentTransaction->setIgnoreJobFailure(job);
610 }
611 }
612
613 void deleteLocalCollectionsResult(KJob *)
614 {
615 --pendingJobs;
616
617 ++progress;
618 checkDone();
619 }
620
621 /**
622 Check update necessity.
623 */
624 void checkUpdateNecessity()
625 {
626 bool updateNeeded = checkPendingRemoteNodes();
627 if (!updateNeeded) {
628 // We can end right now
629 q->emitResult();
630 return;
631 }
632
633 // Since there are differences with the remote collections we need to sync. Start a transaction here.
634 Q_ASSERT(!currentTransaction);
635 currentTransaction = new TransactionSequence(q);
636 currentTransaction->setAutomaticCommittingEnabled(false);
637 q->connect(currentTransaction, SIGNAL(result(KJob*)), SLOT(transactionSequenceResult(KJob*)));
638
639 // Now that a transaction is started we need to fetch local collections again and do the update
640 q->doStart();
641 }
642
643 /** After the transaction has finished report we're done as well. */
644 void transactionSequenceResult(KJob *job)
645 {
646 if (job->error()) {
647 return; // handled by the base class
648 }
649
650 q->emitResult();
651 }
652
653 /**
654 Process what's currently available.
655 */
656 void execute()
657 {
658 kDebug() << Q_FUNC_INFO << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
659 if (!localListDone || !deliveryDone) {
660 return;
661 }
662
663 // If a transaction is not started yet we are still checking if the update is
664 // actually needed.
665 if (!currentTransaction) {
666 checkUpdateNecessity();
667 return;
668 }
669
670 // Since the transaction is already running we need to execute the update.
671 processPendingRemoteNodes(localRoot);
672
673 if (!incremental && deliveryDone) {
674 deleteUnprocessedLocalNodes();
675 }
676
677 if (!hierarchicalRIDs) {
678 deleteLocalCollections(removedRemoteCollections);
679 } else {
680 Collection::List localCols;
681 foreach (const Collection &c, removedRemoteCollections) {
682 LocalNode *node = findMatchingLocalNode(c);
683 if (node) {
684 localCols.append(node->collection);
685 }
686 }
687 deleteLocalCollections(localCols);
688 }
689 removedRemoteCollections.clear();
690
691 checkDone();
692 }
693
694 /**
695 Finds pending remote nodes, which at the end of the day should be an empty set.
696 */
697 QList<RemoteNode *> findPendingRemoteNodes(LocalNode *localNode)
698 {
699 QList<RemoteNode *> rv;
700 rv.append(localNode->pendingRemoteNodes);
701 foreach (LocalNode *child, localNode->childNodes) {
702 rv.append(findPendingRemoteNodes(child));
703 }
704 return rv;
705 }
706
707 /**
708 Are we there yet??
709 @todo progress reporting
710 */
711 void checkDone()
712 {
713 q->setProcessedAmount(KJob::Bytes, progress);
714
715 // still running jobs or not fully delivered local/remote state
716 if (!deliveryDone || pendingJobs > 0 || !localListDone) {
717 return;
718 }
719
720 // safety check: there must be no pending remote nodes anymore
721 QList<RemoteNode *> orphans = findPendingRemoteNodes(localRoot);
722 if (!orphans.isEmpty()) {
723 q->setError(Unknown);
724 q->setErrorText(i18n("Found unresolved orphan collections"));
725 foreach (RemoteNode *orphan, orphans) {
726 kDebug() << "found orphan collection:" << orphan->collection;
727 }
728 q->emitResult();
729 return;
730 }
731
732 kDebug() << Q_FUNC_INFO << "q->commit()";
733 Q_ASSERT(currentTransaction);
734 currentTransaction->commit();
735 }
736
737 CollectionSync *q;
738
739 QString resourceId;
740
741 int pendingJobs;
742 int progress;
743
744 LocalNode *localRoot;
745 TransactionSequence *currentTransaction;
746 QHash<Collection::Id, LocalNode *> localUidMap;
747 QHash<QString, LocalNode *> localRidMap;
748
749 // temporary during build-up of the local node tree, must be empty afterwards
750 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
751
752 // removed remote collections in incremental mode
753 Collection::List removedRemoteCollections;
754
755 // used to store the list of remote nodes passed by the user
756 QList<RemoteNode *> rootRemoteNodes;
757
758 // keep track of the total number of local collections that are known
759 // only used during the preliminary check to see if updating is needed
760 int knownLocalCollections;
761
762 bool incremental;
763 bool streaming;
764 bool hierarchicalRIDs;
765
766 bool localListDone;
767 bool deliveryDone;
768
769 // List of parts where local changes should not be overwritten
770 QSet<QByteArray> keepLocalChanges;
771};
772
773CollectionSync::CollectionSync(const QString &resourceId, QObject *parent)
774 : Job(parent)
775 , d(new Private(this))
776{
777 d->resourceId = resourceId;
778 setTotalAmount(KJob::Bytes, 0);
779}
780
781CollectionSync::~CollectionSync()
782{
783 delete d;
784}
785
786void CollectionSync::setRemoteCollections(const Collection::List &remoteCollections)
787{
788 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + remoteCollections.count());
789 foreach (const Collection &c, remoteCollections) {
790 d->createRemoteNode(c);
791 }
792
793 if (!d->streaming) {
794 d->deliveryDone = true;
795 }
796 d->execute();
797}
798
799void CollectionSync::setRemoteCollections(const Collection::List &changedCollections, const Collection::List &removedCollections)
800{
801 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + changedCollections.count());
802 d->incremental = true;
803 foreach (const Collection &c, changedCollections) {
804 d->createRemoteNode(c);
805 }
806 d->removedRemoteCollections += removedCollections;
807
808 if (!d->streaming) {
809 d->deliveryDone = true;
810 }
811 d->execute();
812}
813
814void CollectionSync::doStart()
815{
816 d->resetNodeTree();
817 Job *parent = (d->currentTransaction ? static_cast<Job *>(d->currentTransaction) : static_cast<Job *>(this));
818 CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, parent);
819 job->fetchScope().setResource(d->resourceId);
820 job->fetchScope().setIncludeUnsubscribed(true);
821 job->fetchScope().setAncestorRetrieval(CollectionFetchScope::Parent);
822 connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
823 SLOT(localCollectionsReceived(Akonadi::Collection::List)));
824 connect(job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)));
825}
826
827void CollectionSync::setStreamingEnabled(bool streaming)
828{
829 d->streaming = streaming;
830}
831
832void CollectionSync::retrievalDone()
833{
834 d->deliveryDone = true;
835 d->execute();
836}
837
838void CollectionSync::setHierarchicalRemoteIds(bool hierarchical)
839{
840 d->hierarchicalRIDs = hierarchical;
841}
842
843void CollectionSync::rollback()
844{
845 if (d->currentTransaction) {
846 d->currentTransaction->rollback();
847 }
848}
849
850void CollectionSync::setKeepLocalChanges(const QSet<QByteArray> &parts)
851{
852 d->keepLocalChanges = parts;
853}
854
855#include "moc_collectionsync_p.cpp"
856