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"
29 #include "entitydisplayattribute.h"
32 #include <KLocalizedString>
33 #include <QtCore/QVariant>
35 using namespace Akonadi;
51 qDeleteAll( childNodes );
52 qDeleteAll( pendingRemoteNodes );
56 QList<LocalNode*> childNodes;
57 QHash<QString, LocalNode*> childRidMap;
61 QList<RemoteNode*> pendingRemoteNodes;
65 Q_DECLARE_METATYPE( LocalNode* )
66 static const
char LOCAL_NODE[] = "LocalNode";
81 Q_DECLARE_METATYPE( RemoteNode* )
82 static const
char REMOTE_NODE[] = "RemoteNode";
96 hierarchicalRIDs( false ),
97 localListDone( false ),
101 localRoot->processed =
true;
102 localUidMap.insert( localRoot->collection.id(), localRoot );
103 if ( !hierarchicalRIDs ) {
104 localRidMap.insert( QString(), localRoot );
114 LocalNode* createLocalNode(
const Collection &col )
116 LocalNode *node =
new LocalNode( col );
117 Q_ASSERT( !localUidMap.contains( col.
id() ) );
118 localUidMap.insert( node->collection.id(), node );
119 if ( !hierarchicalRIDs && !col.
remoteId().isEmpty() ) {
120 localRidMap.insert( node->collection.remoteId(), node );
124 if ( localPendingCollections.contains( col.
id() ) ) {
125 QVector<Collection::Id> childIds = localPendingCollections.take( col.
id() );
127 Q_ASSERT( localUidMap.contains( childId ) );
128 LocalNode *childNode = localUidMap.value( childId );
129 node->childNodes.append( childNode );
130 if ( !childNode->collection.remoteId().isEmpty() ) {
131 node->childRidMap.insert( childNode->collection.remoteId(), childNode );
139 parentNode->childNodes.append( node );
140 if ( !node->collection.remoteId().isEmpty() ) {
141 parentNode->childRidMap.insert( node->collection.remoteId(), node );
151 void createRemoteNode(
const Collection &col )
154 kWarning() <<
"Collection '" << col.
name() <<
"' does not have a remote identifier - skipping";
157 RemoteNode *node =
new RemoteNode( col );
158 localRoot->pendingRemoteNodes.append( node );
165 createLocalNode( c );
169 void localCollectionFetchResult( KJob *job )
171 if ( job->error() ) {
176 if ( !localPendingCollections.isEmpty() ) {
177 q->setError( Unknown );
178 q->setErrorText( i18n(
"Inconsistent local collection tree detected." ) );
183 localListDone =
true;
192 LocalNode* findLocalChildNodeByName( LocalNode *localParentNode,
const QString &name )
194 if ( name.isEmpty() ) {
198 if ( localParentNode == localRoot ) {
202 foreach ( LocalNode *childNode, localParentNode->childNodes ) {
204 if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() ) {
215 LocalNode* findMatchingLocalNode(
const Collection &collection )
217 if ( !hierarchicalRIDs ) {
218 if ( localRidMap.contains( collection.
remoteId() ) ) {
219 return localRidMap.value( collection.
remoteId() );
226 LocalNode *localParent = 0;
228 kWarning() <<
"Remote collection without valid parent found: " << collection;
232 localParent = localRoot;
238 if ( localParent->childRidMap.contains( collection.
remoteId() ) ) {
239 return localParent->childRidMap.value( collection.
remoteId() );
243 if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.
name() ) ) {
244 kDebug() <<
"Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
245 return recoveredLocalNode;
257 LocalNode* findBestLocalAncestor(
const Collection &collection,
bool *exactMatch = 0 )
259 if ( !hierarchicalRIDs ) {
269 kWarning() <<
"Remote collection without valid parent found: " << collection;
272 bool parentIsExact =
false;
273 LocalNode *localParent = findBestLocalAncestor( collection.
parentCollection(), &parentIsExact );
274 if ( !parentIsExact ) {
280 if ( localParent->childRidMap.contains( collection.
remoteId() ) ) {
284 return localParent->childRidMap.value( collection.
remoteId() );
297 void processPendingRemoteNodes( LocalNode *_localRoot )
299 QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
300 _localRoot->pendingRemoteNodes.clear();
301 QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
302 foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
304 LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
306 Q_ASSERT( !localNode->processed );
307 updateLocalCollection( localNode, remoteNode );
311 localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
313 pendingCreations[localNode].append( remoteNode );
317 localNode = findBestLocalAncestor( remoteNode->collection );
319 q->setError( Unknown );
320 q->setErrorText( i18n(
"Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
324 localNode->pendingRemoteNodes.append( remoteNode );
328 for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
329 it != pendingCreations.constEnd(); ++it ) {
330 createLocalCollections( it.key(), it.value() );
337 void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
340 Q_ASSERT( !upd.remoteId().isEmpty() );
341 upd.setId( localNode->collection.id() );
351 c.setParentCollection( localNode->collection.parentCollection() );
354 connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
358 if ( !hierarchicalRIDs ) {
359 LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
360 LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
363 if ( newParent && oldParent != newParent ) {
366 connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
370 localNode->processed =
true;
374 void updateLocalCollectionResult( KJob* job )
377 if ( job->error() ) {
380 if ( qobject_cast<CollectionModifyJob*>( job ) ) {
390 void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
392 foreach ( RemoteNode *remoteNode, remoteNodes ) {
395 Q_ASSERT( !col.
remoteId().isEmpty() );
398 create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
399 create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
400 connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
404 void createLocalCollectionResult( KJob* job )
407 if ( job->error() ) {
412 LocalNode *localNode = createLocalNode( newLocal );
413 localNode->processed =
true;
415 LocalNode *localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
416 Q_ASSERT( localParent->childNodes.contains( localNode ) );
417 RemoteNode *remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
421 processPendingRemoteNodes( localParent );
422 if ( !hierarchicalRIDs ) {
423 processPendingRemoteNodes( localRoot );
432 bool hasProcessedChildren( LocalNode *localNode )
const
434 if ( localNode->processed ) {
437 foreach ( LocalNode *child, localNode->childNodes ) {
438 if ( hasProcessedChildren( child ) ) {
449 Collection::List findUnprocessedLocalCollections( LocalNode *localNode )
const
452 if ( !localNode->processed ) {
453 if ( hasProcessedChildren( localNode ) ) {
454 kWarning() <<
"Found unprocessed local node with processed children, excluding from deletion";
455 kWarning() << localNode->collection;
458 if ( localNode->collection.remoteId().isEmpty() ) {
459 kWarning() <<
"Found unprocessed local node without remoteId, excluding from deletion";
460 kWarning() << localNode->collection;
463 rv.append( localNode->collection );
467 foreach ( LocalNode *child, localNode->childNodes ) {
468 rv.append( findUnprocessedLocalCollections( child ) );
476 void deleteUnprocessedLocalNodes()
482 deleteLocalCollections( cols );
491 q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
493 Q_ASSERT( !col.
remoteId().isEmpty() );
497 connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
503 q->setIgnoreJobFailure( job );
507 void deleteLocalCollectionsResult( KJob* )
520 kDebug() << Q_FUNC_INFO <<
"localListDone: " << localListDone <<
" deliveryDone: " << deliveryDone;
521 if ( !localListDone ) {
525 processPendingRemoteNodes( localRoot );
527 if ( !incremental && deliveryDone ) {
528 deleteUnprocessedLocalNodes();
531 if ( !hierarchicalRIDs ) {
532 deleteLocalCollections( removedRemoteCollections );
535 foreach (
const Collection &c, removedRemoteCollections ) {
536 LocalNode *node = findMatchingLocalNode( c );
538 localCols.append( node->collection );
541 deleteLocalCollections( localCols );
543 removedRemoteCollections.clear();
551 QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
553 QList<RemoteNode*> rv;
554 rv.append( localNode->pendingRemoteNodes );
555 foreach ( LocalNode *child, localNode->childNodes ) {
556 rv.append( findPendingRemoteNodes( child ) );
567 q->setProcessedAmount( KJob::Bytes, progress );
570 if ( !deliveryDone || pendingJobs > 0 || !localListDone ) {
575 QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
576 if ( !orphans.isEmpty() ) {
577 q->setError( Unknown );
578 q->setErrorText( i18n(
"Found unresolved orphan collections" ) );
579 foreach ( RemoteNode* orphan, orphans ) {
580 kDebug() <<
"found orphan collection:" << orphan->collection;
586 kDebug() << Q_FUNC_INFO <<
"q->commit()";
597 LocalNode* localRoot;
598 QHash<Collection::Id, LocalNode*> localUidMap;
599 QHash<QString, LocalNode*> localRidMap;
602 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
609 bool hierarchicalRIDs;
617 d( new Private( this ) )
619 d->resourceId = resourceId;
620 setTotalAmount( KJob::Bytes, 0 );
630 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
631 foreach (
const Collection &c, remoteCollections ) {
632 d->createRemoteNode( c );
635 if ( !d->streaming ) {
636 d->deliveryDone =
true;
643 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
644 d->incremental =
true;
645 foreach (
const Collection &c, changedCollections ) {
646 d->createRemoteNode( c );
648 d->removedRemoteCollections += removedCollections;
650 if ( !d->streaming ) {
651 d->deliveryDone =
true;
664 connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
669 d->streaming = streaming;
674 d->deliveryDone =
true;
680 d->hierarchicalRIDs = hierarchical;
683 #include "moc_collectionsync_p.cpp"
Job that modifies a collection in the Akonadi storage.
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
Sets how many levels of ancestor collections should be included in the retrieval. ...
QString name() const
Returns the i18n'ed name of the collection.
void setIncludeUnsubscribed(bool include)
Sets whether unsubscribed collections should be included in the collection listing.
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.
Job that fetches collections from the Akonadi storage.
qint64 Id
Describes the unique id type.
~CollectionSync()
Destroys this job.
Only retrieve the immediate parent collection.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
void setHierarchicalRemoteIds(bool hierarchical)
Indicate whether the resource supplies collections with hierarchical or global remote identifiers...
void setRemoteCollections(const Collection::List &remoteCollections)
Sets the result of a full remote collection listing.
Collection parentCollection() const
Returns the parent collection of this object.
void doStart()
This method must be reimplemented in the concrete jobs.
QString remoteId() const
Returns the remote id of the entity.
EntityDisplayAttribute * clone() const
Creates a copy of this attribute.
void retrievalDone()
Indicate that all collections have been retrieved in streaming mode.
static Collection root()
Returns the root collection.
Job that deletes a collection in the Akonadi storage.
Id id() const
Returns the unique identifier of the entity.
CollectionSync(const QString &resourceId, QObject *parent=0)
Creates a new collection synchronzier.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
Job that creates a new collection in the Akonadi storage.
List all sub-collections.
void setStreamingEnabled(bool streaming)
Enables streaming, that is not all collections are delivered at once.
QList< Collection > List
Describes a list of collections.
Attribute that stores the properties that are used to display an entity.