mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 18:32:51 +00:00
428 lines
13 KiB
C++
428 lines
13 KiB
C++
/*
|
|
Copyright (c) 2009 Volker Krause <vkrause@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
#include "xmloperations.h"
|
|
#include "global.h"
|
|
#include "test.h"
|
|
|
|
#include <akonadi/collectionfetchjob.h>
|
|
#include <akonadi/collectionfetchscope.h>
|
|
#include <akonadi/itemfetchjob.h>
|
|
#include <akonadi/itemfetchscope.h>
|
|
#include <akonadi/xml/xmlwritejob.h>
|
|
|
|
#include <KDebug>
|
|
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QStringList>
|
|
|
|
using namespace Akonadi;
|
|
|
|
template <typename T> QTextStream& operator<<( QTextStream &s, const QSet<T> &set )
|
|
{
|
|
s << '{';
|
|
foreach ( const T &element, set )
|
|
s << element << ", ";
|
|
s << '}';
|
|
return s;
|
|
}
|
|
|
|
QTextStream& operator<<( QTextStream &s, const QStringList &list )
|
|
{
|
|
s << '(' << list.join( ", " ) << ')';
|
|
return s;
|
|
}
|
|
|
|
XmlOperations::XmlOperations(QObject* parent) :
|
|
QObject( parent ),
|
|
mCollectionFields( 0xFF ),
|
|
mCollectionKey( RemoteId ),
|
|
mItemFields( 0xFF ),
|
|
mItemKey( ItemRemoteId ),
|
|
mNormalizeRemoteIds( false )
|
|
{
|
|
}
|
|
|
|
XmlOperations::~XmlOperations()
|
|
{
|
|
}
|
|
|
|
Item XmlOperations::getItemByRemoteId(const QString& rid)
|
|
{
|
|
return mDocument.itemByRemoteId( rid, true);
|
|
}
|
|
|
|
Collection XmlOperations::getCollectionByRemoteId(const QString& rid)
|
|
{
|
|
return mDocument.collectionByRemoteId(rid);
|
|
}
|
|
|
|
void XmlOperations::setRootCollections(const QString& resourceId)
|
|
{
|
|
CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this );
|
|
job->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
|
|
job->fetchScope().setResource( resourceId );
|
|
if ( job->exec() )
|
|
setRootCollections( job->collections() );
|
|
else
|
|
mErrorMsg = job->errorText();
|
|
}
|
|
|
|
void XmlOperations::setRootCollections(const Collection::List& roots)
|
|
{
|
|
mRoots = roots;
|
|
}
|
|
|
|
void XmlOperations::setXmlFile(const QString& fileName)
|
|
{
|
|
mFileName = fileName;
|
|
if ( QFileInfo( fileName ).isAbsolute() )
|
|
mDocument.loadFile( fileName );
|
|
else
|
|
mDocument.loadFile( Global::basePath() + QDir::separator() + fileName );
|
|
}
|
|
|
|
QString XmlOperations::lastError() const
|
|
{
|
|
return mErrorMsg;
|
|
}
|
|
|
|
void XmlOperations::setCollectionKey(XmlOperations::CollectionField field)
|
|
{
|
|
mCollectionKey = field;
|
|
}
|
|
|
|
void XmlOperations::setCollectionKey(const QString& fieldName)
|
|
{
|
|
const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) );
|
|
setCollectionKey( static_cast<CollectionField>( me.keyToValue( fieldName.toLatin1() ) ) );
|
|
}
|
|
|
|
void XmlOperations::ignoreCollectionField(XmlOperations::CollectionField field)
|
|
{
|
|
mCollectionFields = mCollectionFields & ~field;
|
|
}
|
|
|
|
void XmlOperations::ignoreCollectionField(const QString& fieldName)
|
|
{
|
|
const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "CollectionField" ) );
|
|
ignoreCollectionField( static_cast<CollectionField>( me.keyToValue( fieldName.toLatin1() ) ) );
|
|
}
|
|
|
|
void XmlOperations::setItemKey(XmlOperations::ItemField field)
|
|
{
|
|
mItemKey = field;
|
|
}
|
|
|
|
void XmlOperations::setItemKey(const QString& _fieldName)
|
|
{
|
|
QString fieldName = _fieldName;
|
|
if ( !fieldName.startsWith( QLatin1String ( "Item" ) ) )
|
|
fieldName.prepend( "Item" );
|
|
const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "ItemField" ) );
|
|
setItemKey( static_cast<ItemField>( me.keyToValue( fieldName.toLatin1() ) ) );
|
|
}
|
|
|
|
void XmlOperations::ignoreItemField(XmlOperations::ItemField field)
|
|
{
|
|
mItemFields = mItemFields & ~field;
|
|
}
|
|
|
|
void XmlOperations::ignoreItemField(const QString& _fieldName)
|
|
{
|
|
QString fieldName = _fieldName;
|
|
if ( !fieldName.startsWith( "Item" ) )
|
|
fieldName.prepend( "Item" );
|
|
const QMetaEnum me = metaObject()->enumerator( metaObject()->indexOfEnumerator( "ItemField" ) );
|
|
ignoreItemField( static_cast<ItemField>( me.keyToValue( fieldName.toLatin1() ) ) );
|
|
}
|
|
|
|
void XmlOperations::setNormalizeRemoteIds(bool enable)
|
|
{
|
|
mNormalizeRemoteIds = enable;
|
|
}
|
|
|
|
bool XmlOperations::compare()
|
|
{
|
|
if ( !mDocument.isValid() ) {
|
|
mErrorMsg = mDocument.lastError();
|
|
return false;
|
|
}
|
|
|
|
if ( mRoots.isEmpty() ) {
|
|
if ( !mErrorMsg.isEmpty() )
|
|
mErrorMsg = QLatin1String( "No root collections specified." );
|
|
return false;
|
|
}
|
|
|
|
const Collection::List docRoots = mDocument.childCollections( Collection::root() );
|
|
if ( compareCollections( mRoots, docRoots ) )
|
|
return true;
|
|
|
|
XmlWriteJob *xmlJob = new XmlWriteJob( mRoots, mFileName + ".actual", this );
|
|
if ( !xmlJob->exec() )
|
|
kError() << xmlJob->errorText();
|
|
return false;
|
|
}
|
|
|
|
void XmlOperations::assertEqual()
|
|
{
|
|
if ( !compare() )
|
|
Test::instance()->fail( lastError() );
|
|
}
|
|
|
|
static QString normalizeRemoteId( const QString &in )
|
|
{
|
|
QString out( in );
|
|
if ( in.startsWith( Global:: basePath() ) ) {
|
|
out = in.mid( Global::basePath().length() );
|
|
if ( out.startsWith( QDir::separator() ) )
|
|
out = out.mid( 1 );
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Collection XmlOperations::normalizeCollection( const Collection &in ) const
|
|
{
|
|
Collection out( in );
|
|
if ( mNormalizeRemoteIds )
|
|
out.setRemoteId( normalizeRemoteId( in.remoteId() ) );
|
|
QStringList l = in.contentMimeTypes();
|
|
std::sort( l.begin(), l.end() );
|
|
out.setContentMimeTypes( l );
|
|
return out;
|
|
}
|
|
|
|
Item XmlOperations::normalizeItem( const Item& in ) const
|
|
{
|
|
Item out( in );
|
|
if ( mNormalizeRemoteIds )
|
|
out.setRemoteId( normalizeRemoteId( in.remoteId() ) );
|
|
return out;
|
|
}
|
|
|
|
bool XmlOperations::compareCollections(const Collection::List& _cols, const Collection::List& _refCols)
|
|
{
|
|
Collection::List cols;
|
|
foreach ( const Collection &c, _cols )
|
|
cols.append( normalizeCollection( c ) );
|
|
Collection::List refCols;
|
|
foreach ( const Collection &c, _refCols )
|
|
refCols.append( normalizeCollection( c ) );
|
|
|
|
switch ( mCollectionKey ) {
|
|
case RemoteId:
|
|
sortEntityList( cols, &Collection::remoteId );
|
|
sortEntityList( refCols, &Collection::remoteId );
|
|
break;
|
|
case Name:
|
|
sortEntityList( cols, &Collection::name );
|
|
sortEntityList( refCols, &Collection::name );
|
|
break;
|
|
case None:
|
|
break;
|
|
default:
|
|
Q_ASSERT( false );
|
|
}
|
|
|
|
for ( int i = 0; i < cols.count(); ++i ) {
|
|
const Collection col = cols.at( i );
|
|
if ( refCols.count() <= i ) {
|
|
mErrorMsg = QString::fromLatin1( "Additional collection with remote id '%1' was found." ).arg( col.remoteId() );
|
|
return false;
|
|
}
|
|
|
|
const Collection refCol = refCols.at( i );
|
|
if ( !compareCollection( col, refCol ) )
|
|
return false;
|
|
}
|
|
|
|
if ( refCols.count() != cols.count() ) {
|
|
const Collection refCol = refCols.at( cols.count() );
|
|
mErrorMsg = QString::fromLatin1( "Collection with remote id '%1' is missing." ).arg( refCol.remoteId() );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XmlOperations::compareCollection(const Collection& col, const Collection& refCol)
|
|
{
|
|
// compare the two collections
|
|
if ( !compareValue( col, refCol, &Collection::remoteId, RemoteId ) ||
|
|
!compareValue( col, refCol, &Collection::contentMimeTypes, ContentMimeType ) ||
|
|
!compareValue( col, refCol, &Collection::name, Name ) )
|
|
return false;
|
|
|
|
if ( (mCollectionFields & Attributes) && !compareAttributes( col, refCol ) )
|
|
return false;
|
|
|
|
// compare child items
|
|
ItemFetchJob *ijob = new ItemFetchJob( col, this );
|
|
ijob->fetchScope().fetchAllAttributes( true );
|
|
ijob->fetchScope().fetchFullPayload( true );
|
|
if ( !ijob->exec() ) {
|
|
mErrorMsg = ijob->errorText();
|
|
return false;
|
|
}
|
|
const Item::List items = ijob->items();
|
|
const Item::List refItems = mDocument.items( refCol, true );
|
|
if ( !compareItems( items, refItems ) )
|
|
return false;
|
|
|
|
// compare child collections
|
|
CollectionFetchJob *cjob = new CollectionFetchJob( col, CollectionFetchJob::FirstLevel, this );
|
|
cjob->fetchScope().setAncestorRetrieval( CollectionFetchScope::All );
|
|
if ( !cjob->exec() ) {
|
|
mErrorMsg = cjob->errorText();
|
|
return false;
|
|
}
|
|
const Collection::List cols = cjob->collections();
|
|
const Collection::List refCols = mDocument.childCollections( refCol );
|
|
return compareCollections( cols, refCols );
|
|
}
|
|
|
|
bool XmlOperations::hasItem(const Item& _item, const Collection& _col)
|
|
{
|
|
ItemFetchJob *ijob = new ItemFetchJob( _col, this );
|
|
ijob->fetchScope().fetchAllAttributes( true );
|
|
ijob->fetchScope().fetchFullPayload( true );
|
|
if ( !ijob->exec() ) {
|
|
mErrorMsg = ijob->errorText();
|
|
return false;
|
|
}
|
|
const Item::List items = ijob->items();
|
|
|
|
for( int i = 0; i < items.count(); ++i) {
|
|
if( _item == items.at(i) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool XmlOperations::hasItem(const Item& _item, const QString& rid)
|
|
{
|
|
CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this );
|
|
Collection::List colist;
|
|
|
|
if ( job->exec() )
|
|
colist = job->collections() ;
|
|
foreach( const Collection &collection, colist ) {
|
|
if(rid == collection.remoteId()) {
|
|
return hasItem(_item, collection);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool XmlOperations::compareItems(const Item::List& _items, const Item::List& _refItems)
|
|
{
|
|
Item::List items;
|
|
foreach ( const Item &i, _items )
|
|
items.append( normalizeItem( i ) );
|
|
Item::List refItems;
|
|
foreach ( const Item &i, _refItems )
|
|
refItems.append( normalizeItem( i ) );
|
|
|
|
switch ( mItemKey ) {
|
|
case ItemRemoteId:
|
|
sortEntityList( items, &Item::remoteId );
|
|
sortEntityList( refItems, &Item::remoteId );
|
|
break;
|
|
case ItemNone:
|
|
break;
|
|
default:
|
|
Q_ASSERT( false );
|
|
}
|
|
|
|
for ( int i = 0; i < items.count(); ++i ) {
|
|
const Item item = items.at( i );
|
|
if ( refItems.count() <= i ) {
|
|
mErrorMsg = QString::fromLatin1( "Additional item with remote id '%1' was found." ).arg( item.remoteId() );
|
|
return false;
|
|
}
|
|
|
|
const Item refItem = refItems.at( i );
|
|
if ( !compareItem( item, refItem ) )
|
|
return false;
|
|
}
|
|
|
|
if ( refItems.count() != items.count() ) {
|
|
const Item refItem = refItems.at( items.count() );
|
|
mErrorMsg = QString::fromLatin1( "Item with remote id '%1' is missing." ).arg( refItem.remoteId() );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool XmlOperations::compareItem(const Item& item, const Item& refItem)
|
|
{
|
|
if ( !compareValue( item, refItem, &Item::remoteId, ItemRemoteId ) ||
|
|
!compareValue( item, refItem, &Item::mimeType, ItemMimeType ) ||
|
|
!compareValue( item, refItem, &Item::flags, ItemFlags ) ||
|
|
!compareValue( item, refItem, &Item::payloadData, ItemPayload ) )
|
|
return false;
|
|
|
|
return compareAttributes( item, refItem );
|
|
}
|
|
|
|
bool XmlOperations::compareAttributes(const Entity& entity, const Entity& refEntity)
|
|
{
|
|
Attribute::List attrs = entity.attributes();
|
|
Attribute::List refAttrs = refEntity.attributes();
|
|
std::sort( attrs.begin(), attrs.end(), boost::bind( &Attribute::type, _1 ) < boost::bind( &Attribute::type, _2 ) );
|
|
std::sort( refAttrs.begin(), refAttrs.end(), boost::bind( &Attribute::type, _1 ) < boost::bind( &Attribute::type, _2 ) );
|
|
|
|
for ( int i = 0; i < attrs.count(); ++i ) {
|
|
Attribute* attr = attrs.at( i );
|
|
if ( refAttrs.count() <= i ) {
|
|
mErrorMsg = QString::fromLatin1( "Additional attribute of type '%1' for object with remote id '%2' was found." )
|
|
.arg( QString::fromLatin1( attr->type() ) ).arg( entity.remoteId() );
|
|
return false;
|
|
}
|
|
|
|
Attribute* refAttr = refAttrs.at( i );
|
|
if ( attr->type() != refAttr->type() ) {
|
|
mErrorMsg = QString::fromLatin1( "Object with remote id '%1' misses attribute of type '%2'." )
|
|
.arg( entity.remoteId() ).arg( QString::fromLatin1( refAttr->type() ) );
|
|
return false;
|
|
}
|
|
|
|
bool result = compareValue( attr->serialized(), refAttr->serialized() );
|
|
if ( !result ) {
|
|
mErrorMsg.prepend( QString::fromLatin1( "Object with remote id '%1' differs in attribute '%2':\n" )
|
|
.arg( entity.remoteId() ).arg( QString::fromLatin1( attr->type() ) ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( refAttrs.count() != attrs.count() ) {
|
|
Attribute* refAttr = refAttrs.at( attrs.count() );
|
|
mErrorMsg = QString::fromLatin1( "Object with remote id '%1' misses attribute of type '%2'." )
|
|
.arg( entity.remoteId() ).arg( QString::fromLatin1( refAttr->type() ) );;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|