• Skip to content
  • Skip to link menu
KDE 4.4 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • 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         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       // 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       ++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       // detecting moves is only possible with global RIDs
00296       if ( !hierarchicalRIDs ) {
00297         LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
00298         LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00299         // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
00300         // local parent has been created
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; // handled by the base class
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; // handled by the base class
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; // handled by the base class
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       // still running jobs or not fully delivered local/remote state
00481       if ( !deliveryDone || pendingJobs > 0 || !localListDone )
00482         return;
00483 
00484       // safety check: there must be no pending remote nodes anymore
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     // temporary during build-up of the local node tree, must be empty afterwards
00510     QHash<Collection::Id, QList<Collection::Id> > localPendingCollections;
00511 
00512     // removed remote collections in incremental mode
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"

akonadi

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.6.2-20100208
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