00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "protocolhelper_p.h"
00021
00022 #include "attributefactory.h"
00023 #include "collectionstatistics.h"
00024 #include "entity_p.h"
00025 #include "exception.h"
00026 #include "itemserializer_p.h"
00027 #include "itemserializerplugin.h"
00028
00029 #include <QtCore/QDateTime>
00030 #include <QtCore/QFile>
00031 #include <QtCore/QVarLengthArray>
00032
00033 #include <kdebug.h>
00034 #include <klocale.h>
00035
00036 using namespace Akonadi;
00037
00038 int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start)
00039 {
00040 QVarLengthArray<QByteArray,16> params;
00041 int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start );
00042 for ( int i = 0; i < params.count() - 1; i += 2 ) {
00043 const QByteArray key = params[i];
00044 const QByteArray value = params[i + 1];
00045
00046 if ( key == "INHERIT" )
00047 policy.setInheritFromParent( value == "true" );
00048 else if ( key == "INTERVAL" )
00049 policy.setIntervalCheckTime( value.toInt() );
00050 else if ( key == "CACHETIMEOUT" )
00051 policy.setCacheTimeout( value.toInt() );
00052 else if ( key == "SYNCONDEMAND" )
00053 policy.setSyncOnDemand( value == "true" );
00054 else if ( key == "LOCALPARTS" ) {
00055 QVarLengthArray<QByteArray,16> tmp;
00056 QStringList parts;
00057 Akonadi::ImapParser::parseParenthesizedList( value, tmp );
00058 for ( int j=0; j<tmp.size(); j++ )
00059 parts << QString::fromLatin1( tmp[j] );
00060 policy.setLocalParts( parts );
00061 }
00062 }
00063 return end;
00064 }
00065
00066 QByteArray ProtocolHelper::cachePolicyToByteArray(const CachePolicy & policy)
00067 {
00068 QByteArray rv = "CACHEPOLICY (";
00069 if ( policy.inheritFromParent() ) {
00070 rv += "INHERIT true";
00071 } else {
00072 rv += "INHERIT false";
00073 rv += " INTERVAL " + QByteArray::number( policy.intervalCheckTime() );
00074 rv += " CACHETIMEOUT " + QByteArray::number( policy.cacheTimeout() );
00075 rv += " SYNCONDEMAND " + ( policy.syncOnDemand() ? QByteArray("true") : QByteArray("false") );
00076 rv += " LOCALPARTS (" + policy.localParts().join( QLatin1String(" ") ).toLatin1() + ')';
00077 }
00078 rv += ')';
00079 return rv;
00080 }
00081
00082 void ProtocolHelper::parseAncestorsCached( const QByteArray &data, Entity *entity, Collection::Id parentCollection,
00083 ProtocolHelperValuePool *pool, int start )
00084 {
00085 if ( !pool || parentCollection == -1 ) {
00086
00087 parseAncestors( data, entity, start );
00088 return;
00089 }
00090
00091 if ( pool->ancestorCollections.contains( parentCollection ) ) {
00092
00093 entity->setParentCollection( pool->ancestorCollections.value( parentCollection ) );
00094 } else {
00095
00096 parseAncestors( data, entity, start );
00097 pool->ancestorCollections.insert( parentCollection, entity->parentCollection() );
00098 }
00099 }
00100
00101 void ProtocolHelper::parseAncestors( const QByteArray &data, Entity *entity, int start )
00102 {
00103 Q_UNUSED( start );
00104
00105 static const Collection::Id rootCollectionId = Collection::root().id();
00106 QVarLengthArray<QByteArray, 16> ancestors;
00107 QVarLengthArray<QByteArray, 16> parentIds;
00108
00109 ImapParser::parseParenthesizedList( data, ancestors );
00110 Entity* current = entity;
00111 for ( int i = 0; i < ancestors.count(); ++i ) {
00112 parentIds.clear();
00113 ImapParser::parseParenthesizedList( ancestors[ i ], parentIds );
00114 if ( parentIds.size() != 2 )
00115 break;
00116
00117 const Collection::Id uid = parentIds[ 0 ].toLongLong();
00118 if ( uid == rootCollectionId ) {
00119 current->setParentCollection( Collection::root() );
00120 break;
00121 }
00122
00123 current->parentCollection().setId( uid );
00124 current->parentCollection().setRemoteId( QString::fromUtf8( parentIds[ 1 ] ) );
00125 current = ¤t->parentCollection();
00126 }
00127 }
00128
00129 int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start)
00130 {
00131 int pos = start;
00132
00133
00134 Collection::Id colId = -1;
00135 bool ok = false;
00136 pos = ImapParser::parseNumber( data, colId, &ok, pos );
00137 if ( !ok || colId <= 0 ) {
00138 kDebug() << "Could not parse collection id from response:" << data;
00139 return start;
00140 }
00141
00142 Collection::Id parentId = -1;
00143 pos = ImapParser::parseNumber( data, parentId, &ok, pos );
00144 if ( !ok || parentId < 0 ) {
00145 kDebug() << "Could not parse parent id from response:" << data;
00146 return start;
00147 }
00148
00149 collection = Collection( colId );
00150 collection.setParentCollection( Collection( parentId ) );
00151
00152
00153 QVarLengthArray<QByteArray,16> attributes;
00154 pos = ImapParser::parseParenthesizedList( data, attributes, pos );
00155
00156 for ( int i = 0; i < attributes.count() - 1; i += 2 ) {
00157 const QByteArray key = attributes[i];
00158 const QByteArray value = attributes[i + 1];
00159
00160 if ( key == "NAME" ) {
00161 collection.setName( QString::fromUtf8( value ) );
00162 } else if ( key == "REMOTEID" ) {
00163 collection.setRemoteId( QString::fromUtf8( value ) );
00164 } else if ( key == "REMOTEREVISION" ) {
00165 collection.setRemoteRevision( QString::fromUtf8( value ) );
00166 } else if ( key == "RESOURCE" ) {
00167 collection.setResource( QString::fromUtf8( value ) );
00168 } else if ( key == "MIMETYPE" ) {
00169 QVarLengthArray<QByteArray,16> ct;
00170 ImapParser::parseParenthesizedList( value, ct );
00171 QStringList ct2;
00172 for ( int j = 0; j < ct.size(); j++ )
00173 ct2 << QString::fromLatin1( ct[j] );
00174 collection.setContentMimeTypes( ct2 );
00175 } else if ( key == "MESSAGES" ) {
00176 CollectionStatistics s = collection.statistics();
00177 s.setCount( value.toLongLong() );
00178 collection.setStatistics( s );
00179 } else if ( key == "UNSEEN" ) {
00180 CollectionStatistics s = collection.statistics();
00181 s.setUnreadCount( value.toLongLong() );
00182 collection.setStatistics( s );
00183 } else if ( key == "SIZE" ) {
00184 CollectionStatistics s = collection.statistics();
00185 s.setSize( value.toLongLong() );
00186 collection.setStatistics( s );
00187 } else if ( key == "CACHEPOLICY" ) {
00188 CachePolicy policy;
00189 ProtocolHelper::parseCachePolicy( value, policy );
00190 collection.setCachePolicy( policy );
00191 } else if ( key == "ANCESTORS" ) {
00192 parseAncestors( value, &collection );
00193 } else {
00194 Attribute* attr = AttributeFactory::createAttribute( key );
00195 Q_ASSERT( attr );
00196 attr->deserialize( value );
00197 collection.addAttribute( attr );
00198 }
00199 }
00200
00201 return pos;
00202 }
00203
00204 QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns )
00205 {
00206 QList<QByteArray> l;
00207 foreach ( const Attribute *attr, entity.attributes() ) {
00208 l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() );
00209 l << ImapParser::quote( attr->serialized() );
00210 }
00211 return ImapParser::join( l, " " );
00212 }
00213
00214 QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version )
00215 {
00216 const QByteArray versionString( version != 0 ? '[' + QByteArray::number( version ) + ']' : "" );
00217 switch ( ns ) {
00218 case PartGlobal:
00219 return label + versionString;
00220 case PartPayload:
00221 return "PLD:" + label + versionString;
00222 case PartAttribute:
00223 return "ATR:" + label + versionString;
00224 default:
00225 Q_ASSERT( false );
00226 }
00227 return QByteArray();
00228 }
00229
00230 QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns )
00231 {
00232 if ( data.startsWith( "PLD:" ) ) {
00233 ns = PartPayload;
00234 return data.mid( 4 );
00235 } else if ( data.startsWith( "ATR:" ) ) {
00236 ns = PartAttribute;
00237 return data.mid( 4 );
00238 } else {
00239 ns = PartGlobal;
00240 return data;
00241 }
00242 }
00243
00244 QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Collection &col )
00245 {
00246 if ( col == Collection::root() )
00247 return QByteArray("(0 \"\")");
00248 if ( col.remoteId().isEmpty() )
00249 return QByteArray();
00250 const QByteArray parentHrid = hierarchicalRidToByteArray( col.parentCollection() );
00251 return '(' + QByteArray::number( col.id() ) + ' ' + ImapParser::quote( col.remoteId().toUtf8() ) + ") " + parentHrid;
00252 }
00253
00254 QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Item &item )
00255 {
00256 const QByteArray parentHrid = hierarchicalRidToByteArray( item.parentCollection() );
00257 return '(' + QByteArray::number( item.id() ) + ' ' + ImapParser::quote( item.remoteId().toUtf8() ) + ") " + parentHrid;
00258 }
00259
00260 QByteArray ProtocolHelper::itemFetchScopeToByteArray( const ItemFetchScope &fetchScope )
00261 {
00262 QByteArray command;
00263
00264 if ( fetchScope.fullPayload() )
00265 command += " " AKONADI_PARAM_FULLPAYLOAD;
00266 if ( fetchScope.allAttributes() )
00267 command += " " AKONADI_PARAM_ALLATTRIBUTES;
00268 if ( fetchScope.cacheOnly() )
00269 command += " " AKONADI_PARAM_CACHEONLY;
00270 if ( fetchScope.ancestorRetrieval() != ItemFetchScope::None ) {
00271 switch ( fetchScope.ancestorRetrieval() ) {
00272 case ItemFetchScope::Parent:
00273 command += " ANCESTORS 1";
00274 break;
00275 case ItemFetchScope::All:
00276 command += " ANCESTORS INF";
00277 break;
00278 default:
00279 Q_ASSERT( false );
00280 }
00281 }
00282
00283
00284 command += " " AKONADI_PARAM_EXTERNALPAYLOAD;
00285
00286 command += " (UID REMOTEID REMOTEREVISION COLLECTIONID FLAGS SIZE DATETIME";
00287 foreach ( const QByteArray &part, fetchScope.payloadParts() )
00288 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part );
00289 foreach ( const QByteArray &part, fetchScope.attributes() )
00290 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part );
00291 command += ")\n";
00292
00293 return command;
00294 }
00295
00296 void ProtocolHelper::parseItemFetchResult( const QList<QByteArray> &lineTokens, Item &item, ProtocolHelperValuePool *valuePool )
00297 {
00298
00299 Item::Id uid = -1;
00300 int rev = -1;
00301 QString rid;
00302 QString remoteRevision;
00303 QString mimeType;
00304 Entity::Id cid = -1;
00305
00306 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) {
00307 const QByteArray key = lineTokens.value( i );
00308 const QByteArray value = lineTokens.value( i + 1 );
00309
00310 if ( key == "UID" )
00311 uid = value.toLongLong();
00312 else if ( key == "REV" )
00313 rev = value.toInt();
00314 else if ( key == "REMOTEID" ) {
00315 if ( !value.isEmpty() )
00316 rid = QString::fromUtf8( value );
00317 else
00318 rid.clear();
00319 } else if ( key == "REMOTEREVISION" ) {
00320 remoteRevision = QString::fromUtf8( value );
00321 } else if ( key == "COLLECTIONID" ) {
00322 cid = value.toInt();
00323 } else if ( key == "MIMETYPE" ) {
00324 if ( valuePool )
00325 mimeType = valuePool->mimeTypePool.sharedValue( QString::fromLatin1( value ) );
00326 else
00327 mimeType = QString::fromLatin1( value );
00328 }
00329 }
00330
00331 if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) {
00332 kWarning() << "Broken fetch response: UID, RID, REV or MIMETYPE missing!";
00333 return;
00334 }
00335
00336 item = Item( uid );
00337 item.setRemoteId( rid );
00338 item.setRevision( rev );
00339 item.setRemoteRevision( remoteRevision );
00340 item.setMimeType( mimeType );
00341 item.setStorageCollectionId( cid );
00342 if ( !item.isValid() )
00343 return;
00344
00345
00346 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) {
00347 const QByteArray key = lineTokens.value( i );
00348
00349 if ( key == "UID" || key == "REV" || key == "REMOTEID" ||
00350 key == "MIMETYPE" || key == "COLLECTIONID" || key == "REMOTEREVISION" )
00351 continue;
00352
00353 if ( key == "FLAGS" ) {
00354 QList<QByteArray> flags;
00355 ImapParser::parseParenthesizedList( lineTokens[i + 1], flags );
00356 if ( !flags.isEmpty() ) {
00357 Item::Flags convertedFlags;
00358 convertedFlags.reserve( flags.size() );
00359 foreach ( const QByteArray &flag, flags ) {
00360 if ( valuePool )
00361 convertedFlags.insert( valuePool->flagPool.sharedValue( flag ) );
00362 else
00363 convertedFlags.insert( flag );
00364 }
00365 item.setFlags( convertedFlags );
00366 }
00367 } else if ( key == "SIZE" ) {
00368 const quint64 size = lineTokens[i + 1].toLongLong();
00369 item.setSize( size );
00370 } else if ( key == "DATETIME" ) {
00371 QDateTime datetime;
00372 ImapParser::parseDateTime( lineTokens[i + 1], datetime );
00373 item.setModificationTime( datetime );
00374 } else if ( key == "ANCESTORS" ) {
00375 ProtocolHelper::parseAncestorsCached( lineTokens[i + 1], &item, cid, valuePool );
00376 } else {
00377 int version = 0;
00378 QByteArray plainKey( key );
00379 ProtocolHelper::PartNamespace ns;
00380
00381 ImapParser::splitVersionedKey( key, plainKey, version );
00382 plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns );
00383
00384 switch ( ns ) {
00385 case ProtocolHelper::PartPayload:
00386 {
00387 bool isExternal = false;
00388 const QByteArray fileKey = lineTokens.value( i + 1 );
00389 if ( fileKey == "[FILE]" ) {
00390 isExternal = true;
00391 i++;
00392
00393 }
00394 ItemSerializer::deserialize( item, plainKey, lineTokens.value( i + 1 ), version, isExternal );
00395 break;
00396 }
00397 case ProtocolHelper::PartAttribute:
00398 {
00399 Attribute* attr = AttributeFactory::createAttribute( plainKey );
00400 Q_ASSERT( attr );
00401 if ( lineTokens.value( i + 1 ) == "[FILE]" ) {
00402 ++i;
00403 QFile file( QString::fromUtf8( lineTokens.value( i + 1 ) ) );
00404 if ( file.open( QFile::ReadOnly ) )
00405 attr->deserialize( file.readAll() );
00406 else {
00407 kWarning() << "Failed to open attribute file: " << lineTokens.value( i + 1 );
00408 delete attr;
00409 }
00410 } else {
00411 attr->deserialize( lineTokens.value( i + 1 ) );
00412 }
00413 item.addAttribute( attr );
00414 break;
00415 }
00416 case ProtocolHelper::PartGlobal:
00417 default:
00418 kWarning() << "Unknown item part type:" << key;
00419 }
00420 }
00421 }
00422
00423 item.d_ptr->resetChangeLog();
00424 }