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::parseAncestors( const QByteArray &data, Entity *entity, int start )
00083 {
00084 Q_UNUSED( start );
00085
00086 QList<QByteArray> ancestors;
00087 ImapParser::parseParenthesizedList( data, ancestors );
00088 Entity* current = entity;
00089 foreach ( const QByteArray &uidRidPair, ancestors ) {
00090 QList<QByteArray> parentIds;
00091 ImapParser::parseParenthesizedList( uidRidPair, parentIds );
00092 if ( parentIds.size() != 2 )
00093 break;
00094 const Collection::Id uid = parentIds.at( 0 ).toLongLong();
00095 const QString rid = QString::fromUtf8( parentIds.at( 1 ) );
00096 if ( uid == Collection::root().id() ) {
00097 current->setParentCollection( Collection::root() );
00098 break;
00099 }
00100 current->parentCollection().setId( uid );
00101 current->parentCollection().setRemoteId( rid );
00102 current = ¤t->parentCollection();
00103 }
00104 }
00105
00106 int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start)
00107 {
00108 int pos = start;
00109
00110
00111 Collection::Id colId = -1;
00112 bool ok = false;
00113 pos = ImapParser::parseNumber( data, colId, &ok, pos );
00114 if ( !ok || colId <= 0 ) {
00115 kDebug() << "Could not parse collection id from response:" << data;
00116 return start;
00117 }
00118
00119 Collection::Id parentId = -1;
00120 pos = ImapParser::parseNumber( data, parentId, &ok, pos );
00121 if ( !ok || parentId < 0 ) {
00122 kDebug() << "Could not parse parent id from response:" << data;
00123 return start;
00124 }
00125
00126 collection = Collection( colId );
00127 collection.setParentCollection( Collection( parentId ) );
00128
00129
00130 QVarLengthArray<QByteArray,16> attributes;
00131 pos = ImapParser::parseParenthesizedList( data, attributes, pos );
00132
00133 for ( int i = 0; i < attributes.count() - 1; i += 2 ) {
00134 const QByteArray key = attributes[i];
00135 const QByteArray value = attributes[i + 1];
00136
00137 if ( key == "NAME" ) {
00138 collection.setName( QString::fromUtf8( value ) );
00139 } else if ( key == "REMOTEID" ) {
00140 collection.setRemoteId( QString::fromUtf8( value ) );
00141 } else if ( key == "RESOURCE" ) {
00142 collection.setResource( QString::fromUtf8( value ) );
00143 } else if ( key == "MIMETYPE" ) {
00144 QVarLengthArray<QByteArray,16> ct;
00145 ImapParser::parseParenthesizedList( value, ct );
00146 QStringList ct2;
00147 for ( int j = 0; j < ct.size(); j++ )
00148 ct2 << QString::fromLatin1( ct[j] );
00149 collection.setContentMimeTypes( ct2 );
00150 } else if ( key == "MESSAGES" ) {
00151 CollectionStatistics s = collection.statistics();
00152 s.setCount( value.toLongLong() );
00153 collection.setStatistics( s );
00154 } else if ( key == "UNSEEN" ) {
00155 CollectionStatistics s = collection.statistics();
00156 s.setUnreadCount( value.toLongLong() );
00157 collection.setStatistics( s );
00158 } else if ( key == "SIZE" ) {
00159 CollectionStatistics s = collection.statistics();
00160 s.setSize( value.toLongLong() );
00161 collection.setStatistics( s );
00162 } else if ( key == "CACHEPOLICY" ) {
00163 CachePolicy policy;
00164 ProtocolHelper::parseCachePolicy( value, policy );
00165 collection.setCachePolicy( policy );
00166 } else if ( key == "ANCESTORS" ) {
00167 parseAncestors( value, &collection );
00168 } else {
00169 Attribute* attr = AttributeFactory::createAttribute( key );
00170 Q_ASSERT( attr );
00171 attr->deserialize( value );
00172 collection.addAttribute( attr );
00173 }
00174 }
00175
00176 return pos;
00177 }
00178
00179 QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns )
00180 {
00181 QList<QByteArray> l;
00182 foreach ( const Attribute *attr, entity.attributes() ) {
00183 l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() );
00184 l << ImapParser::quote( attr->serialized() );
00185 }
00186 return ImapParser::join( l, " " );
00187 }
00188
00189 QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version )
00190 {
00191 const QByteArray versionString( version != 0 ? '[' + QByteArray::number( version ) + ']' : "" );
00192 switch ( ns ) {
00193 case PartGlobal:
00194 return label + versionString;
00195 case PartPayload:
00196 return "PLD:" + label + versionString;
00197 case PartAttribute:
00198 return "ATR:" + label + versionString;
00199 default:
00200 Q_ASSERT( false );
00201 }
00202 return QByteArray();
00203 }
00204
00205 QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns )
00206 {
00207 if ( data.startsWith( "PLD:" ) ) {
00208 ns = PartPayload;
00209 return data.mid( 4 );
00210 } else if ( data.startsWith( "ATR:" ) ) {
00211 ns = PartAttribute;
00212 return data.mid( 4 );
00213 } else {
00214 ns = PartGlobal;
00215 return data;
00216 }
00217 }
00218
00219 QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Collection &col )
00220 {
00221 if ( col == Collection::root() )
00222 return QByteArray("(0 \"\")");
00223 if ( col.remoteId().isEmpty() )
00224 return QByteArray();
00225 const QByteArray parentHrid = hierarchicalRidToByteArray( col.parentCollection() );
00226 return '(' + QByteArray::number( col.id() ) + ' ' + ImapParser::quote( col.remoteId().toUtf8() ) + ") " + parentHrid;
00227 }
00228
00229 QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Item &item )
00230 {
00231 const QByteArray parentHrid = hierarchicalRidToByteArray( item.parentCollection() );
00232 return '(' + QByteArray::number( item.id() ) + ' ' + ImapParser::quote( item.remoteId().toUtf8() ) + ") " + parentHrid;
00233 }
00234
00235 QByteArray ProtocolHelper::itemFetchScopeToByteArray( const ItemFetchScope &fetchScope )
00236 {
00237 QByteArray command;
00238
00239 if ( fetchScope.fullPayload() )
00240 command += " " AKONADI_PARAM_FULLPAYLOAD;
00241 if ( fetchScope.allAttributes() )
00242 command += " " AKONADI_PARAM_ALLATTRIBUTES;
00243 if ( fetchScope.cacheOnly() )
00244 command += " " AKONADI_PARAM_CACHEONLY;
00245 if ( fetchScope.ancestorRetrieval() != ItemFetchScope::None ) {
00246 switch ( fetchScope.ancestorRetrieval() ) {
00247 case ItemFetchScope::Parent:
00248 command += " ANCESTORS 1";
00249 break;
00250 case ItemFetchScope::All:
00251 command += " ANCESTORS INF";
00252 break;
00253 default:
00254 Q_ASSERT( false );
00255 }
00256 }
00257
00258
00259 command += " " AKONADI_PARAM_EXTERNALPAYLOAD;
00260
00261 command += " (UID REMOTEID COLLECTIONID FLAGS SIZE DATETIME";
00262 foreach ( const QByteArray &part, fetchScope.payloadParts() )
00263 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part );
00264 foreach ( const QByteArray &part, fetchScope.attributes() )
00265 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part );
00266 command += ")\n";
00267
00268 return command;
00269 }
00270
00271 void ProtocolHelper::parseItemFetchResult( const QList<QByteArray> &lineTokens, Item &item )
00272 {
00273
00274 Item::Id uid = -1;
00275 int rev = -1;
00276 QString rid;
00277 QString mimeType;
00278 Entity::Id cid = -1;
00279
00280 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) {
00281 const QByteArray key = lineTokens.value( i );
00282 const QByteArray value = lineTokens.value( i + 1 );
00283
00284 if ( key == "UID" )
00285 uid = value.toLongLong();
00286 else if ( key == "REV" )
00287 rev = value.toInt();
00288 else if ( key == "REMOTEID" ) {
00289 if ( !value.isEmpty() )
00290 rid = QString::fromUtf8( value );
00291 else
00292 rid.clear();
00293 } else if ( key == "COLLECTIONID" ) {
00294 cid = value.toInt();
00295 } else if ( key == "MIMETYPE" )
00296 mimeType = QString::fromLatin1( value );
00297 }
00298
00299 if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) {
00300 kWarning() << "Broken fetch response: UID, RID, REV or MIMETYPE missing!";
00301 return;
00302 }
00303
00304 item = Item( uid );
00305 item.setRemoteId( rid );
00306 item.setRevision( rev );
00307 item.setMimeType( mimeType );
00308 item.setStorageCollectionId( cid );
00309 if ( !item.isValid() )
00310 return;
00311
00312
00313 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) {
00314 const QByteArray key = lineTokens.value( i );
00315
00316 if ( key == "UID" || key == "REV" || key == "REMOTEID" || key == "MIMETYPE" || key == "COLLECTIONID")
00317 continue;
00318
00319 if ( key == "FLAGS" ) {
00320 QList<QByteArray> flags;
00321 ImapParser::parseParenthesizedList( lineTokens[i + 1], flags );
00322 foreach ( const QByteArray &flag, flags ) {
00323 item.setFlag( flag );
00324 }
00325 } else if ( key == "SIZE" ) {
00326 const quint64 size = lineTokens[i + 1].toLongLong();
00327 item.setSize( size );
00328 } else if ( key == "DATETIME" ) {
00329 QDateTime datetime;
00330 ImapParser::parseDateTime( lineTokens[i + 1], datetime );
00331 item.setModificationTime( datetime );
00332 } else if ( key == "ANCESTORS" ) {
00333 ProtocolHelper::parseAncestors( lineTokens[i + 1], &item );
00334 } else {
00335 int version = 0;
00336 QByteArray plainKey( key );
00337 ProtocolHelper::PartNamespace ns;
00338
00339 ImapParser::splitVersionedKey( key, plainKey, version );
00340 plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns );
00341
00342 switch ( ns ) {
00343 case ProtocolHelper::PartPayload:
00344 {
00345 bool isExternal = false;
00346 const QByteArray fileKey = lineTokens.value( i + 1 );
00347 if ( fileKey == "[FILE]" ) {
00348 isExternal = true;
00349 i++;
00350 kDebug() << "Payload is external: " << isExternal << " filename: " << lineTokens.value( i + 1 );
00351 }
00352 ItemSerializer::deserialize( item, plainKey, lineTokens.value( i + 1 ), version, isExternal );
00353 break;
00354 }
00355 case ProtocolHelper::PartAttribute:
00356 {
00357 Attribute* attr = AttributeFactory::createAttribute( plainKey );
00358 Q_ASSERT( attr );
00359 if ( lineTokens.value( i + 1 ) == "[FILE]" ) {
00360 ++i;
00361 QFile file( QString::fromUtf8( lineTokens.value( i + 1 ) ) );
00362 if ( file.open( QFile::ReadOnly ) )
00363 attr->deserialize( file.readAll() );
00364 else {
00365 kWarning() << "Failed to open attribute file: " << lineTokens.value( i + 1 );
00366 delete attr;
00367 }
00368 } else {
00369 attr->deserialize( lineTokens.value( i + 1 ) );
00370 }
00371 item.addAttribute( attr );
00372 break;
00373 }
00374 case ProtocolHelper::PartGlobal:
00375 default:
00376 kWarning() << "Unknown item part type:" << key;
00377 }
00378 }
00379 }
00380
00381 item.d_ptr->resetChangeLog();
00382 }