• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • 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       LocalNode *node = new LocalNode( col );
00115       Q_ASSERT( !localUidMap.contains( col.id() ) );
00116       localUidMap.insert( node->collection.id(), node );
00117       if ( !hierarchicalRIDs && !col.remoteId().isEmpty() )
00118         localRidMap.insert( node->collection.remoteId(), node );
00119 
00120       // add already existing children
00121       if ( localPendingCollections.contains( col.id() ) ) {
00122         QVector<Collection::Id> childIds = localPendingCollections.take( col.id() );
00123         foreach ( Collection::Id childId, childIds ) {
00124           Q_ASSERT( localUidMap.contains( childId ) );
00125           LocalNode *childNode = localUidMap.value( childId );
00126           node->childNodes.append( childNode );
00127           if ( !childNode->collection.remoteId().isEmpty() )
00128             node->childRidMap.insert( childNode->collection.remoteId(), childNode );
00129         }
00130       }
00131 
00132       // set our parent and add ourselves as child
00133       if ( localUidMap.contains( col.parentCollection().id() ) ) {
00134         LocalNode* parentNode = localUidMap.value( col.parentCollection().id() );
00135         parentNode->childNodes.append( node );
00136         if ( !node->collection.remoteId().isEmpty() )
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 
00186     LocalNode* findLocalChildNodeByName( LocalNode *localParentNode, const QString &name )
00187     {
00188       if ( name.isEmpty() ) // shouldn't happen...
00189         return 0;
00190 
00191       if ( localParentNode == localRoot ) // possibly non-unique names on top-level
00192         return 0;
00193 
00194       foreach ( LocalNode *childNode, localParentNode->childNodes ) {
00195         // the restriction on empty RIDs can possibly removed, but for now I only understand the implication for this case
00196         if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() )
00197           return childNode;
00198       }
00199       return 0;
00200     }
00201 
00206     LocalNode* findMatchingLocalNode( const Collection &collection )
00207     {
00208       if ( !hierarchicalRIDs ) {
00209         if ( localRidMap.contains( collection.remoteId() ) )
00210           return localRidMap.value( collection.remoteId() );
00211         return 0;
00212       } else {
00213         if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() )
00214           return localRoot;
00215         LocalNode *localParent = 0;
00216         if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
00217           kWarning() << "Remote collection without valid parent found: " << collection;
00218           return 0;
00219         }
00220         if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() )
00221           localParent = localRoot;
00222         else
00223           localParent = findMatchingLocalNode( collection.parentCollection() );
00224 
00225         if ( localParent ) {
00226           if ( localParent->childRidMap.contains( collection.remoteId() ) )
00227             return localParent->childRidMap.value( collection.remoteId() );
00228           // check if we have a local folder with a matching name and no RID, if so let's use that one
00229           // we would get an error if we don't do this anyway, as we'd try to create two sibling nodes with the same name
00230           if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.name() ) ) {
00231             kDebug() << "Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
00232             return recoveredLocalNode;
00233           }
00234         }
00235         return 0;
00236       }
00237     }
00238 
00244     LocalNode* findBestLocalAncestor( const Collection &collection, bool *exactMatch = 0 )
00245     {
00246       if ( !hierarchicalRIDs )
00247         return localRoot;
00248       if ( collection == Collection::root() ) {
00249         if ( exactMatch ) *exactMatch = true;
00250         return localRoot;
00251       }
00252       if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
00253         kWarning() << "Remote collection without valid parent found: " << collection;
00254         return 0;
00255       }
00256       bool parentIsExact = false;
00257       LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact );
00258       if ( !parentIsExact ) {
00259         if ( exactMatch ) *exactMatch = false;
00260         return localParent;
00261       }
00262       if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
00263         if ( exactMatch ) *exactMatch = true;
00264         return localParent->childRidMap.value( collection.remoteId() );
00265       }
00266       if ( exactMatch ) *exactMatch = false;
00267       return localParent;
00268     }
00269 
00275     void processPendingRemoteNodes( LocalNode *_localRoot )
00276     {
00277       QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
00278       _localRoot->pendingRemoteNodes.clear();
00279       QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
00280       foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
00281         // step 1: see if we have a matching local node already
00282         LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
00283         if ( localNode ) {
00284           Q_ASSERT( !localNode->processed );
00285           updateLocalCollection( localNode, remoteNode );
00286           continue;
00287         }
00288         // step 2: check if we have the parent at least, then we can create it
00289         localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00290         if ( localNode ) {
00291           pendingCreations[localNode].append( remoteNode );
00292           continue;
00293         }
00294         // step 3: find the best matching ancestor and enqueue it for later processing
00295         localNode = findBestLocalAncestor( remoteNode->collection );
00296         if ( !localNode ) {
00297           q->setError( Unknown );
00298           q->setErrorText( i18n( "Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
00299           q->emitResult();
00300           return;
00301         }
00302         localNode->pendingRemoteNodes.append( remoteNode );
00303       }
00304 
00305       // process the now possible collection creations
00306       for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
00307             it != pendingCreations.constEnd(); ++it )
00308       {
00309         createLocalCollections( it.key(), it.value() );
00310       }
00311     }
00312 
00316     void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
00317     {
00318       Collection upd( remoteNode->collection );
00319       Q_ASSERT( !upd.remoteId().isEmpty() );
00320       upd.setId( localNode->collection.id() );
00321       {
00322         // ### HACK to work around the implicit move attempts of CollectionModifyJob
00323         // which we do explicitly below
00324         Collection c( upd );
00325         c.setParentCollection( localNode->collection.parentCollection() );
00326         ++pendingJobs;
00327         CollectionModifyJob *mod = new CollectionModifyJob( c, q );
00328         connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
00329       }
00330 
00331       // detecting moves is only possible with global RIDs
00332       if ( !hierarchicalRIDs ) {
00333         LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
00334         LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
00335         // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
00336         // local parent has been created
00337         if ( newParent && oldParent != newParent ) {
00338           ++pendingJobs;
00339           CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
00340           connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
00341         }
00342       }
00343 
00344       localNode->processed = true;
00345       delete remoteNode;
00346     }
00347 
00348     void updateLocalCollectionResult( KJob* job )
00349     {
00350       --pendingJobs;
00351       if ( job->error() )
00352         return; // handled by the base class
00353       if ( qobject_cast<CollectionModifyJob*>( job ) )
00354         ++progress;
00355       checkDone();
00356     }
00357 
00362     void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
00363     {
00364       foreach ( RemoteNode *remoteNode, remoteNodes ) {
00365         ++pendingJobs;
00366         Collection col( remoteNode->collection );
00367         Q_ASSERT( !col.remoteId().isEmpty() );
00368         col.setParentCollection( localParent->collection );
00369         CollectionCreateJob *create = new CollectionCreateJob( col, q );
00370         create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
00371         create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
00372         connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
00373       }
00374     }
00375 
00376     void createLocalCollectionResult( KJob* job )
00377     {
00378       --pendingJobs;
00379       if ( job->error() )
00380         return; // handled by the base class
00381 
00382       const Collection newLocal = static_cast<CollectionCreateJob*>( job )->collection();
00383       LocalNode* localNode = createLocalNode( newLocal );
00384       localNode->processed = true;
00385 
00386       LocalNode* localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
00387       Q_ASSERT( localParent->childNodes.contains( localNode ) );
00388       RemoteNode* remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
00389       delete remoteNode;
00390       ++progress;
00391 
00392       processPendingRemoteNodes( localParent );
00393       if ( !hierarchicalRIDs )
00394         processPendingRemoteNodes( localRoot );
00395 
00396       checkDone();
00397     }
00398 
00402     bool hasProcessedChildren( LocalNode *localNode ) const
00403     {
00404       if ( localNode->processed )
00405         return true;
00406       foreach ( LocalNode *child, localNode->childNodes ) {
00407         if ( hasProcessedChildren( child ) )
00408           return true;
00409       }
00410       return false;
00411     }
00412 
00417     Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
00418     {
00419       Collection::List rv;
00420       if ( !localNode->processed ) {
00421         if ( hasProcessedChildren( localNode ) ) {
00422           kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
00423           kWarning() << localNode->collection;
00424           return rv;
00425         }
00426         if ( localNode->collection.remoteId().isEmpty() ) {
00427           kWarning() << "Found unprocessed local node without remoteId, excluding from deletion";
00428           kWarning() << localNode->collection;
00429           return rv;
00430         }
00431         rv.append( localNode->collection );
00432         return rv;
00433       }
00434 
00435       foreach ( LocalNode *child, localNode->childNodes )
00436         rv.append( findUnprocessedLocalCollections( child ) );
00437       return rv;
00438     }
00439 
00443     void deleteUnprocessedLocalNodes()
00444     {
00445       if ( incremental )
00446         return;
00447       const Collection::List cols = findUnprocessedLocalCollections( localRoot );
00448       deleteLocalCollections( cols );
00449     }
00450 
00455     void deleteLocalCollections( const Collection::List &cols )
00456     {
00457       q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
00458       foreach ( const Collection &col, cols ) {
00459         Q_ASSERT( !col.remoteId().isEmpty() ); // empty RID -> stuff we haven't even written to the remote side yet
00460 
00461         ++pendingJobs;
00462         CollectionDeleteJob *job = new CollectionDeleteJob( col, q );
00463         connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
00464 
00465         // It can happen that the groupware servers report us deleted collections
00466         // twice, in this case this collection delete job will fail on the second try.
00467         // To avoid a rollback of the complete transaction we gracefully allow the job
00468         // to fail :)
00469         q->setIgnoreJobFailure( job );
00470       }
00471     }
00472 
00473     void deleteLocalCollectionsResult( KJob* )
00474     {
00475       --pendingJobs;
00476 
00477       ++progress;
00478       checkDone();
00479     }
00480 
00484     void execute()
00485     {
00486       kDebug() << Q_FUNC_INFO << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
00487       if ( !localListDone )
00488         return;
00489 
00490       processPendingRemoteNodes( localRoot );
00491 
00492       if ( !incremental && deliveryDone )
00493         deleteUnprocessedLocalNodes();
00494 
00495       if ( !hierarchicalRIDs ) {
00496         deleteLocalCollections( removedRemoteCollections );
00497       } else {
00498         Collection::List localCols;
00499         foreach ( const Collection &c, removedRemoteCollections ) {
00500           LocalNode *node = findMatchingLocalNode( c );
00501           if ( node )
00502             localCols.append( node->collection );
00503         }
00504         deleteLocalCollections( localCols );
00505       }
00506       removedRemoteCollections.clear();
00507 
00508       checkDone();
00509     }
00510 
00514     QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
00515     {
00516       QList<RemoteNode*> rv;
00517       rv.append( localNode->pendingRemoteNodes );
00518       foreach ( LocalNode *child, localNode->childNodes )
00519         rv.append( findPendingRemoteNodes( child ) );
00520       return rv;
00521     }
00522 
00527     void checkDone()
00528     {
00529       q->setProcessedAmount( KJob::Bytes, progress );
00530 
00531       // still running jobs or not fully delivered local/remote state
00532       if ( !deliveryDone || pendingJobs > 0 || !localListDone )
00533         return;
00534 
00535       // safety check: there must be no pending remote nodes anymore
00536       QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
00537       if ( !orphans.isEmpty() ) {
00538         q->setError( Unknown );
00539         q->setErrorText( i18n( "Found unresolved orphan collections" ) );
00540         foreach ( RemoteNode* orphan, orphans )
00541           kDebug() << "found orphan collection:" << orphan->collection;
00542         q->emitResult();
00543         return;
00544       }
00545 
00546       kDebug() << Q_FUNC_INFO << "q->commit()";
00547       q->commit();
00548     }
00549 
00550     CollectionSync *q;
00551 
00552     QString resourceId;
00553 
00554     int pendingJobs;
00555     int progress;
00556 
00557     LocalNode* localRoot;
00558     QHash<Collection::Id, LocalNode*> localUidMap;
00559     QHash<QString, LocalNode*> localRidMap;
00560 
00561     // temporary during build-up of the local node tree, must be empty afterwards
00562     QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
00563 
00564     // removed remote collections in incremental mode
00565     Collection::List removedRemoteCollections;
00566 
00567     bool incremental;
00568     bool streaming;
00569     bool hierarchicalRIDs;
00570 
00571     bool localListDone;
00572     bool deliveryDone;
00573 };
00574 
00575 CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) :
00576     TransactionSequence( parent ),
00577     d( new Private( this ) )
00578 {
00579   d->resourceId = resourceId;
00580   setTotalAmount( KJob::Bytes, 0 );
00581 }
00582 
00583 CollectionSync::~CollectionSync()
00584 {
00585   delete d;
00586 }
00587 
00588 void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections)
00589 {
00590   setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
00591   foreach ( const Collection &c, remoteCollections )
00592     d->createRemoteNode( c );
00593 
00594   if ( !d->streaming )
00595     d->deliveryDone = true;
00596   d->execute();
00597 }
00598 
00599 void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections)
00600 {
00601   setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
00602   d->incremental = true;
00603   foreach ( const Collection &c, changedCollections )
00604     d->createRemoteNode( c );
00605   d->removedRemoteCollections += removedCollections;
00606 
00607   if ( !d->streaming )
00608     d->deliveryDone = true;
00609   d->execute();
00610 }
00611 
00612 void CollectionSync::doStart()
00613 {
00614   CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this );
00615   job->fetchScope().setResource( d->resourceId );
00616   job->fetchScope().setIncludeUnsubscribed( true );
00617   job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
00618   connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
00619            SLOT(localCollectionsReceived(Akonadi::Collection::List)) );
00620   connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
00621 }
00622 
00623 void CollectionSync::setStreamingEnabled( bool streaming )
00624 {
00625   d->streaming = streaming;
00626 }
00627 
00628 void CollectionSync::retrievalDone()
00629 {
00630   d->deliveryDone = true;
00631   d->execute();
00632 }
00633 
00634 void CollectionSync::setHierarchicalRemoteIds( bool hierarchical )
00635 {
00636   d->hierarchicalRIDs = hierarchical;
00637 }
00638 
00639 #include "collectionsync_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:18:31 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal