• Skip to content
  • Skip to link menu
KDE 4.8 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

akonadi

collectionsync.cpp
00001 /*
00002     Copyright (c) 2007, 2009 Volker Krause <vkrause@kde.org>
00003 
00004     This library is free software; you can redistribute it and/or modify it
00005     under the terms of the GNU Library General Public License as published by
00006     the Free Software Foundation; either version 2 of the License, or (at your
00007     option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful, but WITHOUT
00010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00012     License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to the
00016     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017     02110-1301, USA.
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; // never try to delete that one
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() ) // no remote id here means it hasn't been added to the resource yet, so we exclude it from the sync
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       // add already existing children
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       // set our parent and add ourselves as child
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; // handled by the base class
00168 
00169       // safety check: the local tree has to be connected
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         // step 1: see if we have a matching local node already
00253         LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
00254         if ( localNode ) {
00255           Q_ASSERT( !localNode->processed );
00256           updateLocalCollection( localNode, remoteNode );
00257           continue;
00258         }
00259         // step 2: check if we have the parent at least, then we can create it
00260         localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00261         if ( localNode ) {
00262           pendingCreations[localNode].append( remoteNode );
00263           continue;
00264         }
00265         // step 3: find the best matching ancestor and enqueue it for later processing
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       // process the now possible collection creations
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         // ### HACK to work around the implicit move attempts of CollectionModifyJob
00293         // which we do explicitly below
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       // detecting moves is only possible with global RIDs
00302       if ( !hierarchicalRIDs ) {
00303         LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
00304         LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00305         // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
00306         // local parent has been created
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; // handled by the base class
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; // handled by the base class
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         // It can happen that the groupware servers report us deleted collections
00427         // twice, in this case this collection delete job will fail on the second try.
00428         // To avoid a rollback of the complete transaction we gracefully allow the job
00429         // to fail :)
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       // still running jobs or not fully delivered local/remote state
00493       if ( !deliveryDone || pendingJobs > 0 || !localListDone )
00494         return;
00495 
00496       // safety check: there must be no pending remote nodes anymore
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     // temporary during build-up of the local node tree, must be empty afterwards
00523     QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
00524 
00525     // removed remote collections in incremental mode
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(Akonadi::Collection::List)),
00580            SLOT(localCollectionsReceived(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"

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal