Commit 5b5048d1 authored by Volker Krause's avatar Volker Krause

SVN_MERGE

Merged revisions 1067025,1067047 via svnmerge from 
https://vkrause@svn.kde.org/home/kde/branches/work/akonadi-ports/kdepimlibs

........
  r1067025 | vkrause | 2009-12-28 20:05:37 +0100 (Mon, 28 Dec 2009) | 4 lines
  
  Surprising how well HRID-based resources worked so far, given that most
  commands simply ignored the ancestor chain and used plain RIDs instead
  for addressing objects...
........
  r1067047 | vkrause | 2009-12-28 20:52:46 +0100 (Mon, 28 Dec 2009) | 9 lines
  
  Add some more safeguards:
  - don't crash when moving a collection to a not yet existing parent, real
    fix is rather complicated and still pending though (defering the move
    until the needed local ancestors have been created)
  - in some cases (misbehaving resources or the above workaround) we might
    end up with unprocessed local nodes that have processed children,
    exclude those from deletion in non-incremental mode to avoid possible
    data loss
........

svn path=/trunk/KDE/kdepimlibs/; revision=1067479
parent 3f7a1f71
......@@ -23,6 +23,9 @@
#include "protocolhelper_p.h"
#include "collectionstatistics.h"
#include "collection_p.h"
#include <akonadi/private/protocol_p.h>
#include <KLocale>
using namespace Akonadi;
......@@ -52,18 +55,16 @@ CollectionModifyJob::~CollectionModifyJob()
void CollectionModifyJob::doStart()
{
Q_D( CollectionModifyJob );
if ( !d->mCollection.isValid() && d->mCollection.remoteId().isEmpty() ) {
setError( Unknown );
setErrorText( i18n( "Invalid collection" ) );
QByteArray command = d->newTag();
try {
command += ProtocolHelper::entityIdToByteArray( d->mCollection, AKONADI_CMD_COLLECTIONMODIFY );
} catch ( const std::exception &e ) {
setError( Job::Unknown );
setErrorText( QString::fromUtf8( e.what() ) );
emitResult();
return;
}
QByteArray command = d->newTag();
if ( d->mCollection.isValid() )
command += " MODIFY " + QByteArray::number( d->mCollection.id() );
else
command += " RID MODIFY " + ImapParser::quote( d->mCollection.remoteId().toUtf8() );
QByteArray changes;
if ( d->mCollection.d_func()->contentTypesChanged ) {
QList<QByteArray> bList;
......
......@@ -296,7 +296,9 @@ class CollectionSync::Private
if ( !hierarchicalRIDs ) {
LocalNode *oldParent = localUidMap.value( localNode->collection.parentCollection().id() );
LocalNode *newParent = findMatchingLocalNode( remoteNode->collection.parentCollection() );
if ( oldParent != newParent ) {
// TODO: handle the newParent == 0 case correctly, ie. defer the move until the new
// local parent has been created
if ( newParent && oldParent != newParent ) {
++pendingJobs;
CollectionMoveJob *move = new CollectionMoveJob( upd, newParent->collection, q );
connect( move, SIGNAL(result(KJob*)), q, SLOT(updateLocalCollectionResult(KJob*)) );
......@@ -358,11 +360,31 @@ class CollectionSync::Private
}
/**
Find all local nodes that are not marked as processed.
Checks if the given local node has processed child nodes.
*/
Collection::List findUnprocessedLocalCollections( LocalNode *localNode )
bool hasProcessedChildren( LocalNode *localNode ) const
{
if ( localNode->processed )
return true;
foreach ( LocalNode *child, localNode->childNodes ) {
if ( hasProcessedChildren( child ) )
return true;
}
return false;
}
/**
Find all local nodes that are not marked as processed and have no children that
are marked as processed.
*/
Collection::List findUnprocessedLocalCollections( LocalNode *localNode ) const
{
Collection::List rv;
if ( !localNode->processed && hasProcessedChildren( localNode ) ) {
kWarning() << "Found unprocessed local node with processed children, excluding from deletion";
kWarning() << localNode->collection;
return rv;
}
if ( !localNode->processed ) {
rv.append( localNode->collection );
return rv;
......@@ -379,7 +401,7 @@ class CollectionSync::Private
{
if ( incremental )
return;
Collection::List cols = findUnprocessedLocalCollections( localRoot );
const Collection::List cols = findUnprocessedLocalCollections( localRoot );
deleteLocalCollections( cols );
}
......
......@@ -23,6 +23,8 @@
#include <QtCore/QStringList>
#include <akonadi/entitydisplayattribute.h>
#include <akonadi/collectionstatistics.h>
#include <akonadi/item.h>
namespace Akonadi {
/**
......@@ -112,6 +114,10 @@ namespace CollectionUtils
return false;
return hasValidHierarchicalRID( col.parentCollection() );
}
inline bool hasValidHierarchicalRID( const Item &item )
{
return !item.remoteId().isEmpty() && hasValidHierarchicalRID( item.parentCollection() );
}
}
}
......
......@@ -226,6 +226,12 @@ QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Collection &col )
return '(' + QByteArray::number( col.id() ) + ' ' + ImapParser::quote( col.remoteId().toUtf8() ) + ") " + parentHrid;
}
QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Item &item )
{
const QByteArray parentHrid = hierarchicalRidToByteArray( item.parentCollection() );
return '(' + QByteArray::number( item.id() ) + ' ' + ImapParser::quote( item.remoteId().toUtf8() ) + ") " + parentHrid;
}
QByteArray ProtocolHelper::itemFetchScopeToByteArray( const ItemFetchScope &fetchScope )
{
QByteArray command;
......
......@@ -22,6 +22,7 @@
#include <akonadi/cachepolicy.h>
#include <akonadi/collection.h>
#include <akonadi/collectionutils_p.h>
#include <akonadi/item.h>
#include <akonadi/itemfetchscope.h>
......@@ -121,33 +122,71 @@ class ProtocolHelper
ImapSet set;
set.add( uids );
rv += set.toImapSequenceSet();
} else {
// check if all items have a remote id
QList<QByteArray> rids;
foreach ( const T &object, objects ) {
if ( object.remoteId().isEmpty() )
throw Exception( "No remote identifier specified" );
rids << ImapParser::quote( object.remoteId().toUtf8() );
}
return rv;
}
rv += " " AKONADI_CMD_RID " ";
// check if all items have a remote id
if ( std::find_if( objects.constBegin(), objects.constEnd(),
boost::bind( &QString::isEmpty, boost::bind( &T::remoteId, _1 ) ) )
!= objects.constEnd() )
{
throw Exception( "No remote identifier specified" );
}
// check if we have RIDs or HRIDs
if ( std::find_if( objects.constBegin(), objects.constEnd(),
!boost::bind( static_cast<bool (*)(const T&)>( &CollectionUtils::hasValidHierarchicalRID ), _1 ) )
== objects.constEnd() && objects.size() == 1 ) // ### HRID sets are not yet specified
{
// HRIDs
rv += " " AKONADI_CMD_HRID " ";
if ( !command.isEmpty() ) {
rv += command;
rv += ' ';
}
rv += '(';
rv += ImapParser::join( rids, " " );
rv += ')';
rv += '(' + hierarchicalRidToByteArray( objects.first() ) + ')';
return rv;
}
// RIDs
QList<QByteArray> rids;
foreach ( const T &object, objects ) {
rids << ImapParser::quote( object.remoteId().toUtf8() );
}
rv += " " AKONADI_CMD_RID " ";
if ( !command.isEmpty() ) {
rv += command;
rv += ' ';
}
rv += '(';
rv += ImapParser::join( rids, " " );
rv += ')';
return rv;
}
/**
Converts the given object identifier into a protocol representation.
@throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers.
*/
template <typename T>
static QByteArray entityIdToByteArray( const T &object, const QByteArray &command )
{
return entitySetToByteArray( typename T::List() << object, command );
}
/**
Converts the given collection's hierarchical RID into a protocol representation.
Assumes @p col has a valid hierarchical RID, so check that before!
*/
static QByteArray hierarchicalRidToByteArray( const Collection &col );
/**
Converts the HRID of the given item into an ASAP protocol representation.
Assumes @p item has a valid HRID.
*/
static QByteArray hierarchicalRidToByteArray( const Item &item );
/**
Converts a given ItemFetchScope object into a protocol representation.
*/
......
......@@ -38,6 +38,9 @@ class ProtocolHelperTest : public QObject
Item u3; u3.setId( 3 );
Item r1; r1.setRemoteId( "A" );
Item r2; r2.setRemoteId( "B" );
Item h1; h1.setRemoteId( "H1" ); h1.setParentCollection( Collection::root() );
Item h2; h2.setRemoteId( "H2a" ); h2.parentCollection().setRemoteId( "H2b" ); h2.parentCollection().setParentCollection( Collection::root() );
Item h3; h3.setRemoteId( "H3a" ); h3.parentCollection().setRemoteId( "H3b" );
QTest::newRow( "empty" ) << Item::List() << QByteArray( "CMD" ) << QByteArray() << true;
QTest::newRow( "single uid" ) << (Item::List() << u1) << QByteArray( "CMD" ) << QByteArray( " UID CMD 1" ) << false;
......@@ -49,6 +52,10 @@ class ProtocolHelperTest : public QObject
QTest::newRow( "mixed" ) << (Item::List() << u1 << r1) << QByteArray( "CMD" ) << QByteArray() << true;
QTest::newRow( "empty command, uid" ) << (Item::List() << u1) << QByteArray() << QByteArray( " UID 1" ) << false;
QTest::newRow( "empty command, single rid" ) << (Item::List() << r1) << QByteArray() << QByteArray( " RID (\"A\")" ) << false;
QTest::newRow( "single hrid" ) << (Item::List() << h1) << QByteArray( "CMD" ) << QByteArray( " HRID CMD ((-1 \"H1\") (0 \"\"))" ) << false;
QTest::newRow( "single hrid 2" ) << (Item::List() << h2) << QByteArray( "CMD" ) << QByteArray( " HRID CMD ((-1 \"H2a\") (-1 \"H2b\") (0 \"\"))" ) << false;
QTest::newRow( "mixed hrid/rid" ) << (Item::List() << h1 << r1) << QByteArray( "CMD" ) << QByteArray( " RID CMD (\"H1\" \"A\")" ) << false;
QTest::newRow( "unterminated hrid" ) << (Item::List() << h3) << QByteArray( "CMD" ) << QByteArray( " RID CMD (\"H3a\")" ) << false;
}
void testItemSetToByteArray()
......@@ -61,6 +68,7 @@ class ProtocolHelperTest : public QObject
bool didThrow = false;
try {
const QByteArray r = ProtocolHelper::entitySetToByteArray( items, command );
qDebug() << r << result;
QCOMPARE( r, result );
} catch ( const std::exception &e ) {
qDebug() << e.what();
......@@ -142,11 +150,11 @@ class ProtocolHelperTest : public QObject
Collection c;
c.setParentCollection( Collection::root() );
c.setRemoteId( "r1" );
QTest::newRow( "one level" ) << c << QByteArray( "(-17 \"r1\") (0 \"\")" );
QTest::newRow( "one level" ) << c << QByteArray( "(-20 \"r1\") (0 \"\")" );
Collection c2;
c2.setParentCollection( c );
c2.setRemoteId( "r2" );
QTest::newRow( "two level ok" ) << c2 << QByteArray( "(-18 \"r2\") (-17 \"r1\") (0 \"\")" );
QTest::newRow( "two level ok" ) << c2 << QByteArray( "(-21 \"r2\") (-20 \"r1\") (0 \"\")" );
}
void testHRidToByteArray()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment