Commit 946ec24e authored by Volker Krause's avatar Volker Krause

- cleanup CollectionCreateJob API and the corresponding unit tests

- share collection parsing code

svn path=/trunk/KDE/kdepim/akonadi/libakonadi/; revision=778715
parent 78f872fa
......@@ -70,6 +70,17 @@ CachePolicy & CachePolicy::operator =(const CachePolicy & other)
return *this;
}
bool Akonadi::CachePolicy::operator ==(const CachePolicy & other) const
{
if ( !d->inherit && !other.d->inherit ) {
return d->localParts == other.d->localParts
&& d->timeout == other.d->timeout
&& d->interval == other.d->interval
&& d->syncOnDemand == other.d->syncOnDemand;
}
return d->inherit == other.d->inherit;
}
bool CachePolicy::inheritFromParent() const
{
return d->inherit;
......
......@@ -71,6 +71,11 @@ class AKONADI_EXPORT CachePolicy
*/
CachePolicy& operator=( const CachePolicy &other );
/**
Comparisson operator
*/
bool operator==( const CachePolicy &other ) const;
/**
Inherit cache policy from parent collection.
*/
......
......@@ -30,21 +30,14 @@ class Akonadi::CollectionCreateJobPrivate {
CollectionCreateJobPrivate()
{}
Collection parent;
QString name;
QStringList contentTypes;
Collection collection;
QString remoteId;
QList<QPair<QByteArray, QByteArray> > attributes;
CachePolicy cachePolicy;
};
CollectionCreateJob::CollectionCreateJob( const Collection &parentCollection, const QString &name, QObject * parent ) :
CollectionCreateJob::CollectionCreateJob( const Collection &collection, QObject * parent ) :
Job( parent ),
d( new CollectionCreateJobPrivate )
{
d->parent = parentCollection;
d->name = name;
d->collection = collection;
}
CollectionCreateJob::~ CollectionCreateJob( )
......@@ -54,30 +47,24 @@ CollectionCreateJob::~ CollectionCreateJob( )
void CollectionCreateJob::doStart( )
{
QByteArray command = newTag() + " CREATE \"" + d->name.toUtf8() + "\" ";
command += QByteArray::number( d->parent.id() );
QByteArray command = newTag() + " CREATE \"" + d->collection.name().toUtf8() + "\" ";
command += QByteArray::number( d->collection.parent() );
command += " (";
if ( !d->contentTypes.isEmpty() )
if ( !d->collection.contentTypes().isEmpty() )
{
QList<QByteArray> cList;
foreach( QString s, d->contentTypes ) cList << s.toLatin1();
foreach( QString s, d->collection.contentTypes() ) cList << s.toLatin1();
command += "MIMETYPE (" + ImapParser::join( cList, QByteArray(" ") ) + ')';
}
command += " REMOTEID \"" + d->remoteId.toUtf8() + '"';
typedef QPair<QByteArray,QByteArray> QByteArrayPair;
foreach ( const QByteArrayPair bp, d->attributes )
command += ' ' + bp.first + ' ' + bp.second;
command += " " + ProtocolHelper::cachePolicyToByteArray( d->cachePolicy );
command += " REMOTEID \"" + d->collection.remoteId().toUtf8() + '"';
foreach ( CollectionAttribute* attr, d->collection.attributes() )
command += ' ' + attr->type() + ' ' + ImapParser::quote( attr->toByteArray() );
command += " " + ProtocolHelper::cachePolicyToByteArray( d->collection.cachePolicy() );
command += ")\n";
writeData( command );
emitWriteFinished();
}
void CollectionCreateJob::setContentTypes(const QStringList & contentTypes)
{
d->contentTypes = contentTypes;
}
Collection CollectionCreateJob::collection() const
{
return d->collection;
......@@ -86,50 +73,18 @@ Collection CollectionCreateJob::collection() const
void CollectionCreateJob::doHandleResponse(const QByteArray & tag, const QByteArray & data)
{
if ( tag == "*" ) {
// TODO: share the parsing code with CollectionListJob
int pos = 0;
// collection and parent id
int colId = -1;
bool ok = false;
pos = ImapParser::parseNumber( data, colId, &ok, pos );
if ( !ok || colId <= 0 ) {
kDebug( 5250 ) << "Could not parse collection id from response:" << data;
Collection col;
ProtocolHelper::parseCollection( data, col );
if ( !col.isValid() )
return;
}
int parentId = -1;
pos = ImapParser::parseNumber( data, parentId, &ok, pos );
if ( !ok || parentId < 0 ) {
kDebug( 5250 ) << "Could not parse parent id from response:" << data;
return;
}
d->collection = Collection( colId );
d->collection.setParent( parentId );
d->collection.setName( d->name );
d->collection.setRemoteId( d->remoteId );
} else
col.setParent( d->collection.parent() );
col.setName( d->collection.name() );
col.setRemoteId( d->collection.remoteId() );
d->collection = col;
} else {
Job::doHandleResponse( tag, data );
}
void CollectionCreateJob::setRemoteId(const QString & remoteId)
{
d->remoteId = remoteId;
}
void CollectionCreateJob::setAttribute(CollectionAttribute * attr)
{
Q_ASSERT( !attr->type().isEmpty() );
QByteArray value = ImapParser::quote( attr->toByteArray() );
d->attributes.append( qMakePair( attr->type(), value ) );
}
void CollectionCreateJob::setCachePolicy( const CachePolicy &policy )
{
d->cachePolicy = policy;
}
}
#include "collectioncreatejob.moc"
......@@ -30,8 +30,6 @@ class CollectionCreateJobPrivate;
/**
Job to create collections.
@todo Support setting the remote id.
*/
class AKONADI_EXPORT CollectionCreateJob : public Job
{
......@@ -39,46 +37,21 @@ class AKONADI_EXPORT CollectionCreateJob : public Job
public:
/**
Create a new CollectionCreateJob job.
@param parentCollection The parent collection.
@param name The collection name
@param collection The new collection.
@param parent The parent object.
*/
CollectionCreateJob( const Collection &parentCollection, const QString &name, QObject *parent = 0 );
CollectionCreateJob( const Collection &collection, QObject *parent = 0 );
/**
Destroys this job.
*/
virtual ~CollectionCreateJob();
/**
Set allowed content mimetypes of the newly created collection.
@param contentTypes The allowed content types of the new collection.
*/
void setContentTypes( const QStringList &contentTypes );
/**
Sets the remote id of the collection.
@param remoteId The remote identifier of the collection
*/
void setRemoteId( const QString &remoteId );
/**
Returns the created collection if the job was executed succesfull.
*/
Collection collection() const;
/**
Sets the given collection attribute.
@param attr A collection attribute, ownership stays with the caller.
*/
void setAttribute( CollectionAttribute* attr );
/**
Sets the cache policy.
@param policy The cache policy. Inherting from parent is default.
*/
void setCachePolicy( const CachePolicy &policy );
protected:
virtual void doStart();
virtual void doHandleResponse( const QByteArray &tag, const QByteArray &data );
......
......@@ -134,70 +134,10 @@ void CollectionListJob::doStart()
void CollectionListJob::doHandleResponse( const QByteArray & tag, const QByteArray & data )
{
if ( tag == "*" ) {
int pos = 0;
// collection and parent id
int colId = -1;
bool ok = false;
pos = ImapParser::parseNumber( data, colId, &ok, pos );
if ( !ok || colId <= 0 ) {
kDebug( 5250 ) << "Could not parse collection id from response:" << data;
Collection collection;
ProtocolHelper::parseCollection( data, collection );
if ( !collection.isValid() )
return;
}
int parentId = -1;
pos = ImapParser::parseNumber( data, parentId, &ok, pos );
if ( !ok || parentId < 0 ) {
kDebug( 5250 ) << "Could not parse parent id from response:" << data;
return;
}
Collection collection( colId );
collection.setParent( parentId );
// attributes
QList<QByteArray> attributes;
pos = ImapParser::parseParenthesizedList( data, attributes, pos );
for ( int i = 0; i < attributes.count() - 1; i += 2 ) {
const QByteArray key = attributes.at( i );
const QByteArray value = attributes.at( i + 1 );
if ( key == "NAME" ) {
collection.setName( QString::fromUtf8( value ) );
} else if ( key == "REMOTEID" ) {
collection.setRemoteId( QString::fromUtf8( value ) );
} else if ( key == "RESOURCE" ) {
collection.setResource( QString::fromUtf8( value ) );
} else if ( key == "MIMETYPE" ) {
QList<QByteArray> ct;
ImapParser::parseParenthesizedList( value, ct );
QStringList ct2;
foreach ( const QByteArray b, ct )
ct2 << QString::fromLatin1( b );
collection.setContentTypes( ct2 );
} else if ( key == "CACHEPOLICY" ) {
CachePolicy policy;
ProtocolHelper::parseCachePolicy( value, policy );
collection.setCachePolicy( policy );
} else {
collection.addRawAttribute( key, value );
}
}
// determine collection type
if ( collection.parent() == Collection::root().id() ) {
if ( collection.resource() == QLatin1String( "akonadi_search_resource" ) )
collection.setType( Collection::VirtualParent );
else
collection.setType( Collection::Resource );
} else if ( collection.resource() == QLatin1String( "akonadi_search_resource" ) ) {
collection.setType( Collection::Virtual );
} else if ( collection.contentTypes().isEmpty() ) {
collection.setType( Collection::Structural );
} else {
collection.setType( Collection::Folder );
}
d->collections.append( collection );
d->pendingCollections.append( collection );
......
......@@ -185,12 +185,9 @@ void CollectionSync::slotLocalCreateDone(KJob * job)
void CollectionSync::createLocalCollection(const Collection & c, const Collection & parent)
{
d->pendingJobs++;
CollectionCreateJob *create = new CollectionCreateJob( parent, c.name(), this );
create->setRemoteId( c.remoteId() );
create->setContentTypes( c.contentTypes() );
create->setCachePolicy( c.cachePolicy() );
foreach ( CollectionAttribute *attr, c.attributes() )
create->setAttribute( attr );
Collection col( c );
col.setParent( parent );
CollectionCreateJob *create = new CollectionCreateJob( col, this );
connect( create, SIGNAL(result(KJob*)), SLOT(slotLocalCreateDone(KJob*)) );
}
......
......@@ -106,7 +106,10 @@ void CollectionView::Private::createCollection()
if ( parentId <= 0 )
return;
CollectionCreateJob *job = new CollectionCreateJob( Collection( parentId ), name );
Collection col;
col.setName( name );
col.setParent( parentId );
CollectionCreateJob *job = new CollectionCreateJob( col );
mParent->connect( job, SIGNAL(result(KJob*)), mParent, SLOT(createResult(KJob*)) );
}
......
......@@ -21,6 +21,8 @@
#include "imapparser.h"
#include <kdebug.h>
using namespace Akonadi;
int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start)
......@@ -66,3 +68,73 @@ QByteArray ProtocolHelper::cachePolicyToByteArray(const CachePolicy & policy)
rv += ")";
return rv;
}
int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start)
{
int pos = start;
// collection and parent id
int colId = -1;
bool ok = false;
pos = ImapParser::parseNumber( data, colId, &ok, pos );
if ( !ok || colId <= 0 ) {
kDebug( 5250 ) << "Could not parse collection id from response:" << data;
return start;
}
int parentId = -1;
pos = ImapParser::parseNumber( data, parentId, &ok, pos );
if ( !ok || parentId < 0 ) {
kDebug( 5250 ) << "Could not parse parent id from response:" << data;
return start;
}
collection = Collection( colId );
collection.setParent( parentId );
// attributes
QList<QByteArray> attributes;
pos = ImapParser::parseParenthesizedList( data, attributes, pos );
for ( int i = 0; i < attributes.count() - 1; i += 2 ) {
const QByteArray key = attributes.at( i );
const QByteArray value = attributes.at( i + 1 );
if ( key == "NAME" ) {
collection.setName( QString::fromUtf8( value ) );
} else if ( key == "REMOTEID" ) {
collection.setRemoteId( QString::fromUtf8( value ) );
} else if ( key == "RESOURCE" ) {
collection.setResource( QString::fromUtf8( value ) );
} else if ( key == "MIMETYPE" ) {
QList<QByteArray> ct;
ImapParser::parseParenthesizedList( value, ct );
QStringList ct2;
foreach ( const QByteArray b, ct )
ct2 << QString::fromLatin1( b );
collection.setContentTypes( ct2 );
} else if ( key == "CACHEPOLICY" ) {
CachePolicy policy;
ProtocolHelper::parseCachePolicy( value, policy );
collection.setCachePolicy( policy );
} else {
collection.addRawAttribute( key, value );
}
}
// determine collection type
if ( collection.parent() == Collection::root().id() ) {
if ( collection.resource() == QLatin1String( "akonadi_search_resource" ) )
collection.setType( Collection::VirtualParent );
else
collection.setType( Collection::Resource );
} else if ( collection.resource() == QLatin1String( "akonadi_search_resource" ) ) {
collection.setType( Collection::Virtual );
} else if ( collection.contentTypes().isEmpty() ) {
collection.setType( Collection::Structural );
} else {
collection.setType( Collection::Folder );
}
return pos;
}
......@@ -21,6 +21,7 @@
#define AKONADI_PROTOCOLHELPER_H
#include <libakonadi/cachepolicy.h>
#include <libakonadi/collection.h>
namespace Akonadi {
......@@ -30,6 +31,7 @@ namespace Akonadi {
representation.
@todo Add unit tests for this.
@todo Use exceptions for a useful error handling
*/
class ProtocolHelper
{
......@@ -39,6 +41,7 @@ class ProtocolHelper
@param data The input data.
@param policy The parsed cache policy.
@param start Start of the data, ie. postion after the label
@returns Position in data after the cache policy description.
*/
static int parseCachePolicy( const QByteArray &data, CachePolicy &policy, int start = 0 );
......@@ -47,6 +50,15 @@ class ProtocolHelper
*/
static QByteArray cachePolicyToByteArray( const CachePolicy &policy );
/**
Parse a collection description.
@param data The input data.
@param collection The parsed collection
@param start Start of the data
@returns Position in data after the collection description.
*/
static int parseCollection( const QByteArray &data, Collection &collection, int start = 0 );
};
}
......
......@@ -82,18 +82,18 @@ void CollectionAttributeTest::testAttributes()
// add a custom attribute
TestAttribute *attr = new TestAttribute();
attr->setData( attr1 );
CollectionCreateJob *create = new CollectionCreateJob( Collection( parentColId ), "attribute test", this );
create->setAttribute( attr );
delete attr;
Collection col;
col.setName( "attribute test" );
col.setParent( parentColId );
col.addAttribute( attr );
CollectionCreateJob *create = new CollectionCreateJob( col, this );
QVERIFY( create->exec() );
Collection col = create->collection();
col = create->collection();
QVERIFY( col.isValid() );
attr = col.attribute<TestAttribute>();
#if 0
QVERIFY( attr != 0 );
QCOMPARE( attr->toByteArray(), QByteArray( attr1 ) );
#endif
CollectionListJob *list = new CollectionListJob( col, CollectionListJob::Local, this );
QVERIFY( list->exec() );
......
......@@ -40,8 +40,10 @@ CollectionCreator::CollectionCreator( )
int root = resolver->collection();
startTime.start();
for ( int i = 0; i < COLLECTION_COUNT; ++i ) {
CollectionCreateJob *job = new CollectionCreateJob( Collection( root ),
QLatin1String("col") + QString::number( i ), this );
Collection col;
col.setParent( root );
col.setName( QLatin1String("col") + QString::number( i ) );
CollectionCreateJob *job = new CollectionCreateJob( col, this );
connect( job, SIGNAL(result(KJob*)), SLOT(done(KJob*)) );
++jobCount;
}
......
......@@ -215,102 +215,103 @@ void CollectionJobTest::testResourceFolderList()
QVERIFY( findCol( list, "bla" ).isValid() );
}
void CollectionJobTest::testIllegalCreateFolder( )
void CollectionJobTest::testCreateDeleteFolder_data()
{
// empty
CollectionCreateJob *job = new CollectionCreateJob( Collection::root(), QString(), this );
QVERIFY( !job->exec() );
// search folder
job = new CollectionCreateJob( Collection( searchColId ), "New Folder", this );
QVERIFY( !job->exec() );
// already existing folder
job = new CollectionCreateJob( Collection( res2ColId ), "foo2", this );
QVERIFY( !job->exec() );
// Parent folder with \Noinferiors flag
CollectionPathResolver *resolver = new CollectionPathResolver( "res2/foo2", this );
QVERIFY( resolver->exec() );
job = new CollectionCreateJob( Collection( resolver->collection() ), "new folder", this );
QVERIFY( !job->exec() );
// folder with missing parents
job = new CollectionCreateJob( Collection( INT_MAX ), "sub1", this );
QVERIFY( !job->exec() );
}
void CollectionJobTest::testCreateDeleteFolder( )
{
// simple new folder
CollectionCreateJob *job = new CollectionCreateJob( Collection( res3ColId ), "new folder", this );
QVERIFY( job->exec() );
Collection newCol = job->collection();
QVERIFY( newCol.isValid() );
QCOMPARE( newCol.parent(), res3ColId );
QCOMPARE( newCol.name(), QString( "new folder" ) );
CollectionListJob *ljob = new CollectionListJob( Collection( res3ColId ), CollectionListJob::Flat, this );
QVERIFY( ljob->exec() );
QCOMPARE( findCol( ljob->collections(), "new folder" ), newCol );
CollectionDeleteJob *del = new CollectionDeleteJob( newCol, this );
QVERIFY( del->exec() );
ljob = new CollectionListJob( Collection( res3ColId ), CollectionListJob::Flat, this );
QVERIFY( ljob->exec() );
QVERIFY( !findCol( ljob->collections(), "new folder" ).isValid() );
// folder that already exists within another resource
job = new CollectionCreateJob( Collection( res3ColId ), "foo", this );
QVERIFY( job->exec() );
newCol = job->collection();
QVERIFY( newCol.isValid() );
QTest::addColumn<Collection>("collection");
QTest::addColumn<bool>("creatable");
ljob = new CollectionListJob( Collection( res3ColId ), CollectionListJob::Flat, this );
QVERIFY( ljob->exec() );
QCOMPARE( findCol( ljob->collections(), "foo" ), newCol );
del = new CollectionDeleteJob( newCol, this );
QVERIFY( del->exec() );
Collection col;
QTest::newRow("empty") << col << false;
col.setName( "new folder" );
col.setParent( res3ColId );
QTest::newRow("simple") << col << true;
ljob = new CollectionListJob( Collection( res3ColId ), CollectionListJob::Flat, this );
QVERIFY( ljob->exec() );
QVERIFY( !findCol( ljob->collections(), "res3/foo" ).isValid() );
col.setParent( res3ColId );
col.setName( "foo" );
QTest::newRow( "existing in different resource" ) << col << true;
// folder with attributes
job = new CollectionCreateJob( Collection( res3ColId ), "mail folder", this );
col.setName( "mail folder" );
QStringList mimeTypes;
mimeTypes << "inode/directory" << "message/rfc822";
job->setContentTypes( mimeTypes );
job->setRemoteId( "remote id" );
col.setContentTypes( mimeTypes );
col.setRemoteId( "remote id" );
CachePolicy policy;
policy.setInheritFromParent( false );
policy.setIntervalCheckTime( 60 );
policy.setLocalParts( QStringList( Item::PartEnvelope ) );
policy.enableSyncOnDemand( true );
policy.setCacheTimeout( 120 );
job->setCachePolicy( policy );
QVERIFY( job->exec() );
newCol = job->collection();
QVERIFY( newCol.isValid() );
col.setCachePolicy( policy );
QTest::newRow( "complex" ) << col << true;
CollectionListJob *list = new CollectionListJob( newCol, CollectionListJob::Local, this );
QVERIFY( list->exec() );
QCOMPARE( list->collections().count(), 1 );
Collection col = list->collections().first();
compareLists( col.contentTypes(), mimeTypes );
QCOMPARE( col.remoteId(), QString("remote id") );
QCOMPARE( col.resource(), QString("akonadi_dummy_resource_3") );
QCOMPARE( col.cachePolicy().inheritFromParent(), false );
QCOMPARE( col.cachePolicy().intervalCheckTime(), 60 );
QCOMPARE( col.cachePolicy().localParts(), QStringList( Item::PartEnvelope ) );
QCOMPARE( col.cachePolicy().cacheTimeout(), 120 );
QCOMPARE( col.cachePolicy().syncOnDemand(), true );
col = Collection();
col.setName( "New Folder" );
col.setParent( searchColId );
QTest::newRow( "search folder" ) << col << false;
// cleanup
del = new CollectionDeleteJob( newCol, this );
QVERIFY( del->exec() );
col.setParent( res2ColId );
col.setName( "foo2" );
QTest::newRow( "already existing" ) << col << false;
CollectionPathResolver *resolver = new CollectionPathResolver( "res2/foo2", this );
QVERIFY( resolver->exec() );