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