/* Copyright (c) 2009 Volker Krause 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 #include #include #include #include #include #include #include #include using namespace Akonadi; template QTextStream& operator<<( QTextStream &s, const QSet &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( 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( 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( 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( 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; }