diff --git a/akonadi/itemfetchjob.cpp b/akonadi/itemfetchjob.cpp index 9f6345691..e4cecea5b 100644 --- a/akonadi/itemfetchjob.cpp +++ b/akonadi/itemfetchjob.cpp @@ -1,326 +1,340 @@ /* Copyright (c) 2006 - 2007 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 "itemfetchjob.h" #include "attributefactory.h" #include "collection.h" #include "collectionselectjob_p.h" #include "imapparser_p.h" #include "itemfetchscope.h" #include "itemserializer_p.h" #include "itemserializerplugin.h" #include "job_p.h" #include "entity_p.h" #include "protocol_p.h" #include "protocolhelper_p.h" #include #include #include #include #include using namespace Akonadi; class Akonadi::ItemFetchJobPrivate : public JobPrivate { public: ItemFetchJobPrivate( ItemFetchJob *parent ) : JobPrivate( parent ) { } void timeout() { Q_Q( ItemFetchJob ); mEmitTimer->stop(); // in case we are called by result() if ( !mPendingItems.isEmpty() ) { emit q->itemsReceived( mPendingItems ); mPendingItems.clear(); } } void startFetchJob(); void selectDone( KJob * job ); Q_DECLARE_PUBLIC( ItemFetchJob ) Collection mCollection; Item mItem; Item::List mItems; ItemFetchScope mFetchScope; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer* mEmitTimer; }; void ItemFetchJobPrivate::startFetchJob() { QByteArray command = newTag(); if ( mItem.isValid() ) command += " " AKONADI_CMD_UID " " AKONADI_CMD_ITEMFETCH " " + QByteArray::number( mItem.id() ); else if ( !mItem.remoteId().isEmpty() ) command += " " AKONADI_CMD_RID " " AKONADI_CMD_ITEMFETCH " " + mItem.remoteId().toUtf8(); else command += " " AKONADI_CMD_ITEMFETCH " 1:*"; if ( mFetchScope.fullPayload() ) command += " " AKONADI_PARAM_FULLPAYLOAD; if ( mFetchScope.allAttributes() ) command += " " AKONADI_PARAM_ALLATTRIBUTES; if ( mFetchScope.cacheOnly() ) command += " " AKONADI_PARAM_CACHEONLY; + if ( mFetchScope.ancestorRetrieval() != ItemFetchScope::None ) { + switch ( mFetchScope.ancestorRetrieval() ) { + case ItemFetchScope::Parent: + command += " ANCESTORS 1"; + break; + case ItemFetchScope::All: + command += " ANCESTORS INF"; + break; + default: + Q_ASSERT( false ); + } + } //TODO: detect somehow if server supports external payload attribute command += " " AKONADI_PARAM_EXTERNALPAYLOAD; command += " (UID REMOTEID COLLECTIONID FLAGS SIZE DATETIME"; foreach ( const QByteArray &part, mFetchScope.payloadParts() ) command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part ); foreach ( const QByteArray &part, mFetchScope.attributes() ) command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part ); command += ")\n"; writeData( command ); } void ItemFetchJobPrivate::selectDone( KJob * job ) { if ( !job->error() ) // the collection is now selected, fetch the message(s) startFetchJob(); } ItemFetchJob::ItemFetchJob( const Collection &collection, QObject * parent ) : Job( new ItemFetchJobPrivate( this ), parent ) { Q_D( ItemFetchJob ); d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); d->mCollection = collection; } ItemFetchJob::ItemFetchJob( const Item & item, QObject * parent) : Job( new ItemFetchJobPrivate( this ), parent ) { Q_D( ItemFetchJob ); d->mEmitTimer = new QTimer( this ); d->mEmitTimer->setSingleShot( true ); d->mEmitTimer->setInterval( 100 ); connect( d->mEmitTimer, SIGNAL(timeout()), this, SLOT(timeout()) ); connect( this, SIGNAL(result(KJob*)), this, SLOT(timeout()) ); d->mCollection = Collection::root(); d->mItem = item; } ItemFetchJob::~ItemFetchJob() { } void ItemFetchJob::doStart() { Q_D( ItemFetchJob ); if ( !d->mItem.isValid() ) { // collection content listing if ( d->mCollection == Collection::root() ) { setErrorText( QLatin1String("Cannot list root collection.") ); setError( Unknown ); emitResult(); } CollectionSelectJob *job = new CollectionSelectJob( d->mCollection, this ); connect( job, SIGNAL(result(KJob*)), SLOT(selectDone(KJob*)) ); addSubjob( job ); } else d->startFetchJob(); } void ItemFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) { Q_D( ItemFetchJob ); if ( tag == "*" ) { int begin = data.indexOf( "FETCH" ); if ( begin >= 0 ) { // split fetch response into key/value pairs QList fetchResponse; ImapParser::parseParenthesizedList( data, fetchResponse, begin + 6 ); // create a new item object Item::Id uid = -1; int rev = -1; QString rid; QString mimeType; Entity::Id cid = -1; for ( int i = 0; i < fetchResponse.count() - 1; i += 2 ) { const QByteArray key = fetchResponse.value( i ); const QByteArray value = fetchResponse.value( i + 1 ); if ( key == "UID" ) uid = value.toLongLong(); else if ( key == "REV" ) rev = value.toInt(); else if ( key == "REMOTEID" ) { if ( !value.isEmpty() ) rid = QString::fromUtf8( value ); else rid.clear(); } else if ( key == "COLLECTIONID" ) { cid = value.toInt(); } else if ( key == "MIMETYPE" ) mimeType = QString::fromLatin1( value ); } if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) { kWarning() << "Broken fetch response: UID, RID, REV or MIMETYPE missing!"; return; } Item item( uid ); item.setRemoteId( rid ); item.setRevision( rev ); item.setMimeType( mimeType ); item.setStorageCollectionId( cid ); if ( !item.isValid() ) return; // parse fetch response fields for ( int i = 0; i < fetchResponse.count() - 1; i += 2 ) { const QByteArray key = fetchResponse.value( i ); // skip stuff we dealt with already if ( key == "UID" || key == "REV" || key == "REMOTEID" || key == "MIMETYPE" || key == "COLLECTIONID") continue; // flags if ( key == "FLAGS" ) { QList flags; ImapParser::parseParenthesizedList( fetchResponse[i + 1], flags ); foreach ( const QByteArray &flag, flags ) { item.setFlag( flag ); } } else if ( key == "SIZE" ) { const quint64 size = fetchResponse[i + 1].toLongLong(); item.setSize( size ); } else if ( key == "DATETIME" ) { QDateTime datetime; ImapParser::parseDateTime( fetchResponse[i + 1], datetime ); item.setModificationTime( datetime ); + } else if ( key == "ANCESTORS" ) { + ProtocolHelper::parseAncestors( fetchResponse[i + 1], &item ); } else { int version = 0; QByteArray plainKey( key ); ProtocolHelper::PartNamespace ns; ImapParser::splitVersionedKey( key, plainKey, version ); plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns ); switch ( ns ) { case ProtocolHelper::PartPayload: { bool isExternal = false; QByteArray fileKey = fetchResponse.value( i + 1 ); if (fileKey == "[FILE]") { isExternal = true; i++; kDebug() << "Payload is external: " << isExternal << " filename: " << fetchResponse.value( i + 1 ); } ItemSerializer::deserialize( item, plainKey, fetchResponse.value( i + 1 ), version, isExternal ); break; } case ProtocolHelper::PartAttribute: { Attribute* attr = AttributeFactory::createAttribute( plainKey ); Q_ASSERT( attr ); if ( fetchResponse.value( i + 1 ) == "[FILE]" ) { ++i; QFile f( QString::fromUtf8( fetchResponse.value( i + 1 ) ) ); if ( f.open( QFile::ReadOnly ) ) attr->deserialize( f.readAll() ); else { kWarning() << "Failed to open attribute file: " << fetchResponse.value( i + 1 ); delete attr; } } else { attr->deserialize( fetchResponse.value( i + 1 ) ); } item.addAttribute( attr ); break; } case ProtocolHelper::PartGlobal: default: kWarning() << "Unknown item part type:" << key; } } } item.d_ptr->resetChangeLog(); d->mItems.append( item ); d->mPendingItems.append( item ); if ( !d->mEmitTimer->isActive() ) d->mEmitTimer->start(); return; } } kDebug() << "Unhandled response: " << tag << data; } Item::List ItemFetchJob::items() const { Q_D( const ItemFetchJob ); return d->mItems; } void ItemFetchJob::setFetchScope( ItemFetchScope &fetchScope ) { Q_D( ItemFetchJob ); d->mFetchScope = fetchScope; } void ItemFetchJob::setFetchScope( const ItemFetchScope &fetchScope ) { Q_D( ItemFetchJob ); d->mFetchScope = fetchScope; } ItemFetchScope &ItemFetchJob::fetchScope() { Q_D( ItemFetchJob ); return d->mFetchScope; } void ItemFetchJob::setCollection(const Akonadi::Collection& collection) { Q_D( ItemFetchJob ); d->mCollection = collection; } #include "itemfetchjob.moc" diff --git a/akonadi/itemfetchscope.cpp b/akonadi/itemfetchscope.cpp index 087b48e03..cdf3117fc 100644 --- a/akonadi/itemfetchscope.cpp +++ b/akonadi/itemfetchscope.cpp @@ -1,109 +1,119 @@ /* Copyright (c) 2008 Kevin Krammer 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 "itemfetchscope.h" #include "itemfetchscope_p.h" #include using namespace Akonadi; ItemFetchScope::ItemFetchScope() { d = new ItemFetchScopePrivate(); } ItemFetchScope::ItemFetchScope( const ItemFetchScope &other ) : d( other.d ) { } ItemFetchScope::~ItemFetchScope() { } ItemFetchScope &ItemFetchScope::operator=( const ItemFetchScope &other ) { if ( &other != this ) d = other.d; return *this; } QSet< QByteArray > ItemFetchScope::payloadParts() const { return d->mPayloadParts; } void ItemFetchScope::fetchPayloadPart(const QByteArray & part, bool fetch) { if ( fetch ) d->mPayloadParts.insert( part ); else d->mPayloadParts.remove( part ); } bool ItemFetchScope::fullPayload() const { return d->mFullPayload; } void ItemFetchScope::fetchFullPayload(bool fetch) { d->mFullPayload = fetch; } QSet< QByteArray > ItemFetchScope::attributes() const { return d->mAttributes; } void ItemFetchScope::fetchAttribute(const QByteArray & type, bool fetch) { if ( fetch ) d->mAttributes.insert( type ); else d->mAttributes.remove( type ); } bool ItemFetchScope::allAttributes() const { return d->mAllAttributes; } void ItemFetchScope::fetchAllAttributes(bool fetch) { d->mAllAttributes = fetch; } bool ItemFetchScope::isEmpty() const { return d->mPayloadParts.isEmpty() && d->mAttributes.isEmpty() && !d->mFullPayload && !d->mAllAttributes; } bool ItemFetchScope::cacheOnly() const { return d->mCacheOnly; } void ItemFetchScope::setCacheOnly(bool cacheOnly) { d->mCacheOnly = cacheOnly; } + +ItemFetchScope::AncestorRetrieval ItemFetchScope::ancestorRetrieval() const +{ + return d->mAncestorDepth; +} + +void ItemFetchScope::setAncestorRetrieval( AncestorRetrieval depth ) +{ + d->mAncestorDepth = depth; +} diff --git a/akonadi/itemfetchscope.h b/akonadi/itemfetchscope.h index 4c2f6f836..fecc165ba 100644 --- a/akonadi/itemfetchscope.h +++ b/akonadi/itemfetchscope.h @@ -1,195 +1,221 @@ /* Copyright (c) 2008 Kevin Krammer 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. */ #ifndef ITEMFETCHSCOPE_H #define ITEMFETCHSCOPE_H #include "akonadi_export.h" #include class QStringList; template class QSet; namespace Akonadi { class ItemFetchScopePrivate; /** * @short Specifies which parts of an item should be fetched from the Akonadi storage. * * When items are fetched from server either by using ItemFetchJob explicitly or * when it is being used internally by other classes, e.g. ItemModel, the scope * of the fetch operation can be tailored to the application's current needs. * * There are two supported ways of changing the currently active ItemFetchScope * of classes: * - in-place: modify the ItemFetchScope object the other class holds as a member * - replace: replace the other class' member with a new scope object * * Example: modifying an ItemFetchJob's scope @c in-place * @code * Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( collection ); * job->fetchScope().fetchFullPayload(); * job->fetchScope().fetchAttribute(); * @endcode * * Example: @c replacing an ItemFetchJob's scope * @code * Akonadi::ItemFetchScope scope; * scope.fetchFullPayload(); * scope.fetchAttribute(); * * Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( collection ); * job->setFetchScope( scope ); * @endcode * * This class is implicitly shared. * * @author Kevin Krammer */ class AKONADI_EXPORT ItemFetchScope { public: + /** + * Describes the ancestor retrieval depth. + * @since 4.4 + */ + enum AncestorRetrieval { + None, ///< No ancestor retrieval at all (the default) + Parent, ///< Only retrieve the immediate parent collection + All ///< Retrieve all ancestors, up to Collection::root() + }; + /** * Creates an empty item fetch scope. * * Using an empty scope will only fetch the very basic meta data of items, * e.g. local id, remote id and mime type */ ItemFetchScope(); /** * Creates a new item fetch scope from an @p other. */ ItemFetchScope( const ItemFetchScope &other ); /** * Destroys the item fetch scope. */ ~ItemFetchScope(); /** * Assigns the @p other to this scope and returns a reference to this scope. */ ItemFetchScope &operator=( const ItemFetchScope &other ); /** * Returns the payload parts that should be fetched. * * @see fetchPayloadPart() */ QSet payloadParts() const; /** * Sets which payload parts shall be fetched. * * @param part The payload part identifier. * Valid values depend on the item type. * @param fetch @c true to fetch this part, @c false otherwise. */ void fetchPayloadPart( const QByteArray &part, bool fetch = true ); /** * Returns whether the full payload should be fetched. * * @see fetchFullPayload() */ bool fullPayload() const; /** * Sets whether the full payload shall be fetched. * * @param fetch @c true if the full payload should be fetched, @c false otherwise. */ void fetchFullPayload( bool fetch = true ); /** * Returns all explicitly fetched attributes. * * Undefined if fetchAllAttributes() returns true. * * @see fetchAttribute() */ QSet attributes() const; /** * Sets whether the attribute of the given @p type should be fetched. * * @param type The attribute type to fetch. * @param fetch @c true if the attribute should be fetched, @c false otherwise. */ void fetchAttribute( const QByteArray &type, bool fetch = true ); /** * Sets whether the attribute of the requested type should be fetched. * * @param fetch @c true if the attribute should be fetched, @c false otherwise. */ template inline void fetchAttribute( bool fetch = true ) { T dummy; fetchAttribute( dummy.type(), fetch ); } /** * Returns whether all available attributes should be fetched. * * @see fetchAllAttributes() */ bool allAttributes() const; /** * Sets whether all available attributes should be fetched. * * @param fetch @c true if all available attributes should be fetched, @c false otherwise. */ void fetchAllAttributes( bool fetch = true ); /** * Returns whether payload data should be requested from remote sources or just * from the local cache. * * @see setCacheOnly() */ bool cacheOnly() const; /** * Sets whether payload data should be requested from remote sources or just * from the local cache. * * @param cacheOnly @c true if no remote data should be requested, * @c false otherwise (the default). */ void setCacheOnly( bool cacheOnly ); + /** + * Sets how many levels of ancestor collections should be included in the retrieval. + * + * @param ancestorDepth The desired ancestor retrieval depth. + * @since 4.4 + */ + void setAncestorRetrieval( AncestorRetrieval ancestorDepth ); + + /** + * Returns the ancestor retrieval depth. + * + * @see setAncestorRetrieval() + * @since 4.4 + */ + AncestorRetrieval ancestorRetrieval() const; + /** * Returns @c true if there is nothing to fetch. */ bool isEmpty() const; private: //@cond PRIVATE QSharedDataPointer d; //@endcond }; } #endif diff --git a/akonadi/itemfetchscope_p.h b/akonadi/itemfetchscope_p.h index 32188762c..6848a57d9 100644 --- a/akonadi/itemfetchscope_p.h +++ b/akonadi/itemfetchscope_p.h @@ -1,61 +1,65 @@ /* Copyright (c) 2008 Kevin Krammer 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. */ #ifndef ITEMFETCHSCOPE_P_H #define ITEMFETCHSCOPE_P_H #include #include +#include "itemfetchscope.h" namespace Akonadi { /** * @internal */ class ItemFetchScopePrivate : public QSharedData { public: - ItemFetchScopePrivate() - : mFullPayload( false ), + ItemFetchScopePrivate() : + mAncestorDepth( ItemFetchScope::None ), + mFullPayload( false ), mAllAttributes( false ), mCacheOnly( false ) { } ItemFetchScopePrivate( const ItemFetchScopePrivate &other ) : QSharedData( other ) { mPayloadParts = other.mPayloadParts; mAttributes = other.mAttributes; + mAncestorDepth = other.mAncestorDepth; mFullPayload = other.mFullPayload; mAllAttributes = other.mAllAttributes; mCacheOnly = other.mCacheOnly; } public: QSet mPayloadParts; QSet mAttributes; + ItemFetchScope::AncestorRetrieval mAncestorDepth; bool mFullPayload; bool mAllAttributes; bool mCacheOnly; }; } #endif diff --git a/akonadi/protocolhelper.cpp b/akonadi/protocolhelper.cpp index 29ab112a0..6a40163f3 100644 --- a/akonadi/protocolhelper.cpp +++ b/akonadi/protocolhelper.cpp @@ -1,248 +1,253 @@ /* Copyright (c) 2008 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 "protocolhelper_p.h" #include "attributefactory.h" #include "collectionstatistics.h" #include "exception.h" #include #include #include #include #include #include #include using namespace Akonadi; int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start) { QVarLengthArray params; int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start ); for ( int i = 0; i < params.count() - 1; i += 2 ) { const QByteArray key = params[i]; const QByteArray value = params[i + 1]; if ( key == "INHERIT" ) policy.setInheritFromParent( value == "true" ); else if ( key == "INTERVAL" ) policy.setIntervalCheckTime( value.toInt() ); else if ( key == "CACHETIMEOUT" ) policy.setCacheTimeout( value.toInt() ); else if ( key == "SYNCONDEMAND" ) policy.setSyncOnDemand( value == "true" ); else if ( key == "LOCALPARTS" ) { QVarLengthArray tmp; QStringList parts; Akonadi::ImapParser::parseParenthesizedList( value, tmp ); for ( int j=0; j ancestors; + ImapParser::parseParenthesizedList( data, ancestors ); + Entity* current = entity; + foreach ( const QByteArray &uidRidPair, ancestors ) { + QList parentIds; + ImapParser::parseParenthesizedList( uidRidPair, parentIds ); + if ( parentIds.size() != 2 ) + break; + const Collection::Id uid = parentIds.at( 0 ).toLongLong(); + const QString rid = QString::fromUtf8( parentIds.at( 1 ) ); + if ( uid == Collection::root().id() ) { + current->setParentCollection( Collection::root() ); + break; + } + current->parentCollection().setId( uid ); + current->parentCollection().setRemoteId( rid ); + current = ¤t->parentCollection(); + } +} + int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start) { int pos = start; // collection and parent id Collection::Id colId = -1; bool ok = false; pos = ImapParser::parseNumber( data, colId, &ok, pos ); if ( !ok || colId <= 0 ) { kDebug() << "Could not parse collection id from response:" << data; return start; } Collection::Id parentId = -1; pos = ImapParser::parseNumber( data, parentId, &ok, pos ); if ( !ok || parentId < 0 ) { kDebug() << "Could not parse parent id from response:" << data; return start; } collection = Collection( colId ); collection.setParentCollection( Collection( parentId ) ); // attributes QVarLengthArray attributes; pos = ImapParser::parseParenthesizedList( data, attributes, pos ); for ( int i = 0; i < attributes.count() - 1; i += 2 ) { const QByteArray key = attributes[i]; const QByteArray value = attributes[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" ) { QVarLengthArray ct; ImapParser::parseParenthesizedList( value, ct ); QStringList ct2; for ( int j = 0; j < ct.size(); j++ ) ct2 << QString::fromLatin1( ct[j] ); collection.setContentMimeTypes( ct2 ); } else if ( key == "MESSAGES" ) { CollectionStatistics s = collection.statistics(); s.setCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "UNSEEN" ) { CollectionStatistics s = collection.statistics(); s.setUnreadCount( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "SIZE" ) { CollectionStatistics s = collection.statistics(); s.setSize( value.toLongLong() ); collection.setStatistics( s ); } else if ( key == "CACHEPOLICY" ) { CachePolicy policy; ProtocolHelper::parseCachePolicy( value, policy ); collection.setCachePolicy( policy ); } else if ( key == "ANCESTORS" ) { - QList ancestors; - ImapParser::parseParenthesizedList( value, ancestors ); - Collection* currentCol = &collection; - foreach ( const QByteArray &uidRidPair, ancestors ) { - QList parentIds; - ImapParser::parseParenthesizedList( uidRidPair, parentIds ); - if ( parentIds.size() != 2 ) - break; - const Collection::Id uid = parentIds.at( 0 ).toLongLong(); - const QString rid = QString::fromUtf8( parentIds.at( 1 ) ); - if ( uid == Collection::root().id() ) { - currentCol->setParentCollection( Collection::root() ); - break; - } - currentCol->parentCollection().setId( uid ); - currentCol->parentCollection().setRemoteId( rid ); - currentCol = ¤tCol->parentCollection(); - } + parseAncestors( value, &collection ); } else { Attribute* attr = AttributeFactory::createAttribute( key ); Q_ASSERT( attr ); attr->deserialize( value ); collection.addAttribute( attr ); } } return pos; } QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns ) { QList l; foreach ( const Attribute *attr, entity.attributes() ) { l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() ); l << ImapParser::quote( attr->serialized() ); } return ImapParser::join( l, " " ); } QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version ) { const QByteArray versionString( version != 0 ? '[' + QByteArray::number( version ) + ']' : "" ); switch ( ns ) { case PartGlobal: return label + versionString; case PartPayload: return "PLD:" + label + versionString; case PartAttribute: return "ATR:" + label + versionString; default: Q_ASSERT( false ); } return QByteArray(); } QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns ) { if ( data.startsWith( "PLD:" ) ) { //krazy:exclude=strings ns = PartPayload; return data.mid( 4 ); } else if ( data.startsWith( "ATR:" ) ) { //krazy:exclude=strings ns = PartAttribute; return data.mid( 4 ); } else { ns = PartGlobal; return data; } } QByteArray ProtocolHelper::itemSetToByteArray( const Item::List &_items, const QByteArray &command ) { if ( _items.isEmpty() ) throw Exception( "No items specified" ); Item::List items( _items ); QByteArray rv; std::sort( items.begin(), items.end(), boost::bind( &Item::id, _1 ) < boost::bind( &Item::id, _2 ) ); if ( items.first().isValid() ) { // all items have a uid set rv += " " AKONADI_CMD_UID " "; rv += command; rv += ' '; QList uids; foreach ( const Item &item, items ) uids << item.id(); ImapSet set; set.add( uids ); rv += set.toImapSequenceSet(); } else { // check if all items have a remote id QList rids; foreach ( const Item &item, items ) { if ( item.remoteId().isEmpty() ) throw Exception( i18n( "No remote identifier specified" ) ); rids << ImapParser::quote( item.remoteId().toUtf8() ); } rv += " " AKONADI_CMD_RID " "; rv += command; rv += " ("; rv += ImapParser::join( rids, " " ); rv += ')'; } return rv; } diff --git a/akonadi/protocolhelper_p.h b/akonadi/protocolhelper_p.h index d32f76137..c64767234 100644 --- a/akonadi/protocolhelper_p.h +++ b/akonadi/protocolhelper_p.h @@ -1,94 +1,99 @@ /* Copyright (c) 2008 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. */ #ifndef AKONADI_PROTOCOLHELPER_P_H #define AKONADI_PROTOCOLHELPER_P_H #include #include #include namespace Akonadi { /** @internal Helper methods for converting between libakonadi objects and their protocol representation. @todo Add unit tests for this. @todo Use exceptions for a useful error handling */ class ProtocolHelper { public: /** Part namespaces. */ enum PartNamespace { PartGlobal, PartPayload, PartAttribute }; /** Parse a cache policy definition. @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 ); /** Convert a cache policy object into its protocol representation. */ static QByteArray cachePolicyToByteArray( const CachePolicy &policy ); + /** + Convert a ancestor chain from its protocol representation into an Entity object. + */ + static void parseAncestors( const QByteArray &data, Entity *entity, int start = 0 ); + /** 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 ); /** Convert attributes to their protocol representation. */ static QByteArray attributesToByteArray( const Entity &entity, bool ns = false ); /** Encodes part label and namespace. */ static QByteArray encodePartIdentifier( PartNamespace ns, const QByteArray &label, int version = 0 ); /** Decode part label and namespace. */ static QByteArray decodePartIdentifier( const QByteArray &data, PartNamespace &ns ); /** Converts the given set of items into a protocol representation. @throws A Akonadi::Exception if the item set contains items with missing/invalid identifiers. */ static QByteArray itemSetToByteArray( const Item::List &items, const QByteArray &command ); }; } #endif diff --git a/akonadi/session_p.h b/akonadi/session_p.h index 0c7653196..7c889327d 100644 --- a/akonadi/session_p.h +++ b/akonadi/session_p.h @@ -1,115 +1,115 @@ /* Copyright (c) 2007 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. */ #ifndef AKONADI_SESSION_P_H #define AKONADI_SESSION_P_H #include "session.h" #include "imapparser_p.h" #include #include #include #include class QLocalSocket; namespace Akonadi { /** * @internal */ class SessionPrivate { public: SessionPrivate( Session *parent ) : mParent( parent ), mConnectionSettings( 0 ), protocolVersion( 0 ) { parser = new ImapParser(); } ~SessionPrivate() { delete parser; delete mConnectionSettings; } void startNext(); void reconnect(); void socketDisconnected(); void socketError( QLocalSocket::LocalSocketError error ); void dataReceived(); void doStartNext(); void startJob( Job* job ); void jobDone( KJob* job ); void jobWriteFinished( Akonadi::Job* job ); void jobDestroyed( QObject *job ); bool canPipelineNext(); /** * Creates a new default session for this thread with * the given @p sessionId. The session can be accessed * later by defaultSession(). * * You only need to call this method if you want that the * default session has a special custom id, otherwise a random unique * id is used automatically. */ static void createDefaultSession( const QByteArray &sessionId ); /** Associates the given Job object with this session. */ void addJob( Job* job ); /** Returns the next IMAP tag. */ int nextTag(); /** Sends the given raw data. */ void writeData( const QByteArray &data ); - static int minimumProtocolVersion() { return 18; } + static int minimumProtocolVersion() { return 19; } Session *mParent; QByteArray sessionId; QSettings *mConnectionSettings; QLocalSocket* socket; bool connected; int theNextTag; int protocolVersion; // job management QQueue queue; QQueue pipeline; Job* currentJob; bool jobRunning; // parser stuff ImapParser *parser; }; } #endif diff --git a/akonadi/tests/itemfetchtest.cpp b/akonadi/tests/itemfetchtest.cpp index 592bade2b..ee798a0c0 100644 --- a/akonadi/tests/itemfetchtest.cpp +++ b/akonadi/tests/itemfetchtest.cpp @@ -1,238 +1,257 @@ /* Copyright (c) 2007 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 "itemfetchtest.h" #include "itemfetchtest.moc" #include "collectionpathresolver_p.h" #include "testattribute.h" #include #include #include #include #include #include using namespace Akonadi; #include QTEST_AKONADIMAIN( ItemFetchTest, NoGUI ) void ItemFetchTest::initTestCase() { qRegisterMetaType(); AttributeFactory::registerAttribute(); } void ItemFetchTest::testFetch() { CollectionPathResolver *resolver = new CollectionPathResolver( "res1", this ); QVERIFY( resolver->exec() ); int colId = resolver->collection(); // listing of an empty folder ItemFetchJob *job = new ItemFetchJob( Collection( colId ), this ); QVERIFY( job->exec() ); QVERIFY( job->items().isEmpty() ); resolver = new CollectionPathResolver( "res1/foo", this ); QVERIFY( resolver->exec() ); int colId2 = resolver->collection(); // listing of a non-empty folder job = new ItemFetchJob( Collection( colId2 ), this ); QSignalSpy spy( job, SIGNAL(itemsReceived(Akonadi::Item::List)) ); QVERIFY( spy.isValid() ); QVERIFY( job->exec() ); Item::List items = job->items(); QCOMPARE( items.count(), 15 ); int count = 0; for ( int i = 0; i < spy.count(); ++i ) { Item::List l = spy[i][0].value(); for ( int j = 0; j < l.count(); ++j ) { QVERIFY( items.count() > count + j ); QCOMPARE( items[count + j], l[j] ); } count += l.count(); } QCOMPARE( count, items.count() ); // check if the fetch response is parsed correctly Item item = items[0]; QCOMPARE( item.remoteId(), QString( "A" ) ); QCOMPARE( item.flags().count(), 3 ); QVERIFY( item.hasFlag( "\\Seen" ) ); QVERIFY( item.hasFlag( "\\Flagged" ) ); QVERIFY( item.hasFlag( "\\Draft" ) ); item = items[1]; QCOMPARE( item.flags().count(), 1 ); QVERIFY( item.hasFlag( "\\Flagged" ) ); item = items[2]; QVERIFY( item.flags().isEmpty() ); } void ItemFetchTest::testResourceRetrieval() { Item item( 1 ); ItemFetchJob *job = new ItemFetchJob( item, this ); job->fetchScope().fetchFullPayload( true ); job->fetchScope().fetchAllAttributes( true ); job->fetchScope().setCacheOnly( true ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QCOMPARE( item.id(), 1ll ); QVERIFY( !item.remoteId().isEmpty() ); QVERIFY( !item.hasPayload() ); // not yet in cache QCOMPARE( item.attributes().count(), 1 ); job = new ItemFetchJob( item, this ); job->fetchScope().fetchFullPayload( true ); job->fetchScope().fetchAllAttributes( true ); job->fetchScope().setCacheOnly( false ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QCOMPARE( item.id(), 1ll ); QVERIFY( !item.remoteId().isEmpty() ); QVERIFY( item.hasPayload() ); QCOMPARE( item.attributes().count(), 1 ); } void ItemFetchTest::testIllegalFetch() { // fetch non-existing folder ItemFetchJob *job = new ItemFetchJob( Collection( INT_MAX ), this ); QVERIFY( !job->exec() ); // listing of root job = new ItemFetchJob( Collection::root(), this ); QVERIFY( !job->exec() ); // fetch a non-existing message job = new ItemFetchJob( Item( INT_MAX ), this ); QVERIFY( job->exec() ); QVERIFY( job->items().isEmpty() ); // fetch message with empty reference job = new ItemFetchJob( Item(), this ); QVERIFY( !job->exec() ); } void ItemFetchTest::testMultipartFetch_data() { QTest::addColumn( "fetchFullPayload" ); QTest::addColumn( "fetchAllAttrs" ); QTest::addColumn( "fetchSinglePayload" ); QTest::addColumn( "fetchSingleAttr" ); QTest::newRow( "empty" ) << false << false << false << false; QTest::newRow( "full" ) << true << true << false << false; QTest::newRow( "full payload" ) << true << false << false << false; QTest::newRow( "single payload" ) << false << false << true << false; QTest::newRow( "single" ) << false << false << true << true; QTest::newRow( "attr full" ) << false << true << false << false; QTest::newRow( "attr single" ) << false << false << false << true; QTest::newRow( "mixed cross 1" ) << true << false << false << true; QTest::newRow( "mixed cross 2" ) << false << true << true << false; QTest::newRow( "all" ) << true << true << true << true; QTest::newRow( "all payload" ) << true << false << true << false; QTest::newRow( "all attr" ) << false << true << true << false; } void ItemFetchTest::testMultipartFetch() { QFETCH( bool, fetchFullPayload ); QFETCH( bool, fetchAllAttrs ); QFETCH( bool, fetchSinglePayload ); QFETCH( bool, fetchSingleAttr ); CollectionPathResolver *resolver = new CollectionPathResolver( "res1/foo", this ); QVERIFY( resolver->exec() ); int colId = resolver->collection(); Item item; item.setMimeType( "application/octet-stream" ); item.setPayload( "body data" ); item.attribute( Item::AddIfMissing )->data = "extra data"; ItemCreateJob *job = new ItemCreateJob( item, Collection( colId ), this ); QVERIFY( job->exec() ); Item ref = job->item(); ItemFetchJob *fjob = new ItemFetchJob( ref, this ); if ( fetchFullPayload ) fjob->fetchScope().fetchFullPayload(); if ( fetchAllAttrs ) fjob->fetchScope().fetchAttribute(); if ( fetchSinglePayload ) fjob->fetchScope().fetchPayloadPart( Item::FullPayload ); if ( fetchSingleAttr ) fjob->fetchScope().fetchAttribute(); QVERIFY( fjob->exec() ); QCOMPARE( fjob->items().count(), 1 ); item = fjob->items().first(); if ( fetchFullPayload || fetchSinglePayload ) { QCOMPARE( item.loadedPayloadParts().count(), 1 ); QVERIFY( item.hasPayload() ); QCOMPARE( item.payload(), QByteArray( "body data" ) ); } else { QCOMPARE( item.loadedPayloadParts().count(), 0 ); QVERIFY( !item.hasPayload() ); } if ( fetchAllAttrs || fetchSingleAttr ) { QCOMPARE( item.attributes().count(), 1 ); QVERIFY( item.hasAttribute() ); QCOMPARE( item.attribute()->data, QByteArray( "extra data" ) ); } else { QCOMPARE( item.attributes().count(), 0 ); } // cleanup ItemDeleteJob *djob = new ItemDeleteJob( ref, this ); QVERIFY( djob->exec() ); } void ItemFetchTest::testRidFetch() { Item item; item.setRemoteId( "A" ); Collection col; col.setRemoteId( "10" ); ResourceSelectJob *select = new ResourceSelectJob( "akonadi_knut_resource_0", this ); QVERIFY( select->exec() ); ItemFetchJob *job = new ItemFetchJob( item, this ); job->setCollection( col ); QVERIFY( job->exec() ); QCOMPARE( job->items().count(), 1 ); item = job->items().first(); QVERIFY( item.isValid() ); QCOMPARE( item.remoteId(), QString::fromLatin1( "A" ) ); QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); } + +void ItemFetchTest::testAncestorRetrieval() +{ + ItemFetchJob *job = new ItemFetchJob( Item( 1 ), this ); + job->fetchScope().setAncestorRetrieval( ItemFetchScope::All ); + AKVERIFYEXEC( job ); + QCOMPARE( job->items().count(), 1 ); + const Item item = job->items().first(); + QVERIFY( item.isValid() ); + QCOMPARE( item.remoteId(), QString::fromLatin1( "A" ) ); + QCOMPARE( item.mimeType(), QString::fromLatin1( "application/octet-stream" ) ); + const Collection c = item.parentCollection(); + QCOMPARE( c.remoteId(), QString( "10" ) ); + const Collection c2 = c.parentCollection(); + QCOMPARE( c2.remoteId(), QString( "6" ) ); + const Collection c3 = c2.parentCollection(); + QCOMPARE( c3, Collection::root() ); + +} \ No newline at end of file diff --git a/akonadi/tests/itemfetchtest.h b/akonadi/tests/itemfetchtest.h index a28bc3dfd..167fef248 100644 --- a/akonadi/tests/itemfetchtest.h +++ b/akonadi/tests/itemfetchtest.h @@ -1,39 +1,40 @@ /* Copyright (c) 2007 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. */ #ifndef ITEMFETCHTEST_H #define ITEMFETCHTEST_H #include class ItemFetchTest : public QObject { Q_OBJECT private slots: void initTestCase(); void testFetch(); void testResourceRetrieval(); void testIllegalFetch(); void testMultipartFetch_data(); void testMultipartFetch(); void testRidFetch(); + void testAncestorRetrieval(); }; #endif