• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.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  ~Private()
107  {
108  delete localRoot;
109  }
110 
112  LocalNode* createLocalNode( const Collection &col )
113  {
114  LocalNode *node = new LocalNode( col );
115  Q_ASSERT( !localUidMap.contains( col.id() ) );
116  localUidMap.insert( node->collection.id(), node );
117  if ( !hierarchicalRIDs && !col.remoteId().isEmpty() )
118  localRidMap.insert( node->collection.remoteId(), node );
119 
120  // add already existing children
121  if ( localPendingCollections.contains( col.id() ) ) {
122  QVector<Collection::Id> childIds = localPendingCollections.take( col.id() );
123  foreach ( Collection::Id childId, childIds ) {
124  Q_ASSERT( localUidMap.contains( childId ) );
125  LocalNode *childNode = localUidMap.value( childId );
126  node->childNodes.append( childNode );
127  if ( !childNode->collection.remoteId().isEmpty() )
128  node->childRidMap.insert( childNode->collection.remoteId(), childNode );
129  }
130  }
131 
132  // set our parent and add ourselves as child
133  if ( localUidMap.contains( col.parentCollection().id() ) ) {
134  LocalNode* parentNode = localUidMap.value( col.parentCollection().id() );
135  parentNode->childNodes.append( node );
136  if ( !node->collection.remoteId().isEmpty() )
137  parentNode->childRidMap.insert( node->collection.remoteId(), node );
138  } else {
139  localPendingCollections[ col.parentCollection().id() ].append( col.id() );
140  }
141 
142  return node;
143  }
144 
146  void createRemoteNode( const Collection &col )
147  {
148  if ( col.remoteId().isEmpty() ) {
149  kWarning() << "Collection '" << col.name() << "' does not have a remote identifier - skipping";
150  return;
151  }
152  RemoteNode *node = new RemoteNode( col );
153  localRoot->pendingRemoteNodes.append( node );
154  }
155 
157  void localCollectionsReceived( const Akonadi::Collection::List &localCols )
158  {
159  foreach ( const Collection &c, localCols )
160  createLocalNode( c );
161  }
162 
164  void localCollectionFetchResult( KJob *job )
165  {
166  if ( job->error() )
167  return; // handled by the base class
168 
169  // safety check: the local tree has to be connected
170  if ( !localPendingCollections.isEmpty() ) {
171  q->setError( Unknown );
172  q->setErrorText( i18n( "Inconsistent local collection tree detected." ) );
173  q->emitResult();
174  return;
175  }
176 
177  localListDone = true;
178  execute();
179  }
180 
186  LocalNode* findLocalChildNodeByName( LocalNode *localParentNode, const QString &name )
187  {
188  if ( name.isEmpty() ) // shouldn't happen...
189  return 0;
190 
191  if ( localParentNode == localRoot ) // possibly non-unique names on top-level
192  return 0;
193 
194  foreach ( LocalNode *childNode, localParentNode->childNodes ) {
195  // the restriction on empty RIDs can possibly removed, but for now I only understand the implication for this case
196  if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() )
197  return childNode;
198  }
199  return 0;
200  }
201 
206  LocalNode* findMatchingLocalNode( const Collection &collection )
207  {
208  if ( !hierarchicalRIDs ) {
209  if ( localRidMap.contains( collection.remoteId() ) )
210  return localRidMap.value( collection.remoteId() );
211  return 0;
212  } else {
213  if ( collection.id() == Collection::root().id() || collection.remoteId() == Collection::root().remoteId() )
214  return localRoot;
215  LocalNode *localParent = 0;
216  if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
217  kWarning() << "Remote collection without valid parent found: " << collection;
218  return 0;
219  }
220  if ( collection.parentCollection().id() == Collection::root().id() || collection.parentCollection().remoteId() == Collection::root().remoteId() )
221  localParent = localRoot;
222  else
223  localParent = findMatchingLocalNode( collection.parentCollection() );
224 
225  if ( localParent ) {
226  if ( localParent->childRidMap.contains( collection.remoteId() ) )
227  return localParent->childRidMap.value( collection.remoteId() );
228  // check if we have a local folder with a matching name and no RID, if so let's use that one
229  // 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
230  if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.name() ) ) {
231  kDebug() << "Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
232  return recoveredLocalNode;
233  }
234  }
235  return 0;
236  }
237  }
238 
244  LocalNode* findBestLocalAncestor( const Collection &collection, bool *exactMatch = 0 )
245  {
246  if ( !hierarchicalRIDs )
247  return localRoot;
248  if ( collection == Collection::root() ) {
249  if ( exactMatch ) *exactMatch = true;
250  return localRoot;
251  }
252  if ( collection.parentCollection().id() < 0 && collection.parentCollection().remoteId().isEmpty() ) {
253  kWarning() << "Remote collection without valid parent found: " << collection;
254  return 0;
255  }
256  bool parentIsExact = false;
257  LocalNode *localParent = findBestLocalAncestor( collection.parentCollection(), &parentIsExact );
258  if ( !parentIsExact ) {
259  if ( exactMatch ) *exactMatch = false;
260  return localParent;
261  }
262  if ( localParent->childRidMap.contains( collection.remoteId() ) ) {
263  if ( exactMatch ) *exactMatch = true;
264  return localParent->childRidMap.value( collection.remoteId() );
265  }
266  if ( exactMatch ) *exactMatch = false;
267  return localParent;
268  }
269 
275  void processPendingRemoteNodes( LocalNode *_localRoot )
276  {
277  QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
278  _localRoot->pendingRemoteNodes.clear();
279  QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
280  foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
281  // step 1: see if we have a matching local node already
282  LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
283  if ( localNode ) {
284  Q_ASSERT( !localNode->processed );
285  updateLocalCollection( localNode, remoteNode );
286  continue;
287  }
288  // step 2: check if we have the parent at least, then we can create it
289  localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
290  if ( localNode ) {
291  pendingCreations[localNode].append( remoteNode );
292  continue;
293  }
294  // step 3: find the best matching ancestor and enqueue it for later processing
295  localNode = findBestLocalAncestor( remoteNode->collection );
296  if ( !localNode ) {
297  q->setError( Unknown );
298  q->setErrorText( i18n( "Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
299  q->emitResult();
300  return;
301  }
302  localNode->pendingRemoteNodes.append( remoteNode );
303  }
304 
305  // process the now possible collection creations
306  for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
307  it != pendingCreations.constEnd(); ++it )
308  {
309  createLocalCollections( it.key(), it.value() );
310  }
311  }
312 
316  void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
317  {
318  Collection upd( remoteNode->collection );
319  Q_ASSERT( !upd.remoteId().isEmpty() );
320  upd.setId( localNode->collection.id() );
321  {
322  // ### HACK to work around the implicit move attempts of CollectionModifyJob
323  // which we do explicitly below
324  Collection c( upd );
325  c.setParentCollection( localNode->collection.parentCollection() );
326  ++pendingJobs;
327  CollectionModifyJob *mod = new CollectionModifyJob( c, q );
328  connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
329  }
330 
331  // detecting moves is only possible with global RIDs
332  if ( !hierarchicalRIDs ) {
333  LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
334  LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
335  // TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
336  // local parent has been created
337  if ( newParent && oldParent != newParent ) {
338  ++pendingJobs;
339  CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
340  connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
341  }
342  }
343 
344  localNode->processed = true;
345  delete remoteNode;
346  }
347 
348  void updateLocalCollectionResult( KJob* job )
349  {
350  --pendingJobs;
351  if ( job->error() )
352  return; // handled by the base class
353  if ( qobject_cast<CollectionModifyJob*>( job ) )
354  ++progress;
355  checkDone();
356  }
357 
362  void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
363  {
364  foreach ( RemoteNode *remoteNode, remoteNodes ) {
365  ++pendingJobs;
366  Collection col( remoteNode->collection );
367  Q_ASSERT( !col.remoteId().isEmpty() );
368  col.setParentCollection( localParent->collection );
369  CollectionCreateJob *create = new CollectionCreateJob( col, q );
370  create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
371  create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
372  connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
373  }
374  }
375 
376  void createLocalCollectionResult( KJob* job )
377  {
378  --pendingJobs;
379  if ( job->error() )
380  return; // handled by the base class
381 
382  const Collection newLocal = static_cast<CollectionCreateJob*>( job )->collection();
383  LocalNode* localNode = createLocalNode( newLocal );
384  localNode->processed = true;
385 
386  LocalNode* localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
387  Q_ASSERT( localParent->childNodes.contains( localNode ) );
388  RemoteNode* remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
389  delete remoteNode;
390  ++progress;
391 
392  processPendingRemoteNodes( localParent );
393  if ( !hierarchicalRIDs )
394  processPendingRemoteNodes( localRoot );
395 
396  checkDone();
397  }
398 
402  bool hasProcessedChildren( LocalNode *localNode ) const
403  {
404  if ( localNode->processed )
405  return true;
406  foreach ( LocalNode *child, localNode->childNodes ) {
407  if ( hasProcessedChildren( child ) )
408  return true;
409  }
410  return false;
411  }
412 
417  Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
418  {
419  Collection::List rv;
420  if ( !localNode->processed ) {
421  if ( hasProcessedChildren( localNode ) ) {
422  kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
423  kWarning() << localNode->collection;
424  return rv;
425  }
426  if ( localNode->collection.remoteId().isEmpty() ) {
427  kWarning() << "Found unprocessed local node without remoteId, excluding from deletion";
428  kWarning() << localNode->collection;
429  return rv;
430  }
431  rv.append( localNode->collection );
432  return rv;
433  }
434 
435  foreach ( LocalNode *child, localNode->childNodes )
436  rv.append( findUnprocessedLocalCollections( child ) );
437  return rv;
438  }
439 
443  void deleteUnprocessedLocalNodes()
444  {
445  if ( incremental )
446  return;
447  const Collection::List cols = findUnprocessedLocalCollections( localRoot );
448  deleteLocalCollections( cols );
449  }
450 
455  void deleteLocalCollections( const Collection::List &cols )
456  {
457  q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
458  foreach ( const Collection &col, cols ) {
459  Q_ASSERT( !col.remoteId().isEmpty() ); // empty RID -> stuff we haven't even written to the remote side yet
460 
461  ++pendingJobs;
462  CollectionDeleteJob *job = new CollectionDeleteJob( col, q );
463  connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
464 
465  // It can happen that the groupware servers report us deleted collections
466  // twice, in this case this collection delete job will fail on the second try.
467  // To avoid a rollback of the complete transaction we gracefully allow the job
468  // to fail :)
469  q->setIgnoreJobFailure( job );
470  }
471  }
472 
473  void deleteLocalCollectionsResult( KJob* )
474  {
475  --pendingJobs;
476 
477  ++progress;
478  checkDone();
479  }
480 
484  void execute()
485  {
486  kDebug() << Q_FUNC_INFO << "localListDone: " << localListDone << " deliveryDone: " << deliveryDone;
487  if ( !localListDone )
488  return;
489 
490  processPendingRemoteNodes( localRoot );
491 
492  if ( !incremental && deliveryDone )
493  deleteUnprocessedLocalNodes();
494 
495  if ( !hierarchicalRIDs ) {
496  deleteLocalCollections( removedRemoteCollections );
497  } else {
498  Collection::List localCols;
499  foreach ( const Collection &c, removedRemoteCollections ) {
500  LocalNode *node = findMatchingLocalNode( c );
501  if ( node )
502  localCols.append( node->collection );
503  }
504  deleteLocalCollections( localCols );
505  }
506  removedRemoteCollections.clear();
507 
508  checkDone();
509  }
510 
514  QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
515  {
516  QList<RemoteNode*> rv;
517  rv.append( localNode->pendingRemoteNodes );
518  foreach ( LocalNode *child, localNode->childNodes )
519  rv.append( findPendingRemoteNodes( child ) );
520  return rv;
521  }
522 
527  void checkDone()
528  {
529  q->setProcessedAmount( KJob::Bytes, progress );
530 
531  // still running jobs or not fully delivered local/remote state
532  if ( !deliveryDone || pendingJobs > 0 || !localListDone )
533  return;
534 
535  // safety check: there must be no pending remote nodes anymore
536  QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
537  if ( !orphans.isEmpty() ) {
538  q->setError( Unknown );
539  q->setErrorText( i18n( "Found unresolved orphan collections" ) );
540  foreach ( RemoteNode* orphan, orphans )
541  kDebug() << "found orphan collection:" << orphan->collection;
542  q->emitResult();
543  return;
544  }
545 
546  kDebug() << Q_FUNC_INFO << "q->commit()";
547  q->commit();
548  }
549 
550  CollectionSync *q;
551 
552  QString resourceId;
553 
554  int pendingJobs;
555  int progress;
556 
557  LocalNode* localRoot;
558  QHash<Collection::Id, LocalNode*> localUidMap;
559  QHash<QString, LocalNode*> localRidMap;
560 
561  // temporary during build-up of the local node tree, must be empty afterwards
562  QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
563 
564  // removed remote collections in incremental mode
565  Collection::List removedRemoteCollections;
566 
567  bool incremental;
568  bool streaming;
569  bool hierarchicalRIDs;
570 
571  bool localListDone;
572  bool deliveryDone;
573 };
574 
575 CollectionSync::CollectionSync( const QString &resourceId, QObject *parent ) :
576  TransactionSequence( parent ),
577  d( new Private( this ) )
578 {
579  d->resourceId = resourceId;
580  setTotalAmount( KJob::Bytes, 0 );
581 }
582 
583 CollectionSync::~CollectionSync()
584 {
585  delete d;
586 }
587 
588 void CollectionSync::setRemoteCollections(const Collection::List & remoteCollections)
589 {
590  setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
591  foreach ( const Collection &c, remoteCollections )
592  d->createRemoteNode( c );
593 
594  if ( !d->streaming )
595  d->deliveryDone = true;
596  d->execute();
597 }
598 
599 void CollectionSync::setRemoteCollections(const Collection::List & changedCollections, const Collection::List & removedCollections)
600 {
601  setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
602  d->incremental = true;
603  foreach ( const Collection &c, changedCollections )
604  d->createRemoteNode( c );
605  d->removedRemoteCollections += removedCollections;
606 
607  if ( !d->streaming )
608  d->deliveryDone = true;
609  d->execute();
610 }
611 
612 void CollectionSync::doStart()
613 {
614  CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, this );
615  job->fetchScope().setResource( d->resourceId );
616  job->fetchScope().setIncludeUnsubscribed( true );
617  job->fetchScope().setAncestorRetrieval( CollectionFetchScope::Parent );
618  connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
619  SLOT(localCollectionsReceived(Akonadi::Collection::List)) );
620  connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
621 }
622 
623 void CollectionSync::setStreamingEnabled( bool streaming )
624 {
625  d->streaming = streaming;
626 }
627 
628 void CollectionSync::retrievalDone()
629 {
630  d->deliveryDone = true;
631  d->execute();
632 }
633 
634 void CollectionSync::setHierarchicalRemoteIds( bool hierarchical )
635 {
636  d->hierarchicalRIDs = hierarchical;
637 }
638 
639 #include "collectionsync_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Sep 24 2012 09:06:25 by doxygen 1.8.1.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.9.1 API Reference

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