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

Skip menu "kdepimlibs-4.10.1 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