20 #include "collectionsync_p.h"
21 #include "collection.h"
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"
31 #include "cachepolicy.h"
34 #include <KLocalizedString>
35 #include <QtCore/QVariant>
37 using namespace Akonadi;
53 qDeleteAll( childNodes );
54 qDeleteAll( pendingRemoteNodes );
58 QList<LocalNode*> childNodes;
59 QHash<QString, LocalNode*> childRidMap;
63 QList<RemoteNode*> pendingRemoteNodes;
67 Q_DECLARE_METATYPE( LocalNode* )
68 static const
char LOCAL_NODE[] = "LocalNode";
83 Q_DECLARE_METATYPE( RemoteNode* )
84 static const
char REMOTE_NODE[] = "RemoteNode";
97 currentTransaction( 0 ),
98 knownLocalCollections( 0 ),
101 hierarchicalRIDs( false ),
102 localListDone( false ),
103 deliveryDone( false )
110 qDeleteAll( rootRemoteNodes );
118 localRoot->processed =
true;
119 if ( currentTransaction ) {
121 localRoot->pendingRemoteNodes.swap( rootRemoteNodes );
126 localUidMap.insert( localRoot->collection.id(), localRoot );
127 if ( !hierarchicalRIDs ) {
128 localRidMap.insert( QString(), localRoot );
133 LocalNode* createLocalNode(
const Collection &col )
135 LocalNode *node =
new LocalNode( col );
136 Q_ASSERT( !localUidMap.contains( col.
id() ) );
137 localUidMap.insert( node->collection.id(), node );
138 if ( !hierarchicalRIDs && !col.
remoteId().isEmpty() ) {
139 localRidMap.insert( node->collection.remoteId(), node );
143 if ( localPendingCollections.contains( col.
id() ) ) {
144 QVector<Collection::Id> childIds = localPendingCollections.take( col.
id() );
146 Q_ASSERT( localUidMap.contains( childId ) );
147 LocalNode *childNode = localUidMap.value( childId );
148 node->childNodes.append( childNode );
149 if ( !childNode->collection.remoteId().isEmpty() ) {
150 node->childRidMap.insert( childNode->collection.remoteId(), childNode );
158 parentNode->childNodes.append( node );
159 if ( !node->collection.remoteId().isEmpty() ) {
160 parentNode->childRidMap.insert( node->collection.remoteId(), node );
170 void createRemoteNode(
const Collection &col )
173 kWarning() <<
"Collection '" << col.
name() <<
"' does not have a remote identifier - skipping";
176 RemoteNode *node =
new RemoteNode( col );
177 rootRemoteNodes.append( node );
184 createLocalNode( c );
185 knownLocalCollections++;
190 void localCollectionFetchResult( KJob *job )
192 if ( job->error() ) {
197 if ( !localPendingCollections.isEmpty() ) {
198 q->setError( Unknown );
199 q->setErrorText( i18n(
"Inconsistent local collection tree detected." ) );
204 localListDone =
true;
213 LocalNode* findLocalChildNodeByName( LocalNode *localParentNode,
const QString &name )
const
215 if ( name.isEmpty() ) {
219 if ( localParentNode == localRoot ) {
223 foreach ( LocalNode *childNode, localParentNode->childNodes ) {
225 if ( childNode->collection.name() == name && childNode->collection.remoteId().isEmpty() ) {
236 LocalNode* findMatchingLocalNode(
const Collection &collection )
const
238 if ( !hierarchicalRIDs ) {
239 if ( localRidMap.contains( collection.
remoteId() ) ) {
240 return localRidMap.value( collection.
remoteId() );
247 LocalNode *localParent = 0;
249 kWarning() <<
"Remote collection without valid parent found: " << collection;
253 localParent = localRoot;
259 if ( localParent->childRidMap.contains( collection.
remoteId() ) ) {
260 return localParent->childRidMap.value( collection.
remoteId() );
264 if ( LocalNode *recoveredLocalNode = findLocalChildNodeByName( localParent, collection.
name() ) ) {
265 kDebug() <<
"Recovering collection with lost RID:" << collection << recoveredLocalNode->collection;
266 return recoveredLocalNode;
278 LocalNode* findBestLocalAncestor(
const Collection &collection,
bool *exactMatch = 0 )
280 if ( !hierarchicalRIDs ) {
290 kWarning() <<
"Remote collection without valid parent found: " << collection;
293 bool parentIsExact =
false;
294 LocalNode *localParent = findBestLocalAncestor( collection.
parentCollection(), &parentIsExact );
295 if ( !parentIsExact ) {
301 if ( localParent->childRidMap.contains( collection.
remoteId() ) ) {
305 return localParent->childRidMap.value( collection.
remoteId() );
316 bool checkPendingRemoteNodes()
const
318 if ( rootRemoteNodes.size() != knownLocalCollections ) {
322 foreach ( RemoteNode *remoteNode, rootRemoteNodes ) {
324 LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
326 if ( checkLocalCollection( localNode, remoteNode ) ) {
341 void processPendingRemoteNodes( LocalNode *_localRoot )
343 QList<RemoteNode*> pendingRemoteNodes( _localRoot->pendingRemoteNodes );
344 _localRoot->pendingRemoteNodes.clear();
345 QHash<LocalNode*, QList<RemoteNode*> > pendingCreations;
346 foreach ( RemoteNode *remoteNode, pendingRemoteNodes ) {
348 LocalNode *localNode = findMatchingLocalNode( remoteNode->collection );
350 Q_ASSERT( !localNode->processed );
351 updateLocalCollection( localNode, remoteNode );
355 localNode = findMatchingLocalNode( remoteNode->collection.parentCollection() );
357 pendingCreations[localNode].append( remoteNode );
361 localNode = findBestLocalAncestor( remoteNode->collection );
363 q->setError( Unknown );
364 q->setErrorText( i18n(
"Remote collection without root-terminated ancestor chain provided, resource is broken." ) );
368 localNode->pendingRemoteNodes.append( remoteNode );
372 for ( QHash<LocalNode*, QList<RemoteNode*> >::const_iterator it = pendingCreations.constBegin();
373 it != pendingCreations.constEnd(); ++it ) {
374 createLocalCollections( it.key(), it.value() );
381 bool checkLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
const
383 const Collection &localCollection = localNode->collection;
384 const Collection &remoteCollection = remoteNode->collection;
400 if ( localCollection.
name() != remoteCollection.
name() ) {
428 void updateLocalCollection( LocalNode *localNode, RemoteNode *remoteNode )
431 Q_ASSERT( !upd.remoteId().isEmpty() );
432 Q_ASSERT( currentTransaction );
433 upd.setId( localNode->collection.id() );
443 c.setParentCollection( localNode->collection.parentCollection() );
446 connect( mod, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
450 if ( !hierarchicalRIDs ) {
451 LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
452 LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
455 if ( newParent && oldParent != newParent ) {
458 connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
462 localNode->processed =
true;
466 void updateLocalCollectionResult( KJob* job )
469 if ( job->error() ) {
472 if ( qobject_cast<CollectionModifyJob*>( job ) ) {
482 void createLocalCollections( LocalNode* localParent, QList<RemoteNode*> remoteNodes )
484 foreach ( RemoteNode *remoteNode, remoteNodes ) {
487 Q_ASSERT( !col.
remoteId().isEmpty() );
490 create->setProperty( LOCAL_NODE, QVariant::fromValue( localParent ) );
491 create->setProperty( REMOTE_NODE, QVariant::fromValue( remoteNode ) );
492 connect( create, SIGNAL(result(KJob*)), q, SLOT(createLocalCollectionResult(KJob*)) );
496 void createLocalCollectionResult( KJob* job )
499 if ( job->error() ) {
504 LocalNode *localNode = createLocalNode( newLocal );
505 localNode->processed =
true;
507 LocalNode *localParent = job->property( LOCAL_NODE ).value<LocalNode*>();
508 Q_ASSERT( localParent->childNodes.contains( localNode ) );
509 RemoteNode *remoteNode = job->property( REMOTE_NODE ).value<RemoteNode*>();
513 processPendingRemoteNodes( localParent );
514 if ( !hierarchicalRIDs ) {
515 processPendingRemoteNodes( localRoot );
524 bool hasProcessedChildren( LocalNode *localNode )
const
526 if ( localNode->processed ) {
529 foreach ( LocalNode *child, localNode->childNodes ) {
530 if ( hasProcessedChildren( child ) ) {
541 Collection::List findUnprocessedLocalCollections( LocalNode *localNode )
const
544 if ( !localNode->processed ) {
545 if ( hasProcessedChildren( localNode ) ) {
546 kWarning() <<
"Found unprocessed local node with processed children, excluding from deletion";
547 kWarning() << localNode->collection;
550 if ( localNode->collection.remoteId().isEmpty() ) {
551 kWarning() <<
"Found unprocessed local node without remoteId, excluding from deletion";
552 kWarning() << localNode->collection;
555 rv.append( localNode->collection );
559 foreach ( LocalNode *child, localNode->childNodes ) {
560 rv.append( findUnprocessedLocalCollections( child ) );
568 void deleteUnprocessedLocalNodes()
574 deleteLocalCollections( cols );
583 q->setTotalAmount( KJob::Bytes, q->totalAmount( KJob::Bytes ) + cols.size() );
585 Q_ASSERT( !col.
remoteId().isEmpty() );
588 Q_ASSERT( currentTransaction );
590 connect( job, SIGNAL(result(KJob*)), q, SLOT(deleteLocalCollectionsResult(KJob*)) );
596 currentTransaction->setIgnoreJobFailure( job );
600 void deleteLocalCollectionsResult( KJob* )
611 void checkUpdateNecessity()
613 bool updateNeeded = checkPendingRemoteNodes();
614 if ( !updateNeeded ) {
621 Q_ASSERT( !currentTransaction );
623 currentTransaction->setAutomaticCommittingEnabled(
false );
624 q->connect( currentTransaction, SIGNAL(result(KJob*)), SLOT(transactionSequenceResult(KJob*)) );
631 void transactionSequenceResult( KJob *job )
633 if ( job->error() ) {
645 kDebug() << Q_FUNC_INFO <<
"localListDone: " << localListDone <<
" deliveryDone: " << deliveryDone;
646 if ( !localListDone ) {
652 if ( !currentTransaction ) {
653 checkUpdateNecessity();
658 processPendingRemoteNodes( localRoot );
660 if ( !incremental && deliveryDone ) {
661 deleteUnprocessedLocalNodes();
664 if ( !hierarchicalRIDs ) {
665 deleteLocalCollections( removedRemoteCollections );
668 foreach (
const Collection &c, removedRemoteCollections ) {
669 LocalNode *node = findMatchingLocalNode( c );
671 localCols.append( node->collection );
674 deleteLocalCollections( localCols );
676 removedRemoteCollections.clear();
684 QList<RemoteNode*> findPendingRemoteNodes( LocalNode *localNode )
686 QList<RemoteNode*> rv;
687 rv.append( localNode->pendingRemoteNodes );
688 foreach ( LocalNode *child, localNode->childNodes ) {
689 rv.append( findPendingRemoteNodes( child ) );
700 q->setProcessedAmount( KJob::Bytes, progress );
703 if ( !deliveryDone || pendingJobs > 0 || !localListDone ) {
708 QList<RemoteNode*> orphans = findPendingRemoteNodes( localRoot );
709 if ( !orphans.isEmpty() ) {
710 q->setError( Unknown );
711 q->setErrorText( i18n(
"Found unresolved orphan collections" ) );
712 foreach ( RemoteNode* orphan, orphans ) {
713 kDebug() <<
"found orphan collection:" << orphan->collection;
719 kDebug() << Q_FUNC_INFO <<
"q->commit()";
720 Q_ASSERT( currentTransaction );
721 currentTransaction->commit();
731 LocalNode* localRoot;
733 QHash<Collection::Id, LocalNode*> localUidMap;
734 QHash<QString, LocalNode*> localRidMap;
737 QHash<Collection::Id, QVector<Collection::Id> > localPendingCollections;
743 QList<RemoteNode*> rootRemoteNodes;
747 int knownLocalCollections;
751 bool hierarchicalRIDs;
759 d( new Private( this ) )
761 d->resourceId = resourceId;
762 setTotalAmount( KJob::Bytes, 0 );
772 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + remoteCollections.count() );
773 foreach (
const Collection &c, remoteCollections ) {
774 d->createRemoteNode( c );
777 if ( !d->streaming ) {
778 d->deliveryDone =
true;
785 setTotalAmount( KJob::Bytes, totalAmount( KJob::Bytes ) + changedCollections.count() );
786 d->incremental =
true;
787 foreach (
const Collection &c, changedCollections ) {
788 d->createRemoteNode( c );
790 d->removedRemoteCollections += removedCollections;
792 if ( !d->streaming ) {
793 d->deliveryDone =
true;
801 Job *parent = ( d->currentTransaction ?
static_cast<Job*
>( d->currentTransaction ) : static_cast<Job*>(
this ) );
808 connect( job, SIGNAL(result(KJob*)), SLOT(localCollectionFetchResult(KJob*)) );
813 d->streaming = streaming;
818 d->deliveryDone =
true;
824 d->hierarchicalRIDs = hierarchical;
829 if ( d->currentTransaction ) {
830 d->currentTransaction->rollback();
834 #include "moc_collectionsync_p.cpp"
Job that modifies a collection in the Akonadi storage.
void rollback()
Do a rollback operation if needed.
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
Sets how many levels of ancestor collections should be included in the retrieval. ...
QString name() const
Returns the i18n'ed name of the collection.
void setIncludeUnsubscribed(bool include)
Sets whether unsubscribed collections should be included in the collection listing.
Job that moves a collection in the Akonadi storage to a new parent collection.
CollectionFetchScope & fetchScope()
Returns the collection fetch scope.
void setResource(const QString &resource)
Sets a resource filter, that is only collections owned by the specified resource are retrieved...
Represents a collection of PIM items.
Job that fetches collections from the Akonadi storage.
qint64 Id
Describes the unique id type.
Base class for all actions in the Akonadi storage.
~CollectionSync()
Destroys this job.
Provides interface for custom attributes for Entity.
Only retrieve the immediate parent collection.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
void setHierarchicalRemoteIds(bool hierarchical)
Indicate whether the resource supplies collections with hierarchical or global remote identifiers...
void setRemoteCollections(const Collection::List &remoteCollections)
Sets the result of a full remote collection listing.
Attribute * attribute(const QByteArray &name) const
Returns the attribute of the given type name if available, 0 otherwise.
Collection parentCollection() const
Returns the parent collection of this object.
void doStart()
This method must be reimplemented in the concrete jobs.
QString remoteId() const
Returns the remote id of the entity.
EntityDisplayAttribute * clone() const
Creates a copy of this attribute.
void retrievalDone()
Indicate that all collections have been retrieved in streaming mode.
QString remoteRevision() const
Returns the remote revision of the entity.
static Collection root()
Returns the root collection.
Job that deletes a collection in the Akonadi storage.
Id id() const
Returns the unique identifier of the entity.
CollectionSync(const QString &resourceId, QObject *parent=0)
Creates a new collection synchronzier.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
QStringList contentMimeTypes() const
Returns a list of possible content mimetypes, e.g.
CachePolicy cachePolicy() const
Returns the cache policy of the collection.
virtual QByteArray serialized() const =0
Returns a QByteArray representation of the attribute which will be storaged.
Job that creates a new collection in the Akonadi storage.
virtual QByteArray type() const =0
Returns the type of the attribute.
List all sub-collections.
void setStreamingEnabled(bool streaming)
Enables streaming, that is not all collections are delivered at once.
QList< Collection > List
Describes a list of collections.
Attribute that stores the properties that are used to display an entity.
Attribute::List attributes() const
Returns a list of all attributes of the entity.