• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
collectionsync.cpp
1 /*
2  Copyright (c) 2007, 2009 Volker Krause <vkrause@kde.org>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "collectionsync_p.h"
21 #include "collection.h"
22 
23 #include "collectioncreatejob.h"
24 #include "collectiondeletejob.h"
25 #include "collectionfetchjob.h"
26 #include "collectionmodifyjob.h"
27 #include "collectionfetchscope.h"
28 #include "collectionmovejob.h"
29 #include "entitydisplayattribute.h"
30 
31 #include <kdebug.h>
32 #include <KLocale>
33 #include <QtCore/QVariant>
34 
35 using namespace Akonadi;
36 
37 struct RemoteNode;
38 
42 struct LocalNode
43 {
44  LocalNode( const Collection &col ) :
45  collection( col ),
46  processed( false )
47  {}
48 
49  ~LocalNode()
50  {
51  qDeleteAll( childNodes );
52  qDeleteAll( pendingRemoteNodes );
53  }
54 
55  Collection collection;
56  QList<LocalNode*> childNodes;
57  QHash<QString, LocalNode*> childRidMap;
61  QList<RemoteNode*> pendingRemoteNodes;
62  bool processed;
63 };
64 
65 Q_DECLARE_METATYPE( LocalNode* )
66 static const char LOCAL_NODE[] = "LocalNode";
67 
72 struct RemoteNode
73 {
74  RemoteNode( const Collection &col ) :
75  collection( col )
76  {}
77 
78  Collection collection;
79 };
80 
81 Q_DECLARE_METATYPE( RemoteNode* )
82 static const char REMOTE_NODE[] = "RemoteNode";
83 
87 class CollectionSync::Private
88 {
89  public:
90  Private( CollectionSync *parent ) :
91  q( parent ),
92  pendingJobs( 0 ),
93  progress( 0 ),
94  incremental( false ),
95  streaming( false ),
96  hierarchicalRIDs( false ),
97  localListDone( false ),
98  deliveryDone( false )
99  {
100  localRoot = new LocalNode( Collection::root() );
101  localRoot->processed = true; // never try to delete that one
102  localUidMap.insert( localRoot->collection.id(), localRoot );
103  if ( !hierarchicalRIDs ) {
104  localRidMap.insert( QString(), localRoot );
105  }
106  }
107 
108  ~Private()
109  {
110  delete localRoot;
111  }
112 
114  LocalNode* createLocalNode( const Collection &col )
115  {
116  LocalNode *node = new LocalNode( col );
117  Q_ASSERT( !localUidMap.contains( col.id() ) );
118  localUidMap.insert( node->collection.id(), node );
119  if ( !hierarchicalRIDs && !col.remoteId().isEmpty() ) {
120  localRidMap.insert( node->collection.remoteId(), node );
121  }
122 
123  // add already existing children
124  if ( localPendingCollections.contains( col.id() ) ) {
125  QVector<Collection::Id> childIds = localPendingCollections.take( col.id() );
126  foreach ( Collection::Id childId, childIds ) {
127  Q_ASSERT( localUidMap.contains( childId ) );
128  LocalNode *childNode = localUidMap.value( childId );
129  node->childNodes.append( childNode );
130  if ( !childNode->collection.remoteId().isEmpty() ) {
131  node->childRidMap.insert( childNode->collection.remoteId(), childNode );
132  }
133  }
134  }
135 
136  // set our parent and add ourselves as child
137  if ( localUidMap.contains( col.parentCollection().id() ) ) {
138  LocalNode* parentNode = localUidMap.value( col.parentCollection().id() );
139  parentNode->childNodes.append( node );
140  if ( !node->collection.remoteId().isEmpty() ) {
141  parentNode->childRidMap.insert( node->collection.remoteId(), node );
142  }
143  } else {
144  localPendingCollections[ col.parentCollection().id() ].append( col.id() );
145  }
146 
147  return node;
148  }
149 
151  void createRemoteNode( const Collection &col )
152  {
153  if ( col.remoteId().isEmpty() ) {
154  kWarning() << "Collection '" << col.name() << "' does not have a remote identifier - skipping";
155  return;
156  }
157  RemoteNode *node = new RemoteNode( col );
158  localRoot->pendingRemoteNodes.append( node );
159  }
160 
162  void localCollectionsReceived( const Akonadi::Collection::List &localCols )
163  {
164  foreach ( const Collection &c, localCols )
165  createLocalNode( c );
166  }
167 
169  void localCollectionFetchResult( KJob *job )
170  {
171  if ( job->error() ) {
172  return; // handled by the base class
173  }
174 
175  // safety check: the local tree has to be connected
176  if ( !localPendingCollections.isEmpty() ) {
177  q->setError( Unknown );
178  q->setErrorText( i18n( "Inconsistent local collection tree detected." ) );
179  q->emitResult();
180  return;
181  }
182 
183  localListDone = true;
184  execute();
185  }
186 
192  LocalNode* findLocalChildNodeByName( LocalNode *localParentNode, const QString &name )
193  {
194  if ( name.isEmpty() ) { // shouldn't happen...
195  return 0;
196  }
197 
198  if ( localParentNode == localRoot ) { // possibly non-unique names on top-level
199  return 0;
200  }
201 
202  foreach ( LocalNode *childNode, localParentNode->childNodes ) {
203  // the restriction on empty RIDs can possibly removed, but for now I only understand the implication for this case
204  if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() ) {
205  return childNode;
206  }
207  }
208  return 0;
209  }
210 
215  LocalNode* findMatchingLocalNode( const Collection &collection )
216  {
217  if ( !hierarchicalRIDs ) {
218  if ( localRidMap.contains( collection.remoteId() ) ) {
219  return localRidMap.value( collection.remoteId() );
220  }
221  return 0;
222  } else {
223  if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() ) {
224  return localRoot;
225  }
226  LocalNode *localParent = 0;
227  if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
228  kWarning() << "Remote collection without valid parent found: " << collection;
229  return 0;
230  }
231  if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() ) {
232  localParent = localRoot;
233  } else {
234  localParent = findMatchingLocalNode( collection.parentCollection() );
235  }
236 
237  if ( localParent ) {
238  if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
239  return localParent->childRidMap.value( collection.remoteId() );
240  }
241  // check if we have a local folder with a matching name and no RID, if so let's use that one
242  // 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
243  if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.name() ) ) {
244  kDebug() << "Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
245  return recoveredLocalNode;
246  }
247  }
248  return 0;
249  }
250  }
251 
257  LocalNode* findBestLocalAncestor( const Collection &collection, bool *exactMatch = 0 )
258  {
259  if ( !hierarchicalRIDs ) {
260  return localRoot;
261  }
262  if ( collection == Collection::root() ) {
263  if ( exactMatch ) {
264  *exactMatch = true;
265  }
266  return localRoot;
267  }
268  if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
269  kWarning() << "Remote collection without valid parent found: " << collection;
270  return 0;
271  }
272  bool parentIsExact = false;
273  LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact );
274  if ( !parentIsExact ) {
275  if ( exactMatch ) {
276  *exactMatch = false;
277  }
278  return localParent;
279  }
280  if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
281  if ( exactMatch ) {
282  *exactMatch = true;
283  }
284  return localParent->childRidMap.value( collection.remoteId() );
285  }
286  if ( exactMatch ) {
287  *exactMatch = false;
288  }
289  return localParent;
290  }
291 
297  void processPendingRemoteNodes( LocalNode *_localRoot )
298  {
299  QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
300  _localRoot->pendingRemoteNodes.clear();
301  QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
302  foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
303  // step 1: see if we have a matching local node already
304  LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
305  if ( localNode ) {
306  Q_ASSERT( !localNode->processed );
307  updateLocalCollection( localNode, remoteNode );
308  continue;
309  }
310  // step 2: check if we have the parent at least, then we can create it
311  localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
312  if ( localNode ) {
313  pendingCreations[localNode].append( remoteNode );
314  continue;
315  }
316  // step 3: find the best matching ancestor and enqueue it for later processing
317  localNode = findBestLocalAncestor( remoteNode->collection );
318  if ( !localNode ) {
319  q->setError( Unknown );
320  q->setErrorText( i18n( "Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
321  q->emitResult();
322  return;
323  }
324  localNode->pendingRemoteNodes.append( remoteNode );
325  }
326 
327  // process the now possible collection creations
328  for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
329  it != pendingCreations.constEnd(); ++it ) {
330  createLocalCollections( it.key(), it.value() );
331  }
332  }
333 
337  void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
338  {
339  Collection upd( remoteNode->collection );
340  Q_ASSERT( !upd.remoteId().isEmpty() );
341  upd.setId( localNode->collection.id() );
342  if ( localNode->collection.attribute<EntityDisplayAttribute>() ) {
343  upd.removeAttribute<EntityDisplayAttribute>();
344  upd.addAttribute( localNode->collection.attribute<EntityDisplayAttribute>()->clone() );
345  }
346 
347  {
348  // ### HACK to work around the implicit move attempts of CollectionModifyJob
349  // which we do explicitly below
350  Collection c( upd );
351  c.setParentCollection( localNode->collection.parentCollection() );
352  ++pendingJobs;
353  CollectionModifyJob *mod = new CollectionModifyJob( c, q );
354  connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
355  }
356 
357  // detecting moves is only possible with global RIDs
358  if ( !hierarchicalRIDs ) {
359  LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
360  LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
361  // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
362  // local parent has been created
363  if ( newParent && oldParent != newParent ) {
364  ++pendingJobs;
365  CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
366  connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
367  }
368  }
369 
370  localNode->processed = true;
371  delete remoteNode;
372  }
373 
374  void updateLocalCollectionResult( KJob* job )
375  {
376  --pendingJobs;
377  if ( job->error() ) {
378  return; // handled by the base class
379  }
380  if ( qobject_cast<CollectionModifyJob*>( job ) ) {
381  ++progress;
382  }
383  checkDone();
384  }
385 
390  void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
391  {
392  foreach ( RemoteNode *remoteNode, remoteNodes ) {
393  ++pendingJobs;
394  Collection col( remoteNode->collection );
395  Q_ASSERT( !col.remoteId().isEmpty() );
396  col.setParentCollection( localParent->collection );
397  CollectionCreateJob *create = new CollectionCreateJob( col, q );
398  create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
399  create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
400  connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
401  }
402  }
403 
404  void createLocalCollectionResult( KJob* job )
405  {
406  --pendingJobs;
407  if ( job->error() ) {
408  return; // handled by the base class
409  }
410 
411  const Collection newLocal = static_cast<CollectionCreateJob*>( job )->collection();
412  LocalNode *localNode = createLocalNode( newLocal );
413  localNode->processed = true;
414 
415  LocalNode *localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
416  Q_ASSERT( localParent->childNodes.contains( localNode ) );
417  RemoteNode *remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
418  delete remoteNode;
419  ++progress;
420 
421  processPendingRemoteNodes( localParent );
422  if ( !hierarchicalRIDs ) {
423  processPendingRemoteNodes( localRoot );
424  }
425 
426  checkDone();
427  }
428 
432  bool hasProcessedChildren( LocalNode *localNode ) const
433  {
434  if ( localNode->processed ) {
435  return true;
436  }
437  foreach ( LocalNode *child, localNode->childNodes ) {
438  if ( hasProcessedChildren( child ) ) {
439  return true;
440  }
441  }
442  return false;
443  }
444 
449  Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
450  {
451  Collection::List rv;
452  if ( !localNode->processed ) {
453  if ( hasProcessedChildren( localNode ) ) {
454  kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
455  kWarning() << localNode->collection;
456  return rv;
457  }
458  if ( localNode->collection.remoteId().isEmpty() ) {
459  kWarning() << "Found unprocessed local node without remoteId, excluding from deletion";
460  kWarning() << localNode->collection;
461  return rv;
462  }
463  rv.append( localNode->collection );
464  return rv;
465  }
466 
467  foreach ( LocalNode *child, localNode->childNodes ) {
468  rv.append( findUnprocessedLocalCollections( child ) );
469  }
470  return rv;
471  }
472 
476  void deleteUnprocessedLocalNodes()
477  {
478  if ( incremental ) {
479  return;
480  }
481  const Collection::List cols = findUnprocessedLocalCollections( localRoot );
482  deleteLocalCollections( cols );
483  }
484 
489  void deleteLocalCollections( const Collection::List &cols )
490  {
491  q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
492  foreach ( const Collection &col, cols ) {
493  Q_ASSERT( !col.remoteId().isEmpty() ); // empty RID -> stuff we haven't even written to the remote side yet
494 
495  ++pendingJobs;
496  CollectionDeleteJob *job = new CollectionDeleteJob( col, q );
497  connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
498 
499  // It can happen that the groupware servers report us deleted collections
500  // twice, in this case this collection delete job will fail on the second try.
501  // To avoid a rollback of the complete transaction we gracefully allow the job
502  // to fail :)
503  q->setIgnoreJobFailure( job );
504  }
505  }
506 
507  void deleteLocalCollectionsResult( KJob* )
508  {
509  --pendingJobs;
510 
511  ++progress;
512  checkDone();
513  }
514 
518  void execute()
519  {
520  kDebug() << Q_FUNC_INFO << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
521  if ( !localListDone ) {
522  return;
523  }
524 
525  processPendingRemoteNodes( localRoot );
526 
527  if ( !incremental && deliveryDone ) {
528  deleteUnprocessedLocalNodes();
529  }
530 
531  if ( !hierarchicalRIDs ) {
532  deleteLocalCollections( removedRemoteCollections );
533  } else {
534  Collection::List localCols;
535  foreach ( const Collection &c, removedRemoteCollections ) {
536  LocalNode *node = findMatchingLocalNode( c );
537  if ( node ) {
538  localCols.append( node->collection );
539  }
540  }
541  deleteLocalCollections( localCols );
542  }
543  removedRemoteCollections.clear();
544 
545  checkDone();
546  }
547 
551  QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
552  {
553  QList<RemoteNode*> rv;
554  rv.append( localNode->pendingRemoteNodes );
555  foreach ( LocalNode *child, localNode->childNodes ) {
556  rv.append( findPendingRemoteNodes( child ) );
557  }
558  return rv;
559  }
560 
565  void checkDone()
566  {
567  q->setProcessedAmount( KJob::Bytes, progress );
568 
569  // still running jobs or not fully delivered local/remote state
570  if ( !deliveryDone || pendingJobs > 0 || !localListDone ) {
571  return;
572  }
573 
574  // safety check: there must be no pending remote nodes anymore
575  QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
576  if ( !orphans.isEmpty() ) {
577  q->setError( Unknown );
578  q->setErrorText( i18n( "Found unresolved orphan collections" ) );
579  foreach ( RemoteNode* orphan, orphans ) {
580  kDebug() << "found orphan collection:" << orphan->collection;
581  }
582  q->emitResult();
583  return;
584  }
585 
586  kDebug() << Q_FUNC_INFO << "q->commit()";
587  q->commit();
588  }
589 
590  CollectionSync *q;
591 
592  QString resourceId;
593 
594  int pendingJobs;
595  int progress;
596 
597  LocalNode* localRoot;
598  QHash<Collection::Id, LocalNode*> localUidMap;
599  QHash<QString, LocalNode*> localRidMap;
600 
601  // temporary during build-up of the local node tree, must be empty afterwards
602  QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
603 
604  // removed remote collections in incremental mode
605  Collection::List removedRemoteCollections;
606 
607  bool incremental;
608  bool streaming;
609  bool hierarchicalRIDs;
610 
611  bool localListDone;
612  bool deliveryDone;
613 };
614 
615 CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) :
616  TransactionSequence( parent ),
617  d( new Private( this ) )
618 {
619  d->resourceId = resourceId;
620  setTotalAmount( KJob::Bytes, 0 );
621 }
622 
623 CollectionSync::~CollectionSync()
624 {
625  delete d;
626 }
627 
628 void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections)
629 {
630  setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
631  foreach ( const Collection &c, remoteCollections ) {
632  d->createRemoteNode( c );
633  }
634 
635  if ( !d->streaming ) {
636  d->deliveryDone = true;
637  }
638  d->execute();
639 }
640 
641 void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections)
642 {
643  setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
644  d->incremental = true;
645  foreach ( const Collection &c, changedCollections ) {
646  d->createRemoteNode( c );
647  }
648  d->removedRemoteCollections += removedCollections;
649 
650  if ( !d->streaming ) {
651  d->deliveryDone = true;
652  }
653  d->execute();
654 }
655 
656 void CollectionSync::doStart()
657 {
658  CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this );
659  job->fetchScope().setResource( d->resourceId );
660  job->fetchScope().setIncludeUnsubscribed( true );
661  job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
662  connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
663  SLOT(localCollectionsReceived(Akonadi::Collection::List)) );
664  connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
665 }
666 
667 void CollectionSync::setStreamingEnabled( bool streaming )
668 {
669  d->streaming = streaming;
670 }
671 
672 void CollectionSync::retrievalDone()
673 {
674  d->deliveryDone = true;
675  d->execute();
676 }
677 
678 void CollectionSync::setHierarchicalRemoteIds( bool hierarchical )
679 {
680  d->hierarchicalRIDs = hierarchical;
681 }
682 
683 #include "moc_collectionsync_p.cpp"
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:27:33 by doxygen 1.8.3.1 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.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • 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