00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "collectionsync_p.h"
00021 #include "collection.h"
00022
00023 #include "collectioncreatejob.h"
00024 #include "collectiondeletejob.h"
00025 #include "collectionfetchjob.h"
00026 #include "collectionmodifyjob.h"
00027 #include "collectionfetchscope.h"
00028 #include "collectionmovejob.h"
00029
00030 #include <kdebug.h>
00031 #include <KLocale>
00032 #include <QtCore/QVariant>
00033
00034 using namespace Akonadi;
00035
00036 struct RemoteNode;
00037
00041 struct LocalNode
00042 {
00043 LocalNode( const Collection &col ) :
00044 collection( col ),
00045 processed( false )
00046 {}
00047
00048 ~LocalNode()
00049 {
00050 qDeleteAll( childNodes );
00051 qDeleteAll( pendingRemoteNodes );
00052 }
00053
00054 Collection collection;
00055 QList<LocalNode*> childNodes;
00056 QHash<QString, LocalNode*> childRidMap;
00060 QList<RemoteNode*> pendingRemoteNodes;
00061 bool processed;
00062 };
00063
00064 Q_DECLARE_METATYPE( LocalNode* )
00065 static const char LOCAL_NODE[] = "LocalNode";
00066
00071 struct RemoteNode
00072 {
00073 RemoteNode( const Collection &col ) :
00074 collection( col )
00075 {}
00076
00077 Collection collection;
00078 };
00079
00080 Q_DECLARE_METATYPE( RemoteNode* )
00081 static const char REMOTE_NODE[] = "RemoteNode";
00082
00086 class CollectionSync::Private
00087 {
00088 public:
00089 Private( CollectionSync *parent ) :
00090 q( parent ),
00091 pendingJobs( 0 ),
00092 progress( 0 ),
00093 incremental( false ),
00094 streaming( false ),
00095 hierarchicalRIDs( false ),
00096 localListDone( false ),
00097 deliveryDone( false )
00098 {
00099 localRoot = new LocalNode( Collection::root() );
00100 localRoot->processed = true;
00101 localUidMap.insert( localRoot->collection.id(), localRoot );
00102 if ( !hierarchicalRIDs )
00103 localRidMap.insert( QString(), localRoot );
00104 }
00105
00106 ~Private()
00107 {
00108 delete localRoot;
00109 }
00110
00112 LocalNode* createLocalNode( const Collection &col )
00113 {
00114 if ( col.remoteId().isEmpty() )
00115 return 0;
00116 LocalNode *node = new LocalNode( col );
00117 Q_ASSERT( !localUidMap.contains( col.id() ) );
00118 localUidMap.insert( node->collection.id(), node );
00119 if ( !hierarchicalRIDs )
00120 localRidMap.insert( node->collection.remoteId(), node );
00121
00122
00123 if ( localPendingCollections.contains( col.id() ) ) {
00124 QVector<Collection::Id> childIds = localPendingCollections.take( col.id() );
00125 foreach ( Collection::Id childId, childIds ) {
00126 Q_ASSERT( localUidMap.contains( childId ) );
00127 LocalNode *childNode = localUidMap.value( childId );
00128 node->childNodes.append( childNode );
00129 node->childRidMap.insert( childNode->collection.remoteId(), childNode );
00130 }
00131 }
00132
00133
00134 if ( localUidMap.contains( col.parentCollection().id() ) ) {
00135 LocalNode* parentNode = localUidMap.value( col.parentCollection().id() );
00136 parentNode->childNodes.append( node );
00137 parentNode->childRidMap.insert( node->collection.remoteId(), node );
00138 } else {
00139 localPendingCollections[ col.parentCollection().id() ].append( col.id() );
00140 }
00141
00142 return node;
00143 }
00144
00146 void createRemoteNode( const Collection &col )
00147 {
00148 if ( col.remoteId().isEmpty() ) {
00149 kWarning() << "Collection '" << col.name() << "' does not have a remote identifier - skipping";
00150 return;
00151 }
00152 RemoteNode *node = new RemoteNode( col );
00153 localRoot->pendingRemoteNodes.append( node );
00154 }
00155
00157 void localCollectionsReceived( const Akonadi::Collection::List &localCols )
00158 {
00159 foreach ( const Collection &c, localCols )
00160 createLocalNode( c );
00161 }
00162
00164 void localCollectionFetchResult( KJob *job )
00165 {
00166 if ( job->error() )
00167 return;
00168
00169
00170 if ( !localPendingCollections.isEmpty() ) {
00171 q->setError( Unknown );
00172 q->setErrorText( i18n( "Inconsistent local collection tree detected." ) );
00173 q->emitResult();
00174 return;
00175 }
00176
00177 localListDone = true;
00178 execute();
00179 }
00180
00185 LocalNode* findMatchingLocalNode( const Collection &collection )
00186 {
00187 if ( !hierarchicalRIDs ) {
00188 if ( localRidMap.contains( collection.remoteId() ) )
00189 return localRidMap.value( collection.remoteId() );
00190 return 0;
00191 } else {
00192 if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() )
00193 return localRoot;
00194 LocalNode *localParent = 0;
00195 if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
00196 kWarning() << "Remote collection without valid parent found: " << collection;
00197 return 0;
00198 }
00199 if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() )
00200 localParent = localRoot;
00201 else
00202 localParent = findMatchingLocalNode( collection.parentCollection() );
00203
00204 if ( localParent && localParent->childRidMap.contains( collection.remoteId() ) )
00205 return localParent->childRidMap.value( collection.remoteId() );
00206 return 0;
00207 }
00208 }
00209
00215 LocalNode* findBestLocalAncestor( const Collection &collection, bool *exactMatch = 0 )
00216 {
00217 if ( !hierarchicalRIDs )
00218 return localRoot;
00219 if ( collection == Collection::root() ) {
00220 if ( exactMatch ) *exactMatch = true;
00221 return localRoot;
00222 }
00223 if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
00224 kWarning() << "Remote collection without valid parent found: " << collection;
00225 return 0;
00226 }
00227 bool parentIsExact = false;
00228 LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact );
00229 if ( !parentIsExact ) {
00230 if ( exactMatch ) *exactMatch = false;
00231 return localParent;
00232 }
00233 if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
00234 if ( exactMatch ) *exactMatch = true;
00235 return localParent->childRidMap.value( collection.remoteId() );
00236 }
00237 if ( exactMatch ) *exactMatch = false;
00238 return localParent;
00239 }
00240
00246 void processPendingRemoteNodes( LocalNode *_localRoot )
00247 {
00248 QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
00249 _localRoot->pendingRemoteNodes.clear();
00250 QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
00251 foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
00252
00253 LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
00254 if ( localNode ) {
00255 Q_ASSERT( !localNode->processed );
00256 updateLocalCollection( localNode, remoteNode );
00257 continue;
00258 }
00259
00260 localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00261 if ( localNode ) {
00262 pendingCreations[localNode].append( remoteNode );
00263 continue;
00264 }
00265
00266 localNode = findBestLocalAncestor( remoteNode->collection );
00267 if ( !localNode ) {
00268 q->setError( Unknown );
00269 q->setErrorText( i18n( "Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
00270 q->emitResult();
00271 return;
00272 }
00273 localNode->pendingRemoteNodes.append( remoteNode );
00274 }
00275
00276
00277 for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
00278 it != pendingCreations.constEnd(); ++it )
00279 {
00280 createLocalCollections( it.key(), it.value() );
00281 }
00282 }
00283
00287 void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
00288 {
00289 Collection upd( remoteNode->collection );
00290 upd.setId( localNode->collection.id() );
00291 {
00292
00293
00294 Collection c( upd );
00295 c.setParentCollection( localNode->collection.parentCollection() );
00296 ++pendingJobs;
00297 CollectionModifyJob *mod = new CollectionModifyJob( c, q );
00298 connect( mod, SIGNAL( result( KJob* ) ), q, SLOT( updateLocalCollectionResult( KJob* ) ) );
00299 }
00300
00301
00302 if ( !hierarchicalRIDs ) {
00303 LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
00304 LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00305
00306
00307 if ( newParent && oldParent != newParent ) {
00308 ++pendingJobs;
00309 CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
00310 connect( move, SIGNAL( result( KJob* ) ), q, SLOT( updateLocalCollectionResult( KJob* ) ) );
00311 }
00312 }
00313
00314 localNode->processed = true;
00315 delete remoteNode;
00316 }
00317
00318 void updateLocalCollectionResult( KJob* job )
00319 {
00320 --pendingJobs;
00321 if ( job->error() )
00322 return;
00323 if ( qobject_cast<CollectionModifyJob*>( job ) )
00324 ++progress;
00325 checkDone();
00326 }
00327
00332 void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
00333 {
00334 foreach ( RemoteNode *remoteNode, remoteNodes ) {
00335 ++pendingJobs;
00336 Collection col( remoteNode->collection );
00337 col.setParentCollection( localParent->collection );
00338 CollectionCreateJob *create = new CollectionCreateJob( col, q );
00339 create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
00340 create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
00341 connect( create, SIGNAL( result( KJob* ) ), q, SLOT( createLocalCollectionResult( KJob* ) ) );
00342 }
00343 }
00344
00345 void createLocalCollectionResult( KJob* job )
00346 {
00347 --pendingJobs;
00348 if ( job->error() )
00349 return;
00350
00351 const Collection newLocal = static_cast<CollectionCreateJob*>( job )->collection();
00352 LocalNode* localNode = createLocalNode( newLocal );
00353 localNode->processed = true;
00354
00355 LocalNode* localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
00356 Q_ASSERT( localParent->childNodes.contains( localNode ) );
00357 RemoteNode* remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
00358 delete remoteNode;
00359 ++progress;
00360
00361 processPendingRemoteNodes( localParent );
00362 if ( !hierarchicalRIDs )
00363 processPendingRemoteNodes( localRoot );
00364
00365 checkDone();
00366 }
00367
00371 bool hasProcessedChildren( LocalNode *localNode ) const
00372 {
00373 if ( localNode->processed )
00374 return true;
00375 foreach ( LocalNode *child, localNode->childNodes ) {
00376 if ( hasProcessedChildren( child ) )
00377 return true;
00378 }
00379 return false;
00380 }
00381
00386 Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
00387 {
00388 Collection::List rv;
00389 if ( !localNode->processed && hasProcessedChildren( localNode ) ) {
00390 kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
00391 kWarning() << localNode->collection;
00392 return rv;
00393 }
00394 if ( !localNode->processed ) {
00395 rv.append( localNode->collection );
00396 return rv;
00397 }
00398 foreach ( LocalNode *child, localNode->childNodes )
00399 rv.append( findUnprocessedLocalCollections( child ) );
00400 return rv;
00401 }
00402
00406 void deleteUnprocessedLocalNodes()
00407 {
00408 if ( incremental )
00409 return;
00410 const Collection::List cols = findUnprocessedLocalCollections( localRoot );
00411 deleteLocalCollections( cols );
00412 }
00413
00418 void deleteLocalCollections( const Collection::List &cols )
00419 {
00420 q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
00421 foreach ( const Collection &col, cols ) {
00422 ++pendingJobs;
00423 CollectionDeleteJob *job = new CollectionDeleteJob( col, q );
00424 connect( job, SIGNAL( result( KJob* ) ), q, SLOT( deleteLocalCollectionsResult( KJob* ) ) );
00425
00426
00427
00428
00429
00430 q->setIgnoreJobFailure( job );
00431 }
00432 }
00433
00434 void deleteLocalCollectionsResult( KJob* )
00435 {
00436 --pendingJobs;
00437
00438 ++progress;
00439 checkDone();
00440 }
00441
00445 void execute()
00446 {
00447 kDebug() << Q_FUNC_INFO << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
00448 if ( !localListDone )
00449 return;
00450
00451 processPendingRemoteNodes( localRoot );
00452
00453 if ( !incremental && deliveryDone )
00454 deleteUnprocessedLocalNodes();
00455
00456 if ( !hierarchicalRIDs ) {
00457 deleteLocalCollections( removedRemoteCollections );
00458 } else {
00459 Collection::List localCols;
00460 foreach ( const Collection &c, removedRemoteCollections ) {
00461 LocalNode *node = findMatchingLocalNode( c );
00462 if ( node )
00463 localCols.append( node->collection );
00464 }
00465 deleteLocalCollections( localCols );
00466 }
00467 removedRemoteCollections.clear();
00468
00469 checkDone();
00470 }
00471
00475 QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
00476 {
00477 QList<RemoteNode*> rv;
00478 rv.append( localNode->pendingRemoteNodes );
00479 foreach ( LocalNode *child, localNode->childNodes )
00480 rv.append( findPendingRemoteNodes( child ) );
00481 return rv;
00482 }
00483
00488 void checkDone()
00489 {
00490 q->setProcessedAmount( KJob::Bytes, progress );
00491
00492
00493 if ( !deliveryDone || pendingJobs > 0 || !localListDone )
00494 return;
00495
00496
00497 QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
00498 if ( !orphans.isEmpty() ) {
00499 q->setError( Unknown );
00500 q->setErrorText( i18n( "Found unresolved orphan collections" ) );
00501 foreach ( RemoteNode* orphan, orphans )
00502 kDebug() << "found orphan collection:" << orphan->collection;
00503 q->emitResult();
00504 return;
00505 }
00506
00507 kDebug() << Q_FUNC_INFO << "q->commit()";
00508 q->commit();
00509 }
00510
00511 CollectionSync *q;
00512
00513 QString resourceId;
00514
00515 int pendingJobs;
00516 int progress;
00517
00518 LocalNode* localRoot;
00519 QHash<Collection::Id, LocalNode*> localUidMap;
00520 QHash<QString, LocalNode*> localRidMap;
00521
00522
00523 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
00524
00525
00526 Collection::List removedRemoteCollections;
00527
00528 bool incremental;
00529 bool streaming;
00530 bool hierarchicalRIDs;
00531
00532 bool localListDone;
00533 bool deliveryDone;
00534 };
00535
00536 CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) :
00537 TransactionSequence( parent ),
00538 d( new Private( this ) )
00539 {
00540 d->resourceId = resourceId;
00541 setTotalAmount( KJob::Bytes, 0 );
00542 }
00543
00544 CollectionSync::~CollectionSync()
00545 {
00546 delete d;
00547 }
00548
00549 void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections)
00550 {
00551 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
00552 foreach ( const Collection &c, remoteCollections )
00553 d->createRemoteNode( c );
00554
00555 if ( !d->streaming )
00556 d->deliveryDone = true;
00557 d->execute();
00558 }
00559
00560 void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections)
00561 {
00562 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
00563 d->incremental = true;
00564 foreach ( const Collection &c, changedCollections )
00565 d->createRemoteNode( c );
00566 d->removedRemoteCollections += removedCollections;
00567
00568 if ( !d->streaming )
00569 d->deliveryDone = true;
00570 d->execute();
00571 }
00572
00573 void CollectionSync::doStart()
00574 {
00575 CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this );
00576 job->fetchScope().setResource( d->resourceId );
00577 job->fetchScope().setIncludeUnsubscribed( true );
00578 job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
00579 connect( job, SIGNAL( collectionsReceived( const Akonadi::Collection::List& ) ),
00580 SLOT( localCollectionsReceived( const Akonadi::Collection::List& ) ) );
00581 connect( job, SIGNAL( result( KJob* ) ), SLOT( localCollectionFetchResult( KJob* ) ) );
00582 }
00583
00584 void CollectionSync::setStreamingEnabled( bool streaming )
00585 {
00586 d->streaming = streaming;
00587 }
00588
00589 void CollectionSync::retrievalDone()
00590 {
00591 d->deliveryDone = true;
00592 d->execute();
00593 }
00594
00595 void CollectionSync::setHierarchicalRemoteIds( bool hierarchical )
00596 {
00597 d->hierarchicalRIDs = hierarchical;
00598 }
00599
00600 #include "collectionsync_p.moc"