20#include "collectionsync_p.h"
21#include "collection.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"
30#include "cachepolicy.h"
33#include <KLocalizedString>
34#include <QtCore/QVariant>
52 qDeleteAll(childNodes);
53 qDeleteAll(pendingRemoteNodes);
57 QList<LocalNode *> childNodes;
58 QHash<QString, LocalNode *> childRidMap;
62 QList<RemoteNode *> pendingRemoteNodes;
66Q_DECLARE_METATYPE(LocalNode *)
67static const char LOCAL_NODE[] =
"LocalNode";
82Q_DECLARE_METATYPE(RemoteNode *)
83static const char REMOTE_NODE[] =
"RemoteNode";
85static const char CONTENTMIMETYPES[] =
"CONTENTMIMETYPES";
90class CollectionSync::Private
98 , currentTransaction(0)
99 , knownLocalCollections(0)
102 , hierarchicalRIDs(false)
103 , localListDone(false)
104 , deliveryDone(false)
111 qDeleteAll(rootRemoteNodes);
119 localRoot->processed =
true;
120 if (currentTransaction) {
122 localRoot->pendingRemoteNodes.swap(rootRemoteNodes);
127 localUidMap.insert(localRoot->collection.id(), localRoot);
128 if (!hierarchicalRIDs) {
129 localRidMap.insert(QString(), localRoot);
134 LocalNode *createLocalNode(
const Collection &col)
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);
144 if (localPendingCollections.contains(col.
id())) {
145 QVector<Collection::Id> childIds = localPendingCollections.take(col.
id());
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);
159 parentNode->childNodes.append(node);
160 if (!node->collection.remoteId().isEmpty()) {
161 parentNode->childRidMap.insert(node->collection.remoteId(), node);
174 kWarning() <<
"Collection '" << col.
name() <<
"' does not have a remote identifier - skipping";
177 RemoteNode *node =
new RemoteNode(col);
178 rootRemoteNodes.append(node);
186 knownLocalCollections++;
191 void localCollectionFetchResult(KJob *job)
198 if (!localPendingCollections.isEmpty()) {
200 q->setErrorText(i18n(
"Inconsistent local collection tree detected."));
205 localListDone =
true;
214 LocalNode *findLocalChildNodeByName(LocalNode *localParentNode,
const QString &name)
const
216 if (name.isEmpty()) {
220 if (localParentNode == localRoot) {
224 foreach (LocalNode *childNode, localParentNode->childNodes) {
226 if (childNode->collection.name() == name && childNode->collection.remoteId().isEmpty()) {
237 LocalNode *findMatchingLocalNode(
const Collection &collection)
const
239 if (!hierarchicalRIDs) {
240 if (localRidMap.contains(collection.remoteId())) {
241 return localRidMap.value(collection.remoteId());
248 LocalNode *localParent = 0;
249 if (collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty()) {
250 kWarning() <<
"Remote collection without valid parent found: " << collection;
254 localParent = localRoot;
256 localParent = findMatchingLocalNode(collection.parentCollection());
260 if (localParent->childRidMap.contains(collection.remoteId())) {
261 return localParent->childRidMap.value(collection.remoteId());
265 if (LocalNode *recoveredLocalNode = findLocalChildNodeByName(localParent, collection.name())) {
266 kDebug() <<
"Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
267 return recoveredLocalNode;
279 LocalNode *findBestLocalAncestor(
const Collection &collection,
bool *exactMatch = 0)
281 if (!hierarchicalRIDs) {
290 if (collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty()) {
291 kWarning() <<
"Remote collection without valid parent found: " << collection;
294 bool parentIsExact =
false;
295 LocalNode *localParent = findBestLocalAncestor(collection.parentCollection(), &parentIsExact);
296 if (!parentIsExact) {
302 if (localParent->childRidMap.contains(collection.remoteId())) {
306 return localParent->childRidMap.value(collection.remoteId());
317 bool checkPendingRemoteNodes()
const
319 if (rootRemoteNodes.size() != knownLocalCollections) {
323 foreach (RemoteNode *remoteNode, rootRemoteNodes) {
325 LocalNode *localNode = findMatchingLocalNode(remoteNode->collection);
327 if (checkLocalCollection(localNode, remoteNode)) {
342 void processPendingRemoteNodes(LocalNode *_localRoot)
344 QList<RemoteNode *> pendingRemoteNodes(_localRoot->pendingRemoteNodes);
345 _localRoot->pendingRemoteNodes.clear();
346 QHash<LocalNode *, QList<RemoteNode *> > pendingCreations;
347 foreach (RemoteNode *remoteNode, pendingRemoteNodes) {
349 LocalNode *localNode = findMatchingLocalNode(remoteNode->collection);
351 Q_ASSERT(!localNode->processed);
352 updateLocalCollection(localNode, remoteNode);
356 localNode = findMatchingLocalNode(remoteNode->collection.parentCollection());
358 pendingCreations[localNode].append(remoteNode);
362 localNode = findBestLocalAncestor(remoteNode->collection);
365 q->setErrorText(i18n(
"Remote collection without root-terminated ancestor chain provided, resource is broken."));
369 localNode->pendingRemoteNodes.append(remoteNode);
373 for (QHash<LocalNode *, QList<RemoteNode *> >::const_iterator it = pendingCreations.constBegin();
374 it != pendingCreations.constEnd(); ++it) {
375 createLocalCollections(it.key(), it.value());
382 bool checkLocalCollection(LocalNode *localNode, RemoteNode *remoteNode)
const
384 const Collection &localCollection = localNode->collection;
385 const Collection &remoteCollection = remoteNode->collection;
387 if (!keepLocalChanges.contains(CONTENTMIMETYPES)) {
403 if (localCollection.
name() != remoteCollection.
name()) {
419 if (localAttr && keepLocalChanges.contains(attr->
type())) {
434 void updateLocalCollection(LocalNode *localNode, RemoteNode *remoteNode)
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());
443 foreach (
Attribute *remoteAttr, upd.attributes()) {
444 if (keepLocalChanges.contains(remoteAttr->
type()) && localNode->collection.hasAttribute(remoteAttr->
type())) {
446 Attribute *localAttr = localNode->collection.attribute(remoteAttr->
type());
447 upd.removeAttribute(localAttr->
type());
448 upd.addAttribute(localAttr->
clone());
456 c.setParentCollection(localNode->collection.parentCollection());
459 connect(mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)));
463 if (!hierarchicalRIDs) {
464 LocalNode *oldParent = localUidMap.value(localNode->collection.parentCollection().id());
465 LocalNode *newParent = findMatchingLocalNode(remoteNode->collection.parentCollection());
468 if (newParent && oldParent != newParent) {
471 connect(move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)));
475 localNode->processed =
true;
479 void updateLocalCollectionResult(KJob *job)
485 if (qobject_cast<CollectionModifyJob *>(job)) {
495 void createLocalCollections(LocalNode *localParent, QList<RemoteNode *> remoteNodes)
497 foreach (RemoteNode *remoteNode, remoteNodes) {
500 Q_ASSERT(!col.
remoteId().isEmpty());
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*)));
509 void createLocalCollectionResult(KJob *job)
517 LocalNode *localNode = createLocalNode(newLocal);
518 localNode->processed =
true;
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 *>();
526 processPendingRemoteNodes(localParent);
527 if (!hierarchicalRIDs) {
528 processPendingRemoteNodes(localRoot);
537 bool hasProcessedChildren(LocalNode *localNode)
const
539 if (localNode->processed) {
542 foreach (LocalNode *child, localNode->childNodes) {
543 if (hasProcessedChildren(child)) {
554 Collection::List findUnprocessedLocalCollections(LocalNode *localNode)
const
557 if (!localNode->processed) {
558 if (hasProcessedChildren(localNode)) {
559 kWarning() <<
"Found unprocessed local node with processed children, excluding from deletion";
560 kWarning() << localNode->collection;
563 if (localNode->collection.remoteId().isEmpty()) {
564 kWarning() <<
"Found unprocessed local node without remoteId, excluding from deletion";
565 kWarning() << localNode->collection;
568 rv.append(localNode->collection);
572 foreach (LocalNode *child, localNode->childNodes) {
573 rv.append(findUnprocessedLocalCollections(child));
581 void deleteUnprocessedLocalNodes()
587 deleteLocalCollections(cols);
596 q->setTotalAmount(KJob::Bytes, q->totalAmount(KJob::Bytes) + cols.size());
598 Q_ASSERT(!col.
remoteId().isEmpty());
601 Q_ASSERT(currentTransaction);
603 connect(job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)));
609 currentTransaction->setIgnoreJobFailure(job);
613 void deleteLocalCollectionsResult(KJob *)
624 void checkUpdateNecessity()
626 bool updateNeeded = checkPendingRemoteNodes();
634 Q_ASSERT(!currentTransaction);
636 currentTransaction->setAutomaticCommittingEnabled(
false);
637 q->connect(currentTransaction, SIGNAL(result(KJob*)), SLOT(transactionSequenceResult(KJob*)));
644 void transactionSequenceResult(KJob *job)
658 kDebug() << Q_FUNC_INFO <<
"localListDone: " << localListDone <<
" deliveryDone: " << deliveryDone;
659 if (!localListDone || !deliveryDone) {
665 if (!currentTransaction) {
666 checkUpdateNecessity();
671 processPendingRemoteNodes(localRoot);
673 if (!incremental && deliveryDone) {
674 deleteUnprocessedLocalNodes();
677 if (!hierarchicalRIDs) {
678 deleteLocalCollections(removedRemoteCollections);
681 foreach (
const Collection &c, removedRemoteCollections) {
682 LocalNode *node = findMatchingLocalNode(c);
684 localCols.append(node->collection);
687 deleteLocalCollections(localCols);
689 removedRemoteCollections.clear();
697 QList<RemoteNode *> findPendingRemoteNodes(LocalNode *localNode)
699 QList<RemoteNode *> rv;
700 rv.append(localNode->pendingRemoteNodes);
701 foreach (LocalNode *child, localNode->childNodes) {
702 rv.append(findPendingRemoteNodes(child));
713 q->setProcessedAmount(KJob::Bytes, progress);
716 if (!deliveryDone || pendingJobs > 0 || !localListDone) {
721 QList<RemoteNode *> orphans = findPendingRemoteNodes(localRoot);
722 if (!orphans.isEmpty()) {
724 q->setErrorText(i18n(
"Found unresolved orphan collections"));
725 foreach (RemoteNode *orphan, orphans) {
726 kDebug() <<
"found orphan collection:" << orphan->collection;
732 kDebug() << Q_FUNC_INFO <<
"q->commit()";
733 Q_ASSERT(currentTransaction);
734 currentTransaction->commit();
744 LocalNode *localRoot;
746 QHash<Collection::Id, LocalNode *> localUidMap;
747 QHash<QString, LocalNode *> localRidMap;
750 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
756 QList<RemoteNode *> rootRemoteNodes;
760 int knownLocalCollections;
764 bool hierarchicalRIDs;
770 QSet<QByteArray> keepLocalChanges;
775 , d(new Private(this))
777 d->resourceId = resourceId;
778 setTotalAmount(KJob::Bytes, 0);
788 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + remoteCollections.count());
789 foreach (
const Collection &c, remoteCollections) {
790 d->createRemoteNode(c);
794 d->deliveryDone =
true;
801 setTotalAmount(KJob::Bytes, totalAmount(KJob::Bytes) + changedCollections.count());
802 d->incremental =
true;
803 foreach (
const Collection &c, changedCollections) {
804 d->createRemoteNode(c);
806 d->removedRemoteCollections += removedCollections;
809 d->deliveryDone =
true;
817 Job *parent = (d->currentTransaction ?
static_cast<Job *
>(d->currentTransaction) :
static_cast<Job *
>(
this));
824 connect(job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)));
829 d->streaming = streaming;
834 d->deliveryDone =
true;
840 d->hierarchicalRIDs = hierarchical;
845 if (d->currentTransaction) {
846 d->currentTransaction->rollback();
852 d->keepLocalChanges = parts;
855#include "moc_collectionsync_p.cpp"
Provides interface for custom attributes for Entity.
virtual QByteArray serialized() const =0
Returns a QByteArray representation of the attribute which will be storaged.
virtual Attribute * clone() const =0
Creates a copy of this attribute.
virtual QByteArray type() const =0
Returns the type of the attribute.
Job that creates a new collection in the Akonadi storage.
Job that deletes a collection in the Akonadi storage.
Job that fetches collections from the Akonadi storage.
CollectionFetchScope & fetchScope()
Returns the collection fetch scope.
@ Recursive
List all sub-collections.
void setResource(const QString &resource)
Sets a resource filter, that is only collections owned by the specified resource are retrieved.
AKONADI_DEPRECATED void setIncludeUnsubscribed(bool include)
Sets whether unsubscribed collections should be included in the collection listing.
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
Sets how many levels of ancestor collections should be included in the retrieval.
@ Parent
Only retrieve the immediate parent collection.
Job that modifies a collection in the Akonadi storage.
Job that moves a collection in the Akonadi storage to a new parent collection.
void rollback()
Do a rollback operation if needed.
~CollectionSync()
Destroys this job.
void setStreamingEnabled(bool streaming)
Enables streaming, that is not all collections are delivered at once.
void setKeepLocalChanges(const QSet< QByteArray > &parts)
Allows to specify parts of the collection that should not be changed if locally available.
void retrievalDone()
Indicate that all collections have been retrieved in streaming mode.
void setHierarchicalRemoteIds(bool hierarchical)
Indicate whether the resource supplies collections with hierarchical or global remote identifiers.
CollectionSync(const QString &resourceId, QObject *parent=0)
Creates a new collection synchronzier.
void doStart()
This method must be reimplemented in the concrete jobs.
void setRemoteCollections(const Collection::List &remoteCollections)
Sets the result of a full remote collection listing.
Represents a collection of PIM items.
QStringList contentMimeTypes() const
Returns a list of possible content mimetypes, e.g.
static Collection root()
Returns the root collection.
CachePolicy cachePolicy() const
Returns the cache policy of the collection.
QList< Collection > List
Describes a list of collections.
QString name() const
Returns the i18n'ed name of the collection.
QString remoteId() const
Returns the remote id of the entity.
QString remoteRevision() const
Returns the remote revision of the entity.
Collection parentCollection() const
Returns the parent collection of this object.
Attribute::List attributes() const
Returns a list of all attributes of the entity.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
Id id() const
Returns the unique identifier of the entity.
qint64 Id
Describes the unique id type.
Attribute * attribute(const QByteArray &name) const
Returns the attribute of the given type name if available, 0 otherwise.
Base class for all actions in the Akonadi storage.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
FreeBusyManager::Singleton.