protocolhelper.cpp
00001 /* 00002 Copyright (c) 2008 Volker Krause <vkrause@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 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 // if no pool or parent collection id is provided we can't cache anything, so continue as usual 00087 parseAncestors( data, entity, start ); 00088 return; 00089 } 00090 00091 if ( pool->ancestorCollections.contains( parentCollection ) ) { 00092 // ancestor chain is cached already, so use the cached value 00093 entity->setParentCollection( pool->ancestorCollections.value( parentCollection ) ); 00094 } else { 00095 // not cached yet, parse the chain 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 // collection and parent id 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 // attributes 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:" ) ) { //krazy:exclude=strings 00233 ns = PartPayload; 00234 return data.mid( 4 ); 00235 } else if ( data.startsWith( "ATR:" ) ) { //krazy:exclude=strings 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 //TODO: detect somehow if server supports external payload attribute 00284 command += " " AKONADI_PARAM_EXTERNALPAYLOAD; 00285 00286 command += " (UID REMOTEID REMOTEREVISION COLLECTIONID FLAGS SIZE"; 00287 if ( fetchScope.fetchModificationTime() ) 00288 command += " DATETIME"; 00289 foreach ( const QByteArray &part, fetchScope.payloadParts() ) 00290 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part ); 00291 foreach ( const QByteArray &part, fetchScope.attributes() ) 00292 command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part ); 00293 command += ")\n"; 00294 00295 return command; 00296 } 00297 00298 void ProtocolHelper::parseItemFetchResult( const QList<QByteArray> &lineTokens, Item &item, ProtocolHelperValuePool *valuePool ) 00299 { 00300 // create a new item object 00301 Item::Id uid = -1; 00302 int rev = -1; 00303 QString rid; 00304 QString remoteRevision; 00305 QString mimeType; 00306 Entity::Id cid = -1; 00307 00308 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) { 00309 const QByteArray key = lineTokens.value( i ); 00310 const QByteArray value = lineTokens.value( i + 1 ); 00311 00312 if ( key == "UID" ) 00313 uid = value.toLongLong(); 00314 else if ( key == "REV" ) 00315 rev = value.toInt(); 00316 else if ( key == "REMOTEID" ) { 00317 if ( !value.isEmpty() ) 00318 rid = QString::fromUtf8( value ); 00319 else 00320 rid.clear(); 00321 } else if ( key == "REMOTEREVISION" ) { 00322 remoteRevision = QString::fromUtf8( value ); 00323 } else if ( key == "COLLECTIONID" ) { 00324 cid = value.toInt(); 00325 } else if ( key == "MIMETYPE" ) { 00326 if ( valuePool ) 00327 mimeType = valuePool->mimeTypePool.sharedValue( QString::fromLatin1( value ) ); 00328 else 00329 mimeType = QString::fromLatin1( value ); 00330 } 00331 } 00332 00333 if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) { 00334 kWarning() << "Broken fetch response: UID, RID, REV or MIMETYPE missing!"; 00335 return; 00336 } 00337 00338 item = Item( uid ); 00339 item.setRemoteId( rid ); 00340 item.setRevision( rev ); 00341 item.setRemoteRevision( remoteRevision ); 00342 item.setMimeType( mimeType ); 00343 item.setStorageCollectionId( cid ); 00344 if ( !item.isValid() ) 00345 return; 00346 00347 // parse fetch response fields 00348 for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) { 00349 const QByteArray key = lineTokens.value( i ); 00350 // skip stuff we dealt with already 00351 if ( key == "UID" || key == "REV" || key == "REMOTEID" || 00352 key == "MIMETYPE" || key == "COLLECTIONID" || key == "REMOTEREVISION" ) 00353 continue; 00354 // flags 00355 if ( key == "FLAGS" ) { 00356 QList<QByteArray> flags; 00357 ImapParser::parseParenthesizedList( lineTokens[i + 1], flags ); 00358 if ( !flags.isEmpty() ) { 00359 Item::Flags convertedFlags; 00360 convertedFlags.reserve( flags.size() ); 00361 foreach ( const QByteArray &flag, flags ) { 00362 if ( valuePool ) 00363 convertedFlags.insert( valuePool->flagPool.sharedValue( flag ) ); 00364 else 00365 convertedFlags.insert( flag ); 00366 } 00367 item.setFlags( convertedFlags ); 00368 } 00369 } else if ( key == "SIZE" ) { 00370 const quint64 size = lineTokens[i + 1].toLongLong(); 00371 item.setSize( size ); 00372 } else if ( key == "DATETIME" ) { 00373 QDateTime datetime; 00374 ImapParser::parseDateTime( lineTokens[i + 1], datetime ); 00375 item.setModificationTime( datetime ); 00376 } else if ( key == "ANCESTORS" ) { 00377 ProtocolHelper::parseAncestorsCached( lineTokens[i + 1], &item, cid, valuePool ); 00378 } else { 00379 int version = 0; 00380 QByteArray plainKey( key ); 00381 ProtocolHelper::PartNamespace ns; 00382 00383 ImapParser::splitVersionedKey( key, plainKey, version ); 00384 plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns ); 00385 00386 switch ( ns ) { 00387 case ProtocolHelper::PartPayload: 00388 { 00389 bool isExternal = false; 00390 const QByteArray fileKey = lineTokens.value( i + 1 ); 00391 if ( fileKey == "[FILE]" ) { 00392 isExternal = true; 00393 i++; 00394 //kDebug() << "Payload is external: " << isExternal << " filename: " << lineTokens.value( i + 1 ); 00395 } 00396 ItemSerializer::deserialize( item, plainKey, lineTokens.value( i + 1 ), version, isExternal ); 00397 break; 00398 } 00399 case ProtocolHelper::PartAttribute: 00400 { 00401 Attribute* attr = AttributeFactory::createAttribute( plainKey ); 00402 Q_ASSERT( attr ); 00403 if ( lineTokens.value( i + 1 ) == "[FILE]" ) { 00404 ++i; 00405 QFile file( QString::fromUtf8( lineTokens.value( i + 1 ) ) ); 00406 if ( file.open( QFile::ReadOnly ) ) 00407 attr->deserialize( file.readAll() ); 00408 else { 00409 kWarning() << "Failed to open attribute file: " << lineTokens.value( i + 1 ); 00410 delete attr; 00411 attr = 0; 00412 } 00413 } else { 00414 attr->deserialize( lineTokens.value( i + 1 ) ); 00415 } 00416 if ( attr ) 00417 item.addAttribute( attr ); 00418 break; 00419 } 00420 case ProtocolHelper::PartGlobal: 00421 default: 00422 kWarning() << "Unknown item part type:" << key; 00423 } 00424 } 00425 } 00426 00427 item.d_ptr->resetChangeLog(); 00428 }