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 QList<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 ++pendingJobs;
00290 Collection upd( remoteNode->collection );
00291 upd.setId( localNode->collection.id() );
00292 CollectionModifyJob *mod = new CollectionModifyJob( upd, q );
00293 connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
00294
00295
00296 if ( !hierarchicalRIDs ) {
00297 LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
00298 LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00299
00300
00301 if ( newParent && oldParent != newParent ) {
00302 ++pendingJobs;
00303 CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
00304 connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
00305 }
00306 }
00307
00308 localNode->processed = true;
00309 delete remoteNode;
00310 }
00311
00312 void updateLocalCollectionResult( KJob* job )
00313 {
00314 --pendingJobs;
00315 if ( job->error() )
00316 return;
00317 if ( qobject_cast<CollectionModifyJob*>( job ) )
00318 ++progress;
00319 checkDone();
00320 }
00321
00326 void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
00327 {
00328 foreach ( RemoteNode *remoteNode, remoteNodes ) {
00329 ++pendingJobs;
00330 Collection col( remoteNode->collection );
00331 col.setParentCollection( localParent->collection );
00332 CollectionCreateJob *create = new CollectionCreateJob( col, q );
00333 create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
00334 create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
00335 connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
00336 }
00337 }
00338
00339 void createLocalCollectionResult( KJob* job )
00340 {
00341 --pendingJobs;
00342 if ( job->error() )
00343 return;
00344
00345 const Collection newLocal = static_cast<CollectionCreateJob*>( job )->collection();
00346 LocalNode* localNode = createLocalNode( newLocal );
00347 localNode->processed = true;
00348
00349 LocalNode* localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
00350 Q_ASSERT( localParent->childNodes.contains( localNode ) );
00351 RemoteNode* remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
00352 delete remoteNode;
00353 ++progress;
00354
00355 processPendingRemoteNodes( localParent );
00356 if ( !hierarchicalRIDs )
00357 processPendingRemoteNodes( localRoot );
00358
00359 checkDone();
00360 }
00361
00365 bool hasProcessedChildren( LocalNode *localNode ) const
00366 {
00367 if ( localNode->processed )
00368 return true;
00369 foreach ( LocalNode *child, localNode->childNodes ) {
00370 if ( hasProcessedChildren( child ) )
00371 return true;
00372 }
00373 return false;
00374 }
00375
00380 Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
00381 {
00382 Collection::List rv;
00383 if ( !localNode->processed && hasProcessedChildren( localNode ) ) {
00384 kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
00385 kWarning() << localNode->collection;
00386 return rv;
00387 }
00388 if ( !localNode->processed ) {
00389 rv.append( localNode->collection );
00390 return rv;
00391 }
00392 foreach ( LocalNode *child, localNode->childNodes )
00393 rv.append( findUnprocessedLocalCollections( child ) );
00394 return rv;
00395 }
00396
00400 void deleteUnprocessedLocalNodes()
00401 {
00402 if ( incremental )
00403 return;
00404 const Collection::List cols = findUnprocessedLocalCollections( localRoot );
00405 deleteLocalCollections( cols );
00406 }
00407
00412 void deleteLocalCollections( const Collection::List &cols )
00413 {
00414 q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
00415 foreach ( const Collection &col, cols ) {
00416 ++pendingJobs;
00417 CollectionDeleteJob *job = new CollectionDeleteJob( col, q );
00418 connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
00419 }
00420 }
00421
00422 void deleteLocalCollectionsResult( KJob *job )
00423 {
00424 --pendingJobs;
00425 if ( job->error() )
00426 return;
00427 ++progress;
00428 checkDone();
00429 }
00430
00434 void execute()
00435 {
00436 if ( !localListDone )
00437 return;
00438
00439 processPendingRemoteNodes( localRoot );
00440
00441 if ( !incremental && deliveryDone )
00442 deleteUnprocessedLocalNodes();
00443
00444 if ( !hierarchicalRIDs ) {
00445 deleteLocalCollections( removedRemoteCollections );
00446 } else {
00447 Collection::List localCols;
00448 foreach ( const Collection &c, removedRemoteCollections ) {
00449 LocalNode *node = findMatchingLocalNode( c );
00450 if ( node )
00451 localCols.append( node->collection );
00452 }
00453 deleteLocalCollections( localCols );
00454 }
00455 removedRemoteCollections.clear();
00456
00457 checkDone();
00458 }
00459
00463 QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
00464 {
00465 QList<RemoteNode*> rv;
00466 rv.append( localNode->pendingRemoteNodes );
00467 foreach ( LocalNode *child, localNode->childNodes )
00468 rv.append( findPendingRemoteNodes( child ) );
00469 return rv;
00470 }
00471
00476 void checkDone()
00477 {
00478 q->setProcessedAmount( KJob::Bytes, progress );
00479
00480
00481 if ( !deliveryDone || pendingJobs > 0 || !localListDone )
00482 return;
00483
00484
00485 QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
00486 if ( !orphans.isEmpty() ) {
00487 q->setError( Unknown );
00488 q->setErrorText( i18n( "Found unresolved orphan collections" ) );
00489 foreach ( RemoteNode* orphan, orphans )
00490 kDebug() << "found orphan collection:" << orphan->collection;
00491 q->emitResult();
00492 return;
00493 }
00494
00495 q->commit();
00496 }
00497
00498 CollectionSync *q;
00499
00500 QString resourceId;
00501
00502 int pendingJobs;
00503 int progress;
00504
00505 LocalNode* localRoot;
00506 QHash<Collection::Id, LocalNode*> localUidMap;
00507 QHash<QString, LocalNode*> localRidMap;
00508
00509
00510 QHash<Collection::Id, QList<Collection::Id> > localPendingCollections;
00511
00512
00513 Collection::List removedRemoteCollections;
00514
00515 bool incremental;
00516 bool streaming;
00517 bool hierarchicalRIDs;
00518
00519 bool localListDone;
00520 bool deliveryDone;
00521 };
00522
00523 CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) :
00524 TransactionSequence( parent ),
00525 d( new Private( this ) )
00526 {
00527 d->resourceId = resourceId;
00528 setTotalAmount( KJob::Bytes, 0 );
00529 }
00530
00531 CollectionSync::~CollectionSync()
00532 {
00533 delete d;
00534 }
00535
00536 void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections)
00537 {
00538 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
00539 foreach ( const Collection &c, remoteCollections )
00540 d->createRemoteNode( c );
00541
00542 if ( !d->streaming )
00543 d->deliveryDone = true;
00544 d->execute();
00545 }
00546
00547 void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections)
00548 {
00549 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
00550 d->incremental = true;
00551 foreach ( const Collection &c, changedCollections )
00552 d->createRemoteNode( c );
00553 d->removedRemoteCollections += removedCollections;
00554
00555 if ( !d->streaming )
00556 d->deliveryDone = true;
00557 d->execute();
00558 }
00559
00560 void CollectionSync::doStart()
00561 {
00562 CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this );
00563 job->fetchScope().setResource( d->resourceId );
00564 job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
00565 connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(localCollectionsReceived(Akonadi::Collection::List)) );
00566 connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
00567 }
00568
00569 void CollectionSync::setStreamingEnabled( bool streaming )
00570 {
00571 d->streaming = streaming;
00572 }
00573
00574 void CollectionSync::retrievalDone()
00575 {
00576 d->deliveryDone = true;
00577 d->execute();
00578 }
00579
00580 void CollectionSync::setHierarchicalRemoteIds( bool hierarchical )
00581 {
00582 d->hierarchicalRIDs = hierarchical;
00583 }
00584
00585 #include "collectionsync_p.moc"