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"
32 #include <QtCore/QVariant>
34 using namespace Akonadi;
43 LocalNode(
const Collection &col ) :
50 qDeleteAll( childNodes );
51 qDeleteAll( pendingRemoteNodes );
54 Collection collection;
55 QList<LocalNode*> childNodes;
56 QHash<QString, LocalNode*> childRidMap;
60 QList<RemoteNode*> pendingRemoteNodes;
64 Q_DECLARE_METATYPE( LocalNode* )
65 static const
char LOCAL_NODE[] = "LocalNode";
73 RemoteNode(
const Collection &col ) :
77 Collection collection;
80 Q_DECLARE_METATYPE( RemoteNode* )
81 static const
char REMOTE_NODE[] = "RemoteNode";
86 class CollectionSync::Private
89 Private( CollectionSync *parent ) :
95 hierarchicalRIDs( false ),
96 localListDone( false ),
99 localRoot =
new LocalNode( Collection::root() );
100 localRoot->processed =
true;
101 localUidMap.insert( localRoot->collection.id(), localRoot );
102 if ( !hierarchicalRIDs )
103 localRidMap.insert( QString(), localRoot );
112 LocalNode* createLocalNode(
const Collection &col )
114 LocalNode *node =
new LocalNode( col );
115 Q_ASSERT( !localUidMap.contains( col.id() ) );
116 localUidMap.insert( node->collection.id(), node );
117 if ( !hierarchicalRIDs && !col.remoteId().isEmpty() )
118 localRidMap.insert( node->collection.remoteId(), node );
121 if ( localPendingCollections.contains( col.id() ) ) {
122 QVector<Collection::Id> childIds = localPendingCollections.take( col.id() );
123 foreach ( Collection::Id childId, childIds ) {
124 Q_ASSERT( localUidMap.contains( childId ) );
125 LocalNode *childNode = localUidMap.value( childId );
126 node->childNodes.append( childNode );
127 if ( !childNode->collection.remoteId().isEmpty() )
128 node->childRidMap.insert( childNode->collection.remoteId(), childNode );
133 if ( localUidMap.contains( col.parentCollection().id() ) ) {
134 LocalNode* parentNode = localUidMap.value( col.parentCollection().id() );
135 parentNode->childNodes.append( node );
136 if ( !node->collection.remoteId().isEmpty() )
137 parentNode->childRidMap.insert( node->collection.remoteId(), node );
139 localPendingCollections[ col.parentCollection().id() ].append( col.id() );
146 void createRemoteNode(
const Collection &col )
148 if ( col.remoteId().isEmpty() ) {
149 kWarning() <<
"Collection '" << col.name() <<
"' does not have a remote identifier - skipping";
152 RemoteNode *node =
new RemoteNode( col );
153 localRoot->pendingRemoteNodes.append( node );
157 void localCollectionsReceived(
const Akonadi::Collection::List &localCols )
159 foreach (
const Collection &c, localCols )
160 createLocalNode( c );
164 void localCollectionFetchResult( KJob *job )
170 if ( !localPendingCollections.isEmpty() ) {
171 q->setError( Unknown );
172 q->setErrorText( i18n(
"Inconsistent local collection tree detected." ) );
177 localListDone =
true;
186 LocalNode* findLocalChildNodeByName( LocalNode *localParentNode,
const QString &name )
188 if ( name.isEmpty() )
191 if ( localParentNode == localRoot )
194 foreach ( LocalNode *childNode, localParentNode->childNodes ) {
196 if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() )
206 LocalNode* findMatchingLocalNode(
const Collection &collection )
208 if ( !hierarchicalRIDs ) {
209 if ( localRidMap.contains( collection.remoteId() ) )
210 return localRidMap.value( collection.remoteId() );
213 if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() )
215 LocalNode *localParent = 0;
216 if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
217 kWarning() <<
"Remote collection without valid parent found: " << collection;
220 if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() )
221 localParent = localRoot;
223 localParent = findMatchingLocalNode( collection.parentCollection() );
226 if ( localParent->childRidMap.contains( collection.remoteId() ) )
227 return localParent->childRidMap.value( collection.remoteId() );
230 if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.name() ) ) {
231 kDebug() <<
"Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
232 return recoveredLocalNode;
244 LocalNode* findBestLocalAncestor(
const Collection &collection,
bool *exactMatch = 0 )
246 if ( !hierarchicalRIDs )
248 if ( collection == Collection::root() ) {
249 if ( exactMatch ) *exactMatch =
true;
252 if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
253 kWarning() <<
"Remote collection without valid parent found: " << collection;
256 bool parentIsExact =
false;
257 LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact );
258 if ( !parentIsExact ) {
259 if ( exactMatch ) *exactMatch =
false;
262 if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
263 if ( exactMatch ) *exactMatch =
true;
264 return localParent->childRidMap.value( collection.remoteId() );
266 if ( exactMatch ) *exactMatch =
false;
275 void processPendingRemoteNodes( LocalNode *_localRoot )
277 QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
278 _localRoot->pendingRemoteNodes.clear();
279 QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
280 foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
282 LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
284 Q_ASSERT( !localNode->processed );
285 updateLocalCollection( localNode, remoteNode );
289 localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
291 pendingCreations[localNode].append( remoteNode );
295 localNode = findBestLocalAncestor( remoteNode->collection );
297 q->setError( Unknown );
298 q->setErrorText( i18n(
"Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
302 localNode->pendingRemoteNodes.append( remoteNode );
306 for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
307 it != pendingCreations.constEnd(); ++it )
309 createLocalCollections( it.key(), it.value() );
316 void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
318 Collection upd( remoteNode->collection );
319 Q_ASSERT( !upd.remoteId().isEmpty() );
320 upd.setId( localNode->collection.id() );
325 c.setParentCollection( localNode->collection.parentCollection() );
327 CollectionModifyJob *mod =
new CollectionModifyJob( c, q );
328 connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
332 if ( !hierarchicalRIDs ) {
333 LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
334 LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
337 if ( newParent && oldParent != newParent ) {
339 CollectionMoveJob *move =
new CollectionMoveJob( upd, newParent->collection, q );
340 connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
344 localNode->processed =
true;
348 void updateLocalCollectionResult( KJob* job )
353 if ( qobject_cast<CollectionModifyJob*>( job ) )
362 void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
364 foreach ( RemoteNode *remoteNode, remoteNodes ) {
366 Collection col( remoteNode->collection );
367 Q_ASSERT( !col.remoteId().isEmpty() );
368 col.setParentCollection( localParent->collection );
369 CollectionCreateJob *create =
new CollectionCreateJob( col, q );
370 create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
371 create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
372 connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
376 void createLocalCollectionResult( KJob* job )
382 const Collection newLocal =
static_cast<CollectionCreateJob*
>( job )->collection();
383 LocalNode* localNode = createLocalNode( newLocal );
384 localNode->processed =
true;
386 LocalNode* localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
387 Q_ASSERT( localParent->childNodes.contains( localNode ) );
388 RemoteNode* remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
392 processPendingRemoteNodes( localParent );
393 if ( !hierarchicalRIDs )
394 processPendingRemoteNodes( localRoot );
402 bool hasProcessedChildren( LocalNode *localNode )
const
404 if ( localNode->processed )
406 foreach ( LocalNode *child, localNode->childNodes ) {
407 if ( hasProcessedChildren( child ) )
417 Collection::List findUnprocessedLocalCollections( LocalNode *localNode )
const
420 if ( !localNode->processed ) {
421 if ( hasProcessedChildren( localNode ) ) {
422 kWarning() <<
"Found unprocessed local node with processed children, excluding from deletion";
423 kWarning() << localNode->collection;
426 if ( localNode->collection.remoteId().isEmpty() ) {
427 kWarning() <<
"Found unprocessed local node without remoteId, excluding from deletion";
428 kWarning() << localNode->collection;
431 rv.append( localNode->collection );
435 foreach ( LocalNode *child, localNode->childNodes )
436 rv.append( findUnprocessedLocalCollections( child ) );
443 void deleteUnprocessedLocalNodes()
447 const Collection::List cols = findUnprocessedLocalCollections( localRoot );
448 deleteLocalCollections( cols );
455 void deleteLocalCollections(
const Collection::List &cols )
457 q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
458 foreach (
const Collection &col, cols ) {
459 Q_ASSERT( !col.remoteId().isEmpty() );
462 CollectionDeleteJob *job =
new CollectionDeleteJob( col, q );
463 connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
469 q->setIgnoreJobFailure( job );
473 void deleteLocalCollectionsResult( KJob* )
486 kDebug() << Q_FUNC_INFO <<
"localListDone: " << localListDone <<
" deliveryDone: " << deliveryDone;
487 if ( !localListDone )
490 processPendingRemoteNodes( localRoot );
492 if ( !incremental && deliveryDone )
493 deleteUnprocessedLocalNodes();
495 if ( !hierarchicalRIDs ) {
496 deleteLocalCollections( removedRemoteCollections );
498 Collection::List localCols;
499 foreach (
const Collection &c, removedRemoteCollections ) {
500 LocalNode *node = findMatchingLocalNode( c );
502 localCols.append( node->collection );
504 deleteLocalCollections( localCols );
506 removedRemoteCollections.clear();
514 QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
516 QList<RemoteNode*> rv;
517 rv.append( localNode->pendingRemoteNodes );
518 foreach ( LocalNode *child, localNode->childNodes )
519 rv.append( findPendingRemoteNodes( child ) );
529 q->setProcessedAmount( KJob::Bytes, progress );
532 if ( !deliveryDone || pendingJobs > 0 || !localListDone )
536 QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
537 if ( !orphans.isEmpty() ) {
538 q->setError( Unknown );
539 q->setErrorText( i18n(
"Found unresolved orphan collections" ) );
540 foreach ( RemoteNode* orphan, orphans )
541 kDebug() <<
"found orphan collection:" << orphan->collection;
546 kDebug() << Q_FUNC_INFO <<
"q->commit()";
557 LocalNode* localRoot;
558 QHash<Collection::Id, LocalNode*> localUidMap;
559 QHash<QString, LocalNode*> localRidMap;
562 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
565 Collection::List removedRemoteCollections;
569 bool hierarchicalRIDs;
575 CollectionSync::CollectionSync(
const QString &resourceId, QObject *parent ) :
576 TransactionSequence( parent ),
577 d( new Private( this ) )
579 d->resourceId = resourceId;
580 setTotalAmount( KJob::Bytes, 0 );
583 CollectionSync::~CollectionSync()
588 void CollectionSync::setRemoteCollections(
const Collection::List & remoteCollections)
590 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
591 foreach (
const Collection &c, remoteCollections )
592 d->createRemoteNode( c );
595 d->deliveryDone =
true;
599 void CollectionSync::setRemoteCollections(
const Collection::List & changedCollections,
const Collection::List & removedCollections)
601 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
602 d->incremental =
true;
603 foreach (
const Collection &c, changedCollections )
604 d->createRemoteNode( c );
605 d->removedRemoteCollections += removedCollections;
608 d->deliveryDone =
true;
612 void CollectionSync::doStart()
614 CollectionFetchJob *job =
new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive,
this );
615 job->fetchScope().setResource( d->resourceId );
616 job->fetchScope().setIncludeUnsubscribed(
true );
617 job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
618 connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
619 SLOT(localCollectionsReceived(Akonadi::Collection::List)) );
620 connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
623 void CollectionSync::setStreamingEnabled(
bool streaming )
625 d->streaming = streaming;
628 void CollectionSync::retrievalDone()
630 d->deliveryDone =
true;
634 void CollectionSync::setHierarchicalRemoteIds(
bool hierarchical )
636 d->hierarchicalRIDs = hierarchical;
639 #include "collectionsync_p.moc"